Spring笔记2-- 使用AbstractRoutingDataSource类进行多数据源配置

多数据源在项目中很常见,举几个需求,如页面展示的数据需要从不同的数据库中去查询,修改等,例如数据库的读写分离等,配置多数据源要考虑到如下的情况

  • 单个数据源的情形


    图1.jpg
  • 多个数据源的情形
图2.jpg

每增加一个数据源就要去配置一个sessionFactory,这样比较麻烦,于是springAbstractRoutingDataSource类由此而生

它的实现原理是扩展Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能在运行时, 根据某种key值来动态切换到真正的DataSource上。)
从AbstractRoutingDataSource的源码中:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:

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

获取连接的方法中,重点是determineTargetDataSource()方法,看源码:

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;  
    }

上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。
看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:


接下来,将展示一个多数据源配置demo
1.在xml中配置多个数据源

  • DataSource 1
<bean id="erpDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!-- 基本属性 url、user、password -->
       <!-- <property name="connectionProperties" value="${db_erp.driver}"/>-->
        <property name="url" value="${db_erp.url}"/>
        <property name="username" value="${db_erp.username}"/>
        <property name="password" value="${db_erp.password}"/>

        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="1"/>
        <property name="minIdle" value="1"/>
        <property name="maxActive" value="20"/>

        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000"/>

        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>

        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>

        <property name="validationQuery" value="SELECT 'x'"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>

        <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true"/>
        <property name="maxPoolPreparedStatementPerConnectionSize"
                  value="20"/>

        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="stat"/>
    </bean>
  • DataSource 2
    <bean id="ncDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!-- 基本属性 url、user、password -->
      <!--  <property name="connectionProperties" value="${db_nc.driver}"/>-->
        <property name="url" value="${db_nc.url}"/>
        <property name="username" value="${db_nc.username}"/>
        <property name="password" value="${db_nc.password}"/>

        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="1"/>
        <property name="minIdle" value="1"/>
        <property name="maxActive" value="20"/>

        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000"/>

        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>

        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>

        <property name="validationQuery" value="SELECT 'x'"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>

        <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true"/>
        <property name="maxPoolPreparedStatementPerConnectionSize"
                  value="20"/>

        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="stat"/>
    </bean>
  • DataSource3
    <bean name="oracleDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
          destroy-method="close">
        <!--<property name="connectionProperties" value="${db_oracle.driver}"/>-->
        <!-- 实时库121DG -->
        <property name="url" value="${db_oracle.url}"/>
        <property name="username" value="${db_oracle.username}"/>
        <property name="password" value="${db_oracle.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="1"/>
        <property name="minIdle" value="1"/>
        <property name="maxActive" value="20"/>
        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000"/>
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <property name="validationQuery" value="SELECT 'x' FROM DUAL"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true"/>
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="stat"/>
    </bean>


2、写一个ThreadLocalDynamicDataSource类继承AbstractRoutingDataSource,并实现determineCurrentLookupKey方法

package com.hxqc.basic.dependency.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceTypeManager.get();
    }
}

3、编写DataSourceTypeManager执行类,并利用ThreadLocal以空间换时间的方式解决线程安全问题

package com.hxqc.basic.dependency.datasource;

public class DataSourceTypeManager {
    private static final ThreadLocal<DataSources> dataSourceTypes = new ThreadLocal<DataSources>() {
        @Override
        protected DataSources initialValue() {
            return DataSources.DATASOURCE_DEFAULT;
        }
    };

    public static DataSources get() {
        return dataSourceTypes.get();
    }

    public static void set(DataSources dataSourceType) {
        dataSourceTypes.set(dataSourceType);
    }

    public static void reset() {
        dataSourceTypes.set(DataSources.DATASOURCE_DEFAULT);
    }

    public static void clearDataSources () {
        dataSourceTypes.remove();
    }
}

上面代码利用initialValue方法,调用初始化时指定的DataSources 类型,
这里DataSources 为枚举类型,里面定义三种数据源的名称,代码如下:

package com.hxqc.basic.dependency.datasource;

public enum DataSources {
     DATASOURCE,DATASOURCE_DEFAULT,DATASOURCE_ORACLE
}

4、数据源配置

<bean id="multipleDataSource"
          class="com.hxqc.basic.dependency.datasource.ThreadLocalRountingDataSource">
        <property name="defaultTargetDataSource" ref="ncDataSource"/>
        <property name="targetDataSources">
            <map key-type="com.hxqc.basic.dependency.datasource.DataSources">
                <entry key="DATASOURCE" value-ref="erpDataSource"/>
                <entry key="DATASOURCE_DEFAULT" value-ref="ncDataSource"/>
                <entry key="DATASOURCE_ORACLE" value-ref="oracleDataSource"/>
            </map>
        </property>
    </bean>

5.在DAOImpl中切换数据源 或者配置aop切面根据不同的包下面的数据,采用不同的数据源
1,在Dao层直接切换数据源,如切换到默认数据源

DataSourceTypeManager.set(DataSources.DATASOURCE_DEFAULT)

2.根据aop进行动态切换
aop配置

<bean id="dataSourceInterceptor" class="com.hxqc.basic.dependency.interceptor.DataSourceInterceptor"/>
    <aop:config>
        <aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
            <aop:pointcut id="datasource" expression="execution(* com.hxqc.data.gather.core.erp.*.mapper..*.*(..))"/>
            <aop:pointcut id="datasource_oracle" expression="execution(* com.hxqc.data.gather.core.oracle.*.mapper..*.*(..))"/>
            <aop:pointcut id="datasource_default" expression="execution(* com.hxqc.data.gather.core.nc.*.mapper..*.*(..))"/>
            <aop:before method="setDataSource" pointcut-ref="datasource"/>
            <aop:before method="setDataSourceDefault" pointcut-ref="datasource_default"/>
            <aop:before method="setDataSourceOracle" pointcut-ref="datasource_oracle"/>
        </aop:aspect>
    </aop:config>

解释,这里定义三个切面,分别对应三个数据源,如在oracle包下的,动态切换到oracle数据源
然后配置切面类

package com.hxqc.basic.dependency.interceptor;

import com.hxqc.basic.dependency.datasource.DataSourceTypeManager;
import com.hxqc.basic.dependency.datasource.DataSources;
import com.hxqc.basic.dependency.log.LogU;
import org.aspectj.lang.JoinPoint;

/**
 * 
 * 
 * @author maple
 *
 */
public class DataSourceInterceptor {

    public void setDataSourceDefault(JoinPoint jp){
        DataSourceTypeManager.set(DataSources.DATASOURCE_DEFAULT);
        LogU.i("切换数据源","切换到NC数据源");
        System.out.println(jp.getTarget().getClass().getSimpleName());
    }
    
    public void setDataSource(JoinPoint jp){
        DataSourceTypeManager.set(DataSources.DATASOURCE);
        LogU.i("切换数据源","切换到ERP数据源");
    }

    public void setDataSourceOracle(JoinPoint jp){
        DataSourceTypeManager.set(DataSources.DATASOURCE_ORACLE);
        LogU.i("切换数据源","切换到Oracle数据源");
    }
}

问题:多数据源切换是成功了,但牵涉到事务呢?单数据源事务是ok的,但如果多数据源需要同时使用一个事务呢?这个问题有点头大,网络上有人提出用atomikos开源项目实现JTA分布式事务处理。你怎么看?


本文源码:http://git.oschina.net/youjie1/may-dynamic-datasource
本章完(参考:http://www.cnblogs.com/davidwang456/p/4318303.html)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,724评论 6 342
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,397评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 春在哪里 我问天天降细雨 清风徐徐 细雨滋润 生会万物复苏 春在哪里 我问地大地多情 暗流涌动 绿意渐浓 鹧鸪唱不...
    六六幺幺九阅读 296评论 2 1
  • 【23】最后依然没有划到重点 他们至今已长埋地下分别有35个和15个年头。 我再也没有探望过他们的坟墓,因为他们不...
    贝龙阅读 954评论 64 7