Spring(AbstractRoutingDataSource)实现动态数据源切换
转自: http://blog.51cto.com/linhongyu/1615895
一、前言
近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目A中切换数据源,直接把数据写入项目B的数据库中。这种需求,在数据同步与定时任务中经常需要。
那么问题来了,该如何解决多数据源问题呢?不光是要配置多个数据源,还得能灵活动态的切换数据源。以spring+hibernate框架项目为例(引用:http://blog.csdn.net/wangpeng047/article/details/8866239博客的图片):

单个数据源绑定给sessionFactory,再在Dao层操作,若多个数据源的话,那不是就成了下图:

可见,sessionFactory都写死在了Dao层,若我再添加个数据源的话,则又得添加一个sessionFactory。所以比较好的做法应该是下图:

接下来就为大家讲解下如何用spring来整合这些数据源,同样以spring+hibernate配置为例。
二、实现原理
1、扩展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()方法,看源码:
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
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()方法,来实现数据源的切换:
package com.datasource.test.util.database; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /**
* 获取数据源(依赖于spring)
* @author linhy
*/
public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getDataSource();
}
}
DataSourceHolder这个类则是我们自己封装的对数据源进行操作的类:
package com.datasource.test.util.database; /**
* 数据源操作
* @author linhy
*/
public class DataSourceHolder {
//线程本地环境
private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
//设置数据源
public static void setDataSource(String customerType) {
dataSources.set(customerType);
}
//获取数据源
public static String getDataSource() {
return (String) dataSources.get();
}
//清除数据源
public static void clearDataSource() {
dataSources.remove();
} }
2、有人就要问,那你setDataSource这方法是要在什么时候执行呢?当然是在你需要切换数据源的时候执行啦。手动在代码中调用写死吗?这是多蠢的方法,当然要让它动态咯。所以我们可以应用spring aop来设置,把配置的数据源类型都设置成为注解标签,在service层中需要切换数据源的方法上,写上注解标签,调用相应方法切换数据源咯(就跟你设置事务一样):
@DataSource(name=DataSource.slave1)
public List getProducts(){
当然,注解标签的用法可能很少人用到,但它可是个好东西哦,大大的帮助了我们开发:
package com.datasource.test.util.database;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String name() default DataSource.master;
public static String master = "dataSource1";
public static String slave1 = "dataSource2";
public static String slave2 = "dataSource3";
}
三、配置文件
为了精简篇幅,省略了无关本内容主题的配置。
项目中单独分离出application-database.xml,关于数据源配置的文件。
<?xml version="1.0" encoding="UTF-8"?>
<!-- Spring 数据库相关配置 放在这里 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <bean id = "dataSource1" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
<property name="url" value="${db1.url}"/>
<property name = "user" value = "${db1.user}"/>
<property name = "password" value = "${db1.pwd}"/>
<property name="autoReconnect" value="true"/>
<property name="useUnicode" value="true"/>
<property name="characterEncoding" value="UTF-8"/>
</bean> <bean id = "dataSource2" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
<property name="url" value="${db2.url}"/>
<property name = "user" value = "${db2.user}"/>
<property name = "password" value = "${db2.pwd}"/>
<property name="autoReconnect" value="true"/>
<property name="useUnicode" value="true"/>
<property name="characterEncoding" value="UTF-8"/>
</bean> <bean id = "dataSource3" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
<property name="url" value="${db3.url}"/>
<property name = "user" value = "${db3.user}"/>
<property name = "password" value = "${db3.pwd}"/>
<property name="autoReconnect" value="true"/>
<property name="useUnicode" value="true"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
<!-- 配置多数据源映射关系 -->
<bean id="dataSource" class="com.datasource.test.util.database.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="dataSource1" value-ref="dataSource1"></entry>
<entry key="dataSource2" value-ref="dataSource2"></entry>
<entry key="dataSource3" value-ref="dataSource3"></entry>
</map>
</property>
<!-- 默认目标数据源为你主库数据源 -->
<property name="defaultTargetDataSource" ref="dataSource1"/>
</bean> <bean id="sessionFactoryHibernate" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">com.datasource.test.util.database.ExtendedMySQLDialect</prop>
<prop key="hibernate.show_sql">${SHOWSQL}</prop>
<prop key="hibernate.format_sql">${SHOWSQL}</prop>
<prop key="query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</prop>
<prop key="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</prop>
<prop key="hibernate.c3p0.max_size">30</prop>
<prop key="hibernate.c3p0.min_size">5</prop>
<prop key="hibernate.c3p0.timeout">120</prop>
<prop key="hibernate.c3p0.idle_test_period">120</prop>
<prop key="hibernate.c3p0.acquire_increment">2</prop>
<prop key="hibernate.c3p0.validate">true</prop>
<prop key="hibernate.c3p0.max_statements">100</prop>
</props>
</property>
</bean> <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactoryHibernate"/>
</bean> <bean id="dataSourceExchange" class="com.datasource.test.util.database.DataSourceExchange"/> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactoryHibernate"/>
</bean> <tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="insert*" propagation="NESTED" rollback-for="Exception"/>
<tx:method name="add*" propagation="NESTED" rollback-for="Exception"/>
<tx:method name="update*" propagation="NESTED" rollback-for="Exception"/>
<tx:method name="modify*" propagation="NESTED" rollback-for="Exception"/>
<tx:method name="edit*" propagation="NESTED" rollback-for="Exception"/>
<tx:method name="del*" propagation="NESTED" rollback-for="Exception"/>
<tx:method name="save*" propagation="NESTED" rollback-for="Exception"/>
<tx:method name="send*" propagation="NESTED" rollback-for="Exception"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="search*" read-only="true"/>
<tx:method name="select*" read-only="true"/>
<tx:method name="count*" read-only="true"/>
</tx:attributes>
</tx:advice> <aop:config>
<aop:pointcut id="service" expression="execution(* com.datasource..*.service.*.*(..))"/>
<!-- 关键配置,切换数据源一定要比持久层代码更先执行(事务也算持久层代码) -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="service" order="2"/>
<aop:advisor advice-ref="dataSourceExchange" pointcut-ref="service" order="1"/>
</aop:config> </beans>
四、疑问
多数据源切换是成功了,但牵涉到事务呢?单数据源事务是ok的,但如果多数据源需要同时使用一个事务呢?这个问题有点头大,网络上有人提出用atomikos开源项目实现JTA分布式事务处理。你怎么看?
五、dataSourceExchange 是怎样写的?
dataSourceExchange对应的类可以实现接口org.aopalliance.intercept.MethodInterceptor的invoke方法|@|@Override|@|public Object invoke(MethodInvocation invocation) throws Throwable {|@| DataSource dataSource = invocation.getMethod().getAnnotation(DataSource.class); |@| DataSourceHolder.setDataSource(dataSource.name());|@| try {|@| invocation.proceed();|@| } catch (Exception ex) { |@| }|@| return null;|@|}|@|pointcut的expression也可以写成@annotation(com.xxx.DataSource)|@|使用的时候,只需要在方法上加上注解@DataSource就行了|@|@DataSource(name = DataSource.slave1)|@|public void insert(String name) {|@|}
Spring(AbstractRoutingDataSource)实现动态数据源切换的更多相关文章
- Spring(AbstractRoutingDataSource)实现动态数据源切换--转载
原始出处:http://linhongyu.blog.51cto.com/6373370/1615895 一.前言 近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目 ...
- spring AbstractRoutingDataSource实现动态数据源切换
使用Spring 提供的 AbstractRoutingDataSource 实现 创建 AbstractRoutingDataSource 实现类,负责保存所有数据源与切换数据源策略:public ...
- SpringMVC 利用AbstractRoutingDataSource实现动态数据源切换
SpringMVC 利用AbstractRoutingDataSource实现动态数据源切换 本文转载至:http://exceptioneye.iteye.com/blog/1698064 Spri ...
- AbstractRoutingDataSource 实现动态数据源切换原理简单分析
AbstractRoutingDataSource 实现动态数据源切换原理简单分析 写在前面,项目中用到了动态数据源切换,记录一下其运行机制. 代码展示 下面列出一些关键代码,后续分析会用到 数据配置 ...
- dubbo服务+Spring事务+AOP动态数据源切换 出错
1:问题描述,以及分析 项目用了spring数据源动态切换,服务用的是dubbo.在运行一段时间后程序异常,更新操作没有切换到主库上. 这个问题在先调用读操作后再调用写操作会出现. 经日志分析原因: ...
- AbstractRoutingDataSource实现动态数据源切换 专题
需求:系统中要实现切换数据库(业务数据库和his数据库) 网上很多资料上有提到AbstractRoutingDataSource,大致是这么说的 在Spring 2.0.1中引入了AbstractRo ...
- 利用AbstractRoutingDataSource实现动态数据源切换
需求:系统中要实现切换数据库(业务数据库和his数据库) 网上很多资料上有提到AbstractRoutingDataSource,大致是这么说的 在Spring 2.0.1中引入了AbstractRo ...
- Spring 实现动态数据源切换--转载 (AbstractRoutingDataSource)的使用
[参考]Spring(AbstractRoutingDataSource)实现动态数据源切换--转载 [参考] 利用Spring的AbstractRoutingDataSource解决多数据源的问题 ...
- 【Spring】使用Spring的AbstractRoutingDataSource实现多数据源切换
最近因为项目需要在做两个项目间数据同步的需求,具体是项目1的数据通过消息队列同步到项目2中,因为这个更新操作还涉及到更新多个库的数据,所以就需要多数据源切换的操作.下面就讲讲在Spring中如何进行数 ...
随机推荐
- 003.NFS配置实例
一 NFS常见服务管理 1.1 启动NFS [root@imxhy ~]# systemctl start nfs #CentOS7.x系列启动 [root@imxhy ~]# service nfs ...
- 面向对象设计原则 接口分离原则(Interface Segregation Principle)
接口隔离原则 使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口. 从接口隔离原则的定义可以看出,他似乎跟SRP有许多相似之处. 是的其实ISP和SRP都是强调职责的单一性 ...
- java string 替换img标签 正则表达式 任意多个字符
正则表达式 任意多个字符 (.*) 正则表达式中,“.”(点符号)匹配的是除了换行符“\n”以外的所有字符 要匹配包括 '\n' 在内的任何字符,([\s\S]*) 也可以用 “([\d\D]*)” ...
- Codeforces Round #501 (Div. 3) F. Bracket Substring
题目链接 Codeforces Round #501 (Div. 3) F. Bracket Substring 题解 官方题解 http://codeforces.com/blog/entry/60 ...
- Codeforces Round #397 by Kaspersky Lab and Barcelona Bootcamp (Div. 1 + Div. 2 combined) B. Code obfuscation 水题
B. Code obfuscation 题目连接: http://codeforces.com/contest/765/problem/B Description Kostya likes Codef ...
- UVALive 6913 I Want That Cake 博弈dp
I Want That Cake 题目连接: https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemi ...
- C++ Primer笔记3_默认实參_类初探_名字查找与类的作用域
1.默认函数实參 在C++中,能够为參数指定默认值,C语言是不支持默认參数的,Java也不支持! 默认參数的语法与使用: (1)在函数声明或定义时,直接对參数赋值.这就是默认參数: (2)在函数调用时 ...
- .NET轻量级DBHelpers数据访问组件
一.摘要 一说到ADO.NET大家可能立刻想到的就是增.删.改.查(CRUD)操作,然后再接就想到项目中的SQLHelper.没错本课分享课阿笨给大家带来的是来源于github上开源的DAO数据库访问 ...
- MyEclipse使用总结——MyEclipse中配置WebLogic12c服务器
MyEclipse中配置WebLogic12c服务器的步骤如下: [Window]→[Preferences],如下图所示: 找到WebLogic的配置,如下图所示:
- AutoMapper在MVC中的运用小结
配置.单元测试.AOP注入 Decimal转换成String类型 源数组转换成目标数组 源中的集合(数组)属性转换成目标中的集合(数组)属性 子类父类间的映射 源字典集合转换成目标字典集合 枚举映射 ...