利用Spring的AbstractRoutingDataSource做多数据源动态切换

多数据源系列

SpringBoot2.0 Mybatis Druid搭建一个最简单的多数据源

2、利用Spring的AbstractRoutingDataSource做多数据源动态切换

使用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,它支持更多实用的强大功能,下一篇博客将详细介绍这个开源框架

收藏 (0)
评论列表
正在载入评论列表...
我是有底线的
为您推荐
    暂时没有数据