多数据源系列
1、spring boot2.0 +Mybatis + druid搭建一个最简单的多数据源
2、利用Spring的AbstractRoutingDataSource做多数据源动态切换
3、使用dynamic-datasource-spring-boot-starter做多数据源及源码分析
简介
搭建多数据源有多种方式,上一篇博客介绍了一种最基本的方式搭建多数据源,就是把每个数据源配置了一个DataSource的Bean,这种方式显得比较繁琐,mapper也要放在不同的地方,这里介绍一种动态切换数据源的方式
实操
这里用到了AbstractRoutingDataSource类,来简单的看一下这个类
// 它继承AbstractDataSource,AbstractDataSource实现了DataSource接口,这个接口非常关键
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
// 这两个方法实现了DataSource接口
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
}
// 通过datasouce的名称来查找dataSource
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
主要就是那个getConnection方法,每次执行sql会触发这个getConnection方法,在这里我们就可以给它返回不同的Connection对象,来达到动态切换数据源的目的。这个方法在下一篇博客提到的dynamic-datasource-spring-boot-starter也是这样做的
接下来就是使用方法
jdbc.db1.driver=com.mysql.jdbc.Driver
jdbc.db1.url=jdbc:mysql://127.0.0.1:3306/test1?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&useSSL=false
jdbc.db1.username=root
jdbc.db1.password=root
jdbc.db2.driver=com.mysql.jdbc.Driver
jdbc.db2.url=jdbc:mysql://127.0.0.1:3307/test2?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&useSSL=false
jdbc.db2.username=root
jdbc.db2.password=root
jdbc.db3.driver=com.ibm.db2.jcc.DB2Driver
jdbc.db3.url=jdbc:db2://127.0.0.1:56000/SAMPLE
jdbc.db3.username=db2
jdbc.db3.password=123456
这是一个数据库地址配置文件,可以发现我们使用了三个数据源
<!-- 配置dbcp数据源 -->
<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.db1.driver}"/>
<property name="url" value="${jdbc.db1.url}"/>
<property name="username" value="${jdbc.db1.username}"/>
<property name="password" value="${jdbc.db1.password}"/>
</bean>
<bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.db2.driver}"/>
<property name="url" value="${jdbc.db2.url}"/>
<property name="username" value="${jdbc.db2.username}"/>
<property name="password" value="${jdbc.db2.password}"/>
</bean>
<bean id="dataSource3" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.db3.driver}"/>
<property name="url" value="${jdbc.db3.url}"/>
<property name="username" value="${jdbc.db3.username}"/>
<property name="password" value="${jdbc.db3.password}"/>
</bean>
<bean id="multipleDataSource" class="com.ai.base.tool.datasource.MultipleDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="dataSource1" value-ref="dataSource1"/>
<entry key="dataSource2" value-ref="dataSource2"/>
<entry key="dataSource3" value-ref="dataSource3"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource1"/>
</bean>
然后配置了三个Bean,使用dbcp连接池配置的一个数据源,最后配置了自定义的MultipleDataSource,将这三个数据源配置进去管理
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class MultipleDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
}
使用AbstractRoutingDataSource很简单,只需要实现一个方法,表示将要使用哪个数据源,这里传入的数据源的名称
public class DbContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDbType(String dbType) {
contextHolder.set(dbType);
}
public static void clearDbType() {
contextHolder.remove();
}
public static String getDbType() {
return contextHolder.get();
}
}
还需要使用ThreadLocal维护每个线程连接的数据源是哪个
public void getUserInfo() {
DbContextHolder.setDbType("dataSource1");
List<TA4UserView> ta4UserViews = aimkstService.getTA4UserView();
DbContextHolder.clearDbType();
System.out.println(DbContextHolder.getDbType());
System.out.println(ta4UserViews);
}
这样,当我们要使用其他数据源的时候就设置一下这个Holder就可以了,就动态的切换了数据源了,不设置的话就会进入默认的数据源
还可以使用自定义注解的方式切换数据源,比如在class、method上打上自己的注解,配置AOP可以更加优雅的实现切换数据源操作,而不需要上面的手工set那个holder了
关于多数据源推荐使用dynamic-datasource-spring-boot-starter,它支持更多实用的强大功能,下一篇博客将详细介绍这个开源框架