原始出处:http://linhongyu.blog.51cto.com/6373370/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{@Overrideprotected 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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsdhttp://www.springframework.org/schema/txhttp://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分布式事务处理。你怎么看?

转载于:https://www.cnblogs.com/davidwang456/p/4318303.html

Spring(AbstractRoutingDataSource)实现动态数据源切换--转载相关推荐

  1. Spring(AbstractRoutingDataSource)实现动态数据源切换

    参考:http://linhongyu.blog.51cto.com/6373370/1615895 一.前言 近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目A中 ...

  2. spring boot使用AbstractRoutingDataSource实现动态数据源切换

    一.AbstractRoutingDataSource Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前, ...

  3. Spring AOP之四:利用AOP实现动态数据源切换

    2019独角兽企业重金招聘Python工程师标准>>> 简介和依赖 项目的前提是安装了MySQL数据库,并且建立了2个数据库一个是master,一个是slave,并且这2个数据库都有 ...

  4. 动态数据源切换--AbstractRoutingDataSource

    转载自http://blog.csdn.net/x2145637/article/details/52461198 在Spring 2.0.1中引入了AbstractRoutingDataSource ...

  5. spring 动态数据源切换实例

    我们很多项目中业务都需要涉及到多个数据源,最简单的做法就是直接在java代码里面lookup需要的数据源,但是这样的做法很明显耦合度太高了, 而且当逻辑流程不够严谨的时候就会出现各种大家不愿意看到的问 ...

  6. Spring Boot + Mybatis 配合 AOP 和注解实现动态数据源切换配置

    Spring Boot + Mybatis 配合 AOP 和注解实现动态数据源切换配置 前言: 1. 数据库准备: 2. 环境准备: 3.代码部分 4. 测试: 5.等等 6.配合注解实现 7 .测试 ...

  7. spring environment_程序员:Spring项目中简单几步实现多个动态数据源切换

    每一个请求与其他的用户是面对不同的数据库,这就需要用到动态数据源切换,来满足不同数据库.不同数据表(不同数据源)的灵活调用. 动态数据源切换 满足mysql.oracle等主流数据库进行动态数据源切换 ...

  8. springboot动态数据源切换(多数据源配置)

    动态数据源切换即多数据源切换,由于业务的需要或者历史的遗留等原因,一个项目中配置了多个数据库,用于查询不同类型的数据,因此我们就需要经常在各个库中切换数据源,接下来我们将进行具体的说明: 项目结构如下 ...

  9. java多个数据库数据进行访问_通过Spring Boot配置动态数据源访问多个数据库的实现代码...

    之前写过一篇博客<Spring+Mybatis+Mysql搭建分布式数据库访问框架>描述如何通过Spring+Mybatis配置动态数据源访问多个数据库.但是之前的方案有一些限制(原博客中 ...

最新文章

  1. Spring 3.1 事务配置
  2. ThinkPHP下隐藏index.php以及URL伪静态
  3. Vue.js 模板语法
  4. 【推荐】让你事半功倍的交互体验自查清单
  5. [scala-spark]2. Scala运行原理与源码查看
  6. Ayoub's function CodeForces - 1301C(组合数学)
  7. cox风险回归模型参数估计_信用风险管理:分类模型和超参数调整
  8. powerdesigner中如何在自动生成建表SQL时添加模式名schema
  9. HTML5中的Web Notification桌面通知(右下角提示)
  10. WEB前端优化必备压缩工具YUI-compressor详解
  11. 文本文档类型怎么改php,如何更改文件类型(3种方法)
  12. Java web项目中获取WebRoot目录下的文件
  13. linux 查看服务器防火墙状态,linux防火墙查看状态firewall、iptable
  14. 计算机科学 贺楠,计算机学部-黑龙江东方学院.DOC
  15. 图书馆管理系统需求规格说明书
  16. PHP执行底层机制-zend详解
  17. 对称、群论与魔术(二)——用群来描述对称性
  18. Oracle11g导入DMP文件并导出为CSV文件过程全纪录
  19. 人月神话 中文版 pdf
  20. RecyclerView StaggeredGridLayoutManager瀑布流实现中遇到的问题

热门文章

  1. 单片机代码怎么读懂_单片机程序员的面试经验
  2. 电阻应用电路之指示灯电路的设计
  3. java 8 string_String.join() --Java8中String类新增方法
  4. Oracle数据库对象----视图
  5. linux集群巡检,Linux巡检
  6. 8种相似度度量方式的原理及实现【笔记自用】【1】
  7. pyspark 计算 皮尔逊相关系数
  8. 嗅探软件和网络测试,决战无线网络 七款浏览器载入速度测试
  9. 将搜索二叉树转换成双向链表
  10. Tensorflow实现MNIST数据自编码(1)