利用AbstractRoutingDataSource实现动态数据源切换

需求:系统中要实现切换数据库(业务数据库和his数据库)

网上很多资料上有提到AbstractRoutingDataSource,大致是这么说的

在Spring 2.0.1中引入了AbstractRoutingDataSource, 该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。

     Spring动态配置多数据源,即在大型应用中对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效提高系统的水平伸缩性。而这样的方案就会不同于常见的单一数据实例的方案,这就要程序在运行时根据当时的请求及系统状态来动态的决定将数据存储在哪个数据库实例中,以及从哪个数据库提取数据。

Spring对于多数据源,以数据库表为参照,大体上可以分成两大类情况: 
一是,表级上的跨数据库。即,对于不同的数据库却有相同的表(表名和表结构完全相同)。 
二是,非表级上的跨数据库。即,多个数据源不存在相同的表。 
Spring2.x的版本中采用Proxy模式,就是我们在方案中实现一个虚拟的数据源,并且用它来封装数据源选择逻辑,这样就可以有效地将数据源选择逻辑从Client中分离出来。Client提供选择所需的上下文(因为这是Client所知道的),由虚拟的DataSource根据Client提供的上下文来实现数据源的选择。 
具体的实现就是,虚拟的DataSource仅需继承AbstractRoutingDataSource实现determineCurrentLookupKey()在其中封装数据源的选择逻辑

一、原理

首先看下AbstractRoutingDataSource类结构,继承了AbstractDataSource:

public abstract class AbstractRoutingDataSource extends org.springframework.jdbc.datasource.AbstractDataSource implements org.springframework.beans.factory.InitializingBean

既然是AbstractDataSource,当然就是javax.sql.DataSource的子类,于是我们自然地回去看它的getConnection方法:

public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }

    public Connection getConnection(String username, String password) throws SQLException {
        return determineTargetDataSource().getConnection(username, password);
    }

原来关键就在determineTargetDataSource()里:

 1 protected DataSource determineTargetDataSource() {
 2         Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
 3         Object lookupKey = determineCurrentLookupKey();
 4         DataSource dataSource = this.resolvedDataSources.get(lookupKey);
 5         if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
 6             dataSource = this.resolvedDefaultDataSource;
 7         }
 8         if (dataSource == null) {
 9             throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
10         }
11         return dataSource;
12     }

这里用到了我们需要进行实现的抽象方法determineCurrentLookupKey(),该方法返回需要使用的DataSource的key值,然后根据这个key从resolvedDataSources这个map里取出对应的DataSource,如果找不到,则用默认的resolvedDefaultDataSource。

回过头看AbstractDataSource的afterPropertiesSet方法:

 1 public void afterPropertiesSet() {
 2         if (this.targetDataSources == null) {
 3             throw new IllegalArgumentException("Property 'targetDataSources' is required");
 4         }
 5         this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
 6         for (Map.Entry entry : this.targetDataSources.entrySet()) {
 7             Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
 8             DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
 9             this.resolvedDataSources.put(lookupKey, dataSource);
10         }
11         if (this.defaultTargetDataSource != null) {
12             this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
13         }
14     }

配置数据源实例:

    <bean id="onlineDynamicDataSource" class="com.xx.stat.base.dynamic.DynamicDataSource">
       <property name="targetDataSources">   
          <map key-type="java.lang.String">   
             <entry key="xx" value-ref="dataSourceXX"/>   
             <entry key="yy" value-ref="dataSourceYY"/>   
          </map>   
       </property>   
       <property name="defaultTargetDataSource" ref="dataSource"/>  
    </bean>

观察上面的配置文件,发现我们配置的是targetDataSources和defaultTargetDataSource

二、Spring配置多数据源的方式和具体使用过程

1、数据源的名称常量类

 1 public enum DatabaseTypeEnum {
 2     DB_DLHMC("dlhmc", "dlhmc数据库,默认的数据库"),DB_HIS("his", "HIS数据库");
 3     private String value;
 4     private String desc;
 5 
 6     private DatabaseTypeEnum(String value, String description) {
 7         this.value = value;
 8         this.desc = description;
 9     }
10 
11     public String getValue() {
12         return value;
13     }
14 
15     public String getDesc() {
16         return desc;
17     }
18 
19     @Override
20     public String toString() {
21 
22         return "{" + value + ":" + desc + "}";
23     }
24 
25     public static DatabaseTypeEnum from(String value) {
26         for (DatabaseTypeEnum item : values()) {
27             if (item.getValue() == value) {
28                 return item;
29             }
30         }
31         throw new IllegalArgumentException(String.format(
32                 "非法的输入参数 '%s' ! 必须是%s中的其中一个。", value, Arrays.asList(values())
33                         .toString()));
34     }
35 
36 }

2、建立一个获得和设置上下文环境的类,主要负责改变上下文数据源的名称

 1 public class DatabaseContextHolder {
 2     private static ThreadLocal<String> contextHolder=new ThreadLocal<String>();
 3     public static void setDbType(String dbType){
 4         contextHolder.set(dbType);
 5     }
 6     public static String getDbType(){
 7         return contextHolder.get();
 8     }
 9     
10     public static void clearDbType(){
11         contextHolder.remove();
12     }
13 
14 }

3、建立动态数据源类,注意,这个类必须继承AbstractRoutingDataSource,且实现方法 determineCurrentLookupKey,该方法返回一个Object,一般是返回字符串

1 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
2 
3 public class DynamicDataSource extends AbstractRoutingDataSource{
4     @Override
5     protected Object determineCurrentLookupKey() {
6         return DatabaseContextHolder.getDbType();
7     }
8 
9 }

4、编写spring的配置文件配置多个数据源

 1 <!-- 数据源配置 -->
 2     <bean id="defaultDS" class="com.alibaba.druid.pool.DruidDataSource" p:driverClassName="${jdbc.driver}" p:url="${jdbc.jdbcUrl}" p:username="${jdbc.username}" p:password="${jdbc.password}"
 3         p:initialSize="${jdbc.initialSize}" p:maxActive="${jdbc.maxActive}" p:testOnBorrow="${jdbc.testOnBorrow:false}" destroy-method="close">
 4     </bean>
 5     <bean id="hisDS" class="com.alibaba.druid.pool.DruidDataSource" p:driverClassName="${his.jdbc.driver}" p:url="${his.jdbc.jdbcUrl}" p:username="${his.jdbc.username}" p:password="${his.jdbc.password}"
 6         p:initialSize="${his.jdbc.initialSize}" p:maxActive="${his.jdbc.maxActive}" p:testOnBorrow="${his.jdbc.testOnBorrow:false}" destroy-method="close">
 7     </bean>
 8     <bean id="dataSource" class="com.supconit.util.datasource.DynamicDataSource">
 9         <property name="targetDataSources">
10             <map key-type="java.lang.String">
11                 <entry key="dlhmc" value-ref="defaultDS" />
12                 <entry key="his" value-ref="hisDS" />
13                 <!-- entry key="2" value-ref="ds2" / -->
14             </map>
15         </property>
16         <property name="defaultTargetDataSource" ref="defaultDS" />
17     </bean>

5、使用

@Override
    public List<VBedPatientNew> selectNursinglevel() {
        DatabaseContextHolder.setDbType(DatabaseTypeEnum.DB_HIS.getValue());
        List<VBedPatientNew> result=selectList("selectNursinglevel");
        DatabaseContextHolder.clearDbType();
        return result;
    }
原文地址:https://www.cnblogs.com/winkey4986/p/3623371.html