一、单类型数据源(例如:两个mysql)
1、spring-mybatis.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 数据源1 -->
<bean id="childDataSourceOne" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="clone">
<!-- 基本属性driverClassName、 url、user、password -->
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<!-- 通常来说,只需要修改initialSize、minIdle、maxActive -->
<!-- 初始化时建立物理连接的个数,缺省值为0 -->
<property name="initialSize" value="10"/>
<!-- 最小连接池数量 -->
<property name="minIdle" value="10"/>
<!-- 最大连接池数量,缺省值为8 -->
<property name="maxActive" value="500"/>
<!-- 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 -->
<property name="maxWait" value="60000"/>
<!--
有些数据库连接的时候有超时限制(MySQL连接在8小时后断开),或者由于网络中断等原因,连接池的连接会出现失效的情况,这时候可以设置一个testWhileIdle参数为true,
如果检测到当前连接不活跃的时间超过了timeBetweenEvictionRunsMillis,则去手动检测一下当前连接的有效性,在保证确实有效后才加以使用。
在检测活跃性时,如果当前的活跃时间大于minEvictableIdleTimeMillis,则认为需要关闭当前连接。当
然,为了保证绝对的可用性,你也可以使用testOnBorrow为true(即在每次获取Connection对象时都检测其可用性),不过这样会影响性能。
-->
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒(3600000:为1小时) -->
<property name="timeBetweenEvictionRunsMillis" value="3600000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒(300000:为5分钟) -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<!-- 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 -->
<property name="validationQuery" value="${jdbc.validationQuery}"/>
<!-- 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。建议配置为true,不影响性能,并且保证安全性。-->
<property name="testWhileIdle" value="true"/>
<!-- 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。缺省值:true -->
<property name="testOnBorrow" value="false"/>
<!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。缺省值:false -->
<property name="testOnReturn" value="false"/>
<!-- 监控数据库 -->
<property name="filters" value="mergeStat"/>
</bean>
<!-- 数据源2 -->
<bean id="childDataSourceTwo" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="clone">
<!-- 基本属性driverClassName、 url、user、password -->
<property name="driverClassName" value="${jdbc2.driver}"/>
<property name="url" value="${jdbc2.url}"/>
<property name="username" value="${jdbc2.username}"/>
<property name="password" value="${jdbc2.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<!-- 通常来说,只需要修改initialSize、minIdle、maxActive -->
<!-- 初始化时建立物理连接的个数,缺省值为0 -->
<property name="initialSize" value="10"/>
<!-- 最小连接池数量 -->
<property name="minIdle" value="10"/>
<!-- 最大连接池数量,缺省值为8 -->
<property name="maxActive" value="500"/>
<!-- 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 -->
<property name="maxWait" value="60000"/>
<!--
有些数据库连接的时候有超时限制(MySQL连接在8小时后断开),或者由于网络中断等原因,连接池的连接会出现失效的情况,这时候可以设置一个testWhileIdle参数为true,
如果检测到当前连接不活跃的时间超过了timeBetweenEvictionRunsMillis,则去手动检测一下当前连接的有效性,在保证确实有效后才加以使用。
在检测活跃性时,如果当前的活跃时间大于minEvictableIdleTimeMillis,则认为需要关闭当前连接。当
然,为了保证绝对的可用性,你也可以使用testOnBorrow为true(即在每次获取Connection对象时都检测其可用性),不过这样会影响性能。
-->
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒(3600000:为1小时) -->
<property name="timeBetweenEvictionRunsMillis" value="3600000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒(300000:为5分钟) -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<!-- 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 -->
<property name="validationQuery" value="${jdbc.validationQuery}"/>
<!-- 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。建议配置为true,不影响性能,并且保证安全性。-->
<property name="testWhileIdle" value="true"/>
<!-- 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。缺省值:true -->
<property name="testOnBorrow" value="false"/>
<!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。缺省值:false -->
<property name="testOnReturn" value="false"/>
<!-- 监控数据库 -->
<property name="filters" value="mergeStat"/>
</bean>
<bean id="dataSourceSwitcher" class="com.demo.DataSourceSwitcher">
<property name="targetDataSources">
<map>
<entry key="ds1" value-ref="childDataSourceOne"/>
<entry key="ds2" value-ref="childDataSourceTwo"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="childDataSourceOne"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/demo/**/*.xml"/>
<property name="dataSource" ref="dataSourceSwitcher"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.demo.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!--======= 事务配置 Begin ================= -->
<!-- 事务管理器(由Spring管理MyBatis的事务) -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceSwitcher"/>
</bean>
<!-- 注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!--======= 事务配置 End =================== -->
<!-- 配置druid监控spring jdbc -->
<bean id="druid-stat-interceptor" class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor"/>
<bean id="druid-stat-pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut" scope="prototype">
<property name="patterns">
<list>
<value>com.demo.service.impl.*</value>
</list>
</property>
</bean>
<aop:config>
<aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut"/>
</aop:config>
</beans>
2、UseDataSource.java
package com.demo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <p>
* <code>UseDataSource</code>
* </p>
* Description:
*
* @author
* @date 2020-07-27 下午 05:12
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseDataSource {
String source() default "";
}
3、DataSourceType.java
package com.demo.enums;
import lombok.Getter;
/**
* <p>
* <code>DataSourceType</code>
* </p>
* Description:
*
* @author
* @date 2020-07-27 下午 05:12
*/
@Getter
public enum DataSourceType {
SOURCE_1("ds1", "数据源1-默认数据源"),
SOURCE_2("ds2", "数据源2");
DataSourceType(String source, String desc) {
this.source = source;
this.desc = desc;
}
/**
* 数据源标识
*/
String source;
/**
* 数据源描述
*/
String desc;
}
4、DataSourceSwitcher.java
package com.demo.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* <p>
* <code>DataSourceSwitcher</code>
* </p>
* Description: 数据源切换
*
* @author
* @date 2020-07-27 下午 05:12
*/
@Slf4j
public class DataSourceSwitcher extends AbstractRoutingDataSource {
/**
* ThreadLocal是线程安全的,并且不能在多线程之间共享
*/
private static final ThreadLocal<String> DATA_SOURCE_KEY = new ThreadLocal<>();
public static void clearDataSourceType() {
String s = DATA_SOURCE_KEY.get();
log.info("thread:{},remove,dataSource:{}", Thread.currentThread().getName(), s);
DATA_SOURCE_KEY.remove();
}
@Override
protected Object determineCurrentLookupKey() {
String s = DATA_SOURCE_KEY.get();
log.info("thread:{},determine,dataSource:{}", Thread.currentThread().getName(), s);
return s;
}
public static void setDataSourceKey(String dataSource) {
log.info("thread:{},set,dataSource:{}", Thread.currentThread().getName(), dataSource);
DATA_SOURCE_KEY.set(dataSource);
}
public static String getDataSourceKey() {
String s = DATA_SOURCE_KEY.get();
log.info("thread:{},get,dataSource:{}", Thread.currentThread().getName(), s);
return s;
}
}
5、DataSourceAsp.java
package com.demo.aop;
import com.demo.annotation.UseDataSource;
import com.demo.enums.DataSourceType;
import com.demo.util.DataSourceSwitcher;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* <p>
* <code>DataSourceAsp</code>
* </p>
* Description: 数据源切换
*
* @author
* @date 2020-07-27 下午 05:12
*/
@Component
@Aspect
@Slf4j
@Order(Ordered.LOWEST_PRECEDENCE - 1)
public class DataSourceAsp {
@Pointcut("@annotation(com.demo.annotation.UseDataSource)")
public void useDataSource() {
}
@Around(value = "useDataSource() && @annotation(dataSource)")
public Object dataSourceSwitcher(ProceedingJoinPoint joinPoint, UseDataSource dataSource) throws Throwable {
try {
String ds = dataSource.source();
ds = StringUtils.isBlank(ds) ? DataSourceType.SOURCE_1.getSource() : ds;
DataSourceSwitcher.setDataSourceKey(ds);
return joinPoint.proceed();
} catch (Exception e) {
log.error("==>thread:{}, execute error", Thread.currentThread().getName(), e);
throw e;
} finally {
DataSourceSwitcher.setDataSourceKey(DataSourceType.SOURCE_1.getSource());
}
}
}
6、使用方法
注解加到serviceImpl上,例如:
@Override
@UseDataSource(source = "ds2")
@Transactional(rollbackFor = Exception.class)
public void save() {}
二、多类型数据源(例如:一个mysql,一个oracle)
1、spring-mybatis.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 数据源1 -->
<bean id="childDataSourceOne" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="clone">
<!-- 基本属性driverClassName、 url、user、password -->
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<!-- 通常来说,只需要修改initialSize、minIdle、maxActive -->
<!-- 初始化时建立物理连接的个数,缺省值为0 -->
<property name="initialSize" value="10"/>
<!-- 最小连接池数量 -->
<property name="minIdle" value="10"/>
<!-- 最大连接池数量,缺省值为8 -->
<property name="maxActive" value="500"/>
<!-- 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 -->
<property name="maxWait" value="60000"/>
<!--
有些数据库连接的时候有超时限制(MySQL连接在8小时后断开),或者由于网络中断等原因,连接池的连接会出现失效的情况,这时候可以设置一个testWhileIdle参数为true,
如果检测到当前连接不活跃的时间超过了timeBetweenEvictionRunsMillis,则去手动检测一下当前连接的有效性,在保证确实有效后才加以使用。
在检测活跃性时,如果当前的活跃时间大于minEvictableIdleTimeMillis,则认为需要关闭当前连接。当
然,为了保证绝对的可用性,你也可以使用testOnBorrow为true(即在每次获取Connection对象时都检测其可用性),不过这样会影响性能。
-->
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒(3600000:为1小时) -->
<property name="timeBetweenEvictionRunsMillis" value="3600000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒(300000:为5分钟) -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<!-- 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 -->
<property name="validationQuery" value="${jdbc.validationQuery}"/>
<!-- 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。建议配置为true,不影响性能,并且保证安全性。-->
<property name="testWhileIdle" value="true"/>
<!-- 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。缺省值:true -->
<property name="testOnBorrow" value="false"/>
<!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。缺省值:false -->
<property name="testOnReturn" value="false"/>
<!-- 监控数据库 -->
<property name="filters" value="mergeStat"/>
</bean>
<!-- 数据源2 -->
<bean id="childDataSourceTwo" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="clone">
<!-- 基本属性driverClassName、 url、user、password -->
<property name="driverClassName" value="${jdbc2.driver}"/>
<property name="url" value="${jdbc2.url}"/>
<property name="username" value="${jdbc2.username}"/>
<property name="password" value="${jdbc2.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<!-- 通常来说,只需要修改initialSize、minIdle、maxActive -->
<!-- 初始化时建立物理连接的个数,缺省值为0 -->
<property name="initialSize" value="10"/>
<!-- 最小连接池数量 -->
<property name="minIdle" value="10"/>
<!-- 最大连接池数量,缺省值为8 -->
<property name="maxActive" value="500"/>
<!-- 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 -->
<property name="maxWait" value="60000"/>
<!--
有些数据库连接的时候有超时限制(MySQL连接在8小时后断开),或者由于网络中断等原因,连接池的连接会出现失效的情况,这时候可以设置一个testWhileIdle参数为true,
如果检测到当前连接不活跃的时间超过了timeBetweenEvictionRunsMillis,则去手动检测一下当前连接的有效性,在保证确实有效后才加以使用。
在检测活跃性时,如果当前的活跃时间大于minEvictableIdleTimeMillis,则认为需要关闭当前连接。当
然,为了保证绝对的可用性,你也可以使用testOnBorrow为true(即在每次获取Connection对象时都检测其可用性),不过这样会影响性能。
-->
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒(3600000:为1小时) -->
<property name="timeBetweenEvictionRunsMillis" value="3600000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒(300000:为5分钟) -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<!-- 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 -->
<property name="validationQuery" value="${jdbc.validationQuery}"/>
<!-- 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。建议配置为true,不影响性能,并且保证安全性。-->
<property name="testWhileIdle" value="true"/>
<!-- 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。缺省值:true -->
<property name="testOnBorrow" value="false"/>
<!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。缺省值:false -->
<property name="testOnReturn" value="false"/>
<!-- 监控数据库 -->
<property name="filters" value="mergeStat"/>
</bean>
<bean id="dataSourceSwitcher" class="com.demo.util.DataSourceSwitcher">
<property name="targetDataSources">
<map>
<entry key="ds1" value-ref="childDataSourceOne"/>
<entry key="ds2" value-ref="childDataSourceTwo"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="childDataSourceOne"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis/mybatis-config-oracle.xml"/>
<property name="mapperLocations" value="classpath:com/demo/dao/oracle/*.xml"/>
<property name="dataSource" ref="dataSourceSwitcher"/>
</bean>
<bean id="sqlSessionFactoryTwo" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis/mybatis-config-mysql.xml"/>
<property name="mapperLocations" value="classpath:com/demo/dao/mysql/*.xml"/>
<property name="dataSource" ref="dataSourceSwitcher"/>
</bean>
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.demo.dao.oracle"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<bean id="mapperScannerConfigurerTwo" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.demo.dao.mysql"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryTwo"/>
</bean>
<!--======= 事务配置 Begin ================= -->
<!-- 事务管理器(由Spring管理MyBatis的事务) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceSwitcher"/>
</bean>
<!-- 注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!--======= 事务配置 End =================== -->
<!-- 配置druid监控spring jdbc -->
<bean id="druid-stat-interceptor" class="com.alibaba.druid.support.spring.stat.DruidStatInterceptor"/>
<bean id="druid-stat-pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut" scope="prototype">
<property name="patterns">
<list>
<value>com.demo.service.impl.*</value>
</list>
</property>
</bean>
<aop:config>
<aop:advisor advice-ref="druid-stat-interceptor" pointcut-ref="druid-stat-pointcut"/>
</aop:config>
</beans>
2、UseDataSource.java:与单类型数据源相同
3、DataSourceType.java:与单类型数据源相同
4、DataSourceSwitcher.java:与单类型数据源相同
5、DataSourceAsp.java:与单类型数据源相同
6、使用方法:与单类型数据源相同
7、注意
7.1、两个数据源的mapperLocations扫描的xml路径和basePackage扫描的mapper路径不要有交集,否则会造成失败