利用Spring的AbstractRoutingDataSource做多數據源動態切換

多數據源系列

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,它支持更多實用的強大功能,下一篇博客將詳細介紹這個開源框架

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章