博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring(AbstractRoutingDataSource)实现动态数据源切换
阅读量:6225 次
发布时间:2019-06-21

本文共 7487 字,大约阅读时间需要 24 分钟。

转自: 

 

一、前言

    近期一项目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的源码中:

1 public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

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

1 public Connection getConnection() throws SQLException {  2     return determineTargetDataSource().getConnection();  3 }  4   5 public Connection getConnection(String username, String password) throws SQLException {  6      return determineTargetDataSource().getConnection(username, password);  7 }

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

 
1 /**  2      * Retrieve the current target DataSource. Determines the  3      * {
@link #determineCurrentLookupKey() current lookup key}, performs 4 * a lookup in the {
@link #setTargetDataSources targetDataSources} map, 5 * falls back to the specified 6 * {
@link #setDefaultTargetDataSource default target DataSource} if necessary. 7 * @see #determineCurrentLookupKey() 8 */ 9 protected DataSource determineTargetDataSource() { 10 Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); 11 Object lookupKey = determineCurrentLookupKey(); 12 DataSource dataSource = this.resolvedDataSources.get(lookupKey); 13 if (dataSource == null && (this.lenientFallback || lookupKey == null)) { 14 dataSource = this.resolvedDefaultDataSource; 15 } 16 if (dataSource == null) { 17 throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); 18 } 19 return dataSource; 20 }

    上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。

    看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:

1 package com.datasource.test.util.database; 2  3 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 4  5 /** 6  * 获取数据源(依赖于spring) 7  * @author linhy 8  */ 9 public class DynamicDataSource extends AbstractRoutingDataSource{10     @Override11     protected Object determineCurrentLookupKey() {12         return DataSourceHolder.getDataSource();13     }14 }

    DataSourceHolder这个类则是我们自己封装的对数据源进行操作的类:

1 package com.datasource.test.util.database; 2  3 /** 4  * 数据源操作 5  * @author linhy 6  */ 7 public class DataSourceHolder { 8     //线程本地环境 9     private static final ThreadLocal
dataSources = new ThreadLocal
();10 //设置数据源11 public static void setDataSource(String customerType) {12 dataSources.set(customerType);13 }14 //获取数据源15 public static String getDataSource() {16 return (String) dataSources.get();17 }18 //清除数据源19 public static void clearDataSource() {20 dataSources.remove();21 }22 23 }

    2、有人就要问,那你setDataSource这方法是要在什么时候执行呢?当然是在你需要切换数据源的时候执行啦。手动在代码中调用写死吗?这是多蠢的方法,当然要让它动态咯。所以我们可以应用spring aop来设置,把配置的数据源类型都设置成为注解标签,在service层中需要切换数据源的方法上,写上注解标签,调用相应方法切换数据源咯(就跟你设置事务一样):

1 @DataSource(name=DataSource.slave1)2 public List getProducts(){
 

    当然,注解标签的用法可能很少人用到,但它可是个好东西哦,大大的帮助了我们开发:

1 package com.datasource.test.util.database; 2  3 import java.lang.annotation.*; 4  5 @Target({ElementType.METHOD, ElementType.TYPE}) 6 @Retention(RetentionPolicy.RUNTIME) 7 @Documented 8 public @interface DataSource { 9     String name() default DataSource.master;10 11     public static String master = "dataSource1";12 13     public static String slave1 = "dataSource2";14 15     public static String slave2 = "dataSource3";16 17 }

三、配置文件

    为了精简篇幅,省略了无关本内容主题的配置。

    项目中单独分离出application-database.xml,关于数据源配置的文件。

1 
2
3
13 14
15
16
17
18
19
20
21
22 23
24
25
26
27
28
29
30
31 32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 53
54
55
56
57
com.datasource.test.util.database.ExtendedMySQLDialect
58
${SHOWSQL}
59
${SHOWSQL}
60
org.hibernate.hql.classic.ClassicQueryTranslatorFactory
61
org.hibernate.connection.C3P0ConnectionProvider
62
30
63
5
64
120
65
120
66
2
67
true
68
100
69
70
71
72 73
74
75
76 77
78 79
80
81
82 83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101 102
103
104
105
106
107
108 109

四、疑问

    多数据源切换是成功了,但牵涉到事务呢?单数据源事务是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) {|@|}

你可能感兴趣的文章
grep -- 一个正则表达式的执行者
查看>>
Weblogic 布署
查看>>
VC运行库版本不同导致链接.LIB静态库时发生重复定义问题的一个案例分析和总结...
查看>>
IOS多线程-1
查看>>
javascript:直播吧
查看>>
【虚拟化-基础篇】安装部署ESXi
查看>>
基于SDN,NFV的服务感知网络架构下篇
查看>>
浅谈JSON和JSONP区别及jQuery的ajax jsonp的使用
查看>>
VMware Agent 安装完成后,RDP无法连接
查看>>
apache+多版本php
查看>>
关于测试人员的职业发展
查看>>
C++与C#数据类型比较总结
查看>>
Try .NET & Github Gist
查看>>
Windows页目录自映射方案
查看>>
java 线程之executors线程池
查看>>
about lesscss~
查看>>
ubuntu 系统的一些错误解决
查看>>
MongoDB学习记录
查看>>
字符串
查看>>
如何在Ubuntu/CentOS上安装Linux内核4.0
查看>>