- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Spring(AbstractRoutingDataSource)实现动态数据源切换示例由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
1、前言 。
近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目A中切换数据源,直接把数据写入项目B的数据库中。这种需求,在数据同步与定时任务中经常需要.
那么问题来了,该如何解决多数据源问题呢?不光是要配置多个数据源,还得能灵活动态的切换数据源。以spring+hibernate框架项目为例:
单个数据源绑定给sessionFactory,再在Dao层操作,若多个数据源的话,那不是就成了下图:
可见,sessionFactory都写死在了Dao层,若我再添加个数据源的话,则又得添加一个sessionFactory。所以比较好的做法应该是下图:
接下来就为大家讲解下如何用spring来整合这些数据源,同样以spring+hibernate配置为例.
2、实现原理 。
1、扩展Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。) 。
从AbstractRoutingDataSource的源码中:
。
。
我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:
1
2
3
4
5
6
7
|
public
Connection getConnection()
throws
SQLException {
return
determineTargetDataSource().getConnection();
}
public
Connection getConnection(String username, String password)
throws
SQLException {
return
determineTargetDataSource().getConnection(username, password);
}
|
获取连接的方法中,重点是determineTargetDataSource()方法,看源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/**
* 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()方法,来实现数据源的切换:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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这个类则是我们自己封装的对数据源进行操作的类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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层中需要切换数据源的方法上,写上注解标签,调用相应方法切换数据源咯(就跟你设置事务一样):
1
2
|
@DataSource
(name=DataSource.slave1)
public
List getProducts(){
|
当然,注解标签的用法可能很少人用到,但它可是个好东西哦,大大的帮助了我们开发:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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"
;
}
|
3、配置文件 。
为了精简篇幅,省略了无关本内容主题的配置.
项目中单独分离出application-database.xml,关于数据源配置的文件.
1
2
3
4
5
6
7
8
9
10
11
12
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
58
59
60
61
62
63
64
65
66
67
68
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
|
<?
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
>
|
4、疑问 。
多数据源切换是成功了,但牵涉到事务呢?单数据源事务是ok的,但如果多数据源需要同时使用一个事务呢?这个问题有点头大,网络上有人提出用atomikos开源项目实现JTA分布式事务处理。你怎么看?
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我.
原文链接:http://www.cnblogs.com/davidwang456/p/4318303.html 。
最后此篇关于Spring(AbstractRoutingDataSource)实现动态数据源切换示例的文章就讲到这里了,如果你想了解更多关于Spring(AbstractRoutingDataSource)实现动态数据源切换示例的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我用 AbstractRoutingDataSource动态更改数据源和 ThreadLocal设置 currentLookupKey。当我每个 http 请求只使用一个数据源时,它工作得很好。我用
我目前有一个有两个数据源的程序。每个数据源都与一个事务管理器绑定(bind)。 如果我有一个访问两个数据源的函数并且发生错误,如果一个数据源回滚,第二个数据源也会回滚吗? 谢谢! 最佳答案
我现在在数据库中有 2 个表: 用户 用户数据库 在用户中我存储登录名、密码、角色 在 user_database 中,我存储数据库驱动程序、url、密码和用户。图数据库 我希望用户登录到我的页面,下
Spring AbstractRoutingDatasource 动态数据源 数据源上下文 AbstractRoutingDatasource 需要信息决定路由到那个数据源,该信息我们称为上下文
1、前言 近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目A中切换数据源,直接把数据写入项目B的数据库中。这种需求,在数据同步与定时任务中经常需要。 那么问
使用 AbstractRoutingDataSource 时如何在数据源之间共享事务切换 Activity 数据源? 到目前为止,在没有事务的情况下,查询会在两个数据库上正确执行,但是当我开始事务时,
我一直在使用 AbstractRoutingDataSource取得了巨大的成功,但遇到了一个我无法解决的问题:当我使用 @Async 启动异步方法时,它丢失了本地线程的上下文,我无法弄清楚设置数据库
我有一个 Spring+Hibernate/Flex 应用程序,需要在数据库模式之间动态切换。为了实现这一点,我在this之后实现了一个AbstractRoutingDataSource。文章。不幸的
我正在使用 Spring、Spring Data JPA、Spring Security、Primefaces 的项目...... 我正在关注this tutorial关于 Spring 的动态数据源
我关注了this tutorial从 2007 年开始学习如何使用 Spring 设置多个可选择的 DataSource 实例: 我的问题是,由于这篇文章已经很老了,现在是否有更好的方法来代替使用 T
根据this article ,您可以使用 Spring Framework 中的 AbstractRoutingDataSource 来动态更改应用程序使用的数据源。 但是,使用的数据源是由配置定义
我使用 JPA 注释(Hibernate 实现)来初始化我的数据库架构。我关注文章 DYNAMIC DATASOURCE ROUTING实现动态数据源路由类。 但是,我有两个数据库(映射了 2 个数据
大家好,我已按照本指南使用 Spring-Boot 实现 Multi-Tenancy 应用程序: https://www.baeldung.com/spring-abstract-routing-da
我是 Spring boot 的新手,我正在开发一个需要能够连接到多个可用数据库之一的新应用程序。根据用户的凭据,我将确定要连接到哪个数据库,因此我需要能够在运行时动态更改连接。我找到了一个旧的 Sp
我正在使用 SpringBoot 以及 Hibernate 作为持久性提供程序。对于我的应用程序,我需要在 2 个数据库之间进行动态选择。 (For simplicity sake, domain
我在 Spring 中使用 Hibernate,相关配置: 最佳答案 想一想... 一些代码想要从DataSource 获取Connection。可能是为了启动
我在 Spring 中使用 Hibernate,相关配置: 最佳答案 想一想... 一些代码想要从DataSource 获取Connection。可能是为了启动
我们的应用程序使用 Struts2 Internalization 和 Spring AbstractRoutingDataSource 进行动态数据库更改。更改语言,需要选择对应的语言数据库。 因此
我正在使用 Spring 实现一个 Multi-Tenancy 系统,其中每个租户都有自己的数据库。我的一切都已启动并运行。 我扩展了“AbstractRoutingDataSource”并覆盖了“d
我是一名优秀的程序员,十分优秀!