Spring中实现多数据源事务管理
文章转自 https://www.2cto.com/kf/201507/424229.html
前言
由于项目中引入了多个数据源,并且需要对多个数据源进行写操作,那么多数据源的事务管理自然成了不可避免的问题,这也让我对@Transactional注解有了进一步的理解(但实际上也并不是非常深入)
然而这是一个演进的过程,刚开始项目中并没有使用@Transactional指定具体的TransactionManager,所以新增一个数据源后,对原有的事务产生了影响了,这也是偶尔在一次测试报错而结果没有回滚之后才发现的,遂对于@Transactional注解的一些参数项进行了了解。
研究
由于容器中存在两个TransactionManager,那么被@Transactional注解的方法到底使用了哪个TransactionManager来进行事务管理,抑或是同时使用了两个TransactionManager来进行事务管理都是我们需要搞清楚的问题。
首先我们先看看@Transactional注解上有没有提供配置项来指定TransactionManager,果不其然,发现value属性就是用来指定具体TransactionManager的,通过id或者name来指定唯一一个TransactionManager,那么对于只需要一个事务管理的方法,问题就简单多了:
|
1
2
3
4
|
<code class="hljs" cs=""> @Transactional(value = database2TransactionManager) public void test(String a) { // business operation }</code> |
关于不指定TransactionManager时会使用哪一个TransactionManager,有兴趣的童鞋可以参考另一篇文章,讲的比较清晰:https://blog.sina.com.cn/s/blog_8f61307b0100ynfb.html
好了,回到我们研究的问题,那么对于需要写入多个数据源的业务方法该怎么办呢?
进一步研究
看来@Transactional是没有提供这种功能了,那么就自己写了一个吧。我记得Spring中的事务管理分编程式事务和声明式事务。我们平时使用的@Transactional就是声明式事务,它的好处其实也就是灵活度更高、代码的耦合性更低,最终的事务管理实现还是一样的,只不过将具体逻辑都剥离到了切面中。所以我们可以手写一个切面来写一次“编程式事务”,当然在具体应用时,还是声明式的。
Java中一般编程式事务的写法:
|
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
|
<code class="hljs" java="">public class UserServiceImpl implements UserService { @Resource private TransactionManager txManager; @Resource private UserDao userDao; @Resource private AddressDao addressDao; public boolean saveUser(User user) { TransactionDefinition txDefinition = new TransactionDefinition(); TransactionStatus txStatus = txManager.getTransaction(txDefinition); boolean result = false; try { result = userDao.save(user); if(!result){ return false; } result = addressDao.save(user.getId(), user.getAddress()); txManager.commit(txStatus); } catch (Exception e) { result = false; txManager.rollback(txStatus); } return result; }}</code> |
我们借用这个逻辑将事务管理相关提取到切面中,并在进入目标方法之前,让多个TransactionManager都开启事务,并在成功执行后一并提交或失败后一并回滚,具体代码:
|
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
|
<code class="hljs" java="">/** * @author Zhu * @date 2015-7-15 * @version 0.0.1 * @description */public class MultiTransactionalAspect { private Logger logger = LoggerFactory.getLogger(getClass()); public Object around(ProceedingJoinPoint pjp, MultiTransactional multiTransactional) throws Throwable { Stack<datasourcetransactionmanager> dataSourceTransactionManagerStack = new Stack<datasourcetransactionmanager>(); Stack<transactionstatus> transactionStatuStack = new Stack<transactionstatus>(); try { if (!openTransaction(dataSourceTransactionManagerStack, transactionStatuStack, multiTransactional)) { return null; } Object ret = pjp.proceed(); commit(dataSourceTransactionManagerStack, transactionStatuStack); return ret; } catch (Throwable e) { rollback(dataSourceTransactionManagerStack, transactionStatuStack); logger.error(String.format( MultiTransactionalAspect, method:%s-%s occors error:, pjp .getTarget().getClass().getSimpleName(), pjp .getSignature().getName()), e); throw e; } } /** * @author Zhu * @date 2015-7-25下午7:55:46 * @description * @param dataSourceTransactionManagerStack * @param transactionStatuStack * @param values */ private boolean openTransaction( Stack<datasourcetransactionmanager> dataSourceTransactionManagerStack, Stack<transactionstatus> transactionStatuStack, MultiTransactional multiTransactional) { String[] transactionMangerNames = multiTransactional.values(); if (ArrayUtils.isEmpty(multiTransactional.values())) { return false; } for (String beanName : transactionMangerNames) { DataSourceTransactionManager dataSourceTransactionManager = (DataSourceTransactionManager) ContextHolder .getBean(beanName); TransactionStatus transactionStatus = dataSourceTransactionManager .getTransaction(new DefaultTransactionDefinition()); transactionStatuStack.push(transactionStatus); dataSourceTransactionManagerStack .push(dataSourceTransactionManager); } return true; } /** * @author Zhu * @date 2015-7-25下午7:56:39 * @description * @param dataSourceTransactionManagerStack * @param transactionStatuStack */ private void commit( Stack<datasourcetransactionmanager> dataSourceTransactionManagerStack, Stack<transactionstatus> transactionStatuStack) { while (!dataSourceTransactionManagerStack.isEmpty()) { dataSourceTransactionManagerStack.pop().commit( transactionStatuStack.pop()); } } /** * @author Zhu * @date 2015-7-25下午7:56:42 * @description * @param dataSourceTransactionManagerStack * @param transactionStatuStack */ private void rollback( Stack<datasourcetransactionmanager> dataSourceTransactionManagerStack, Stack<transactionstatus> transactionStatuStack) { while (!dataSourceTransactionManagerStack.isEmpty()) { dataSourceTransactionManagerStack.pop().rollback( transactionStatuStack.pop()); } }</transactionstatus></datasourcetransactionmanager></transactionstatus></datasourcetransactionmanager></transactionstatus></datasourcetransactionmanager></transactionstatus></transactionstatus></datasourcetransactionmanager></datasourcetransactionmanager></code> |
整体结构很清晰:
1. 首先根据指定的多个TransactionManager依次开启事务,这个次序不影响,因为其实大家都是平等的。
2. 其次就是调用目标方法执行具体的业务逻辑
3. 若是成功返回则提交每个事务,若中途报错,那么就回滚每个事务
其中为什么要用Stack来保存TransactionManager和TransactionStatus呢?那是因为Spring的事务处理是按照LIFO/stack behavior的方式进行的。如若顺序有误,则会报错:
|
1
2
3
4
5
6
|
<code avrasm="" class="hljs">java.lang.IllegalStateException: Cannot deactivate transaction synchronization - not active at org.springframework.transaction.support.TransactionSynchronizationManager.clearSynchronization(TransactionSynchronizationManager.java:313) at org.springframework.transaction.support.TransactionSynchronizationManager.clear(TransactionSynchronizationManager.java:451) at org.springframework.transaction.support.AbstractPlatformTransactionManager.cleanupAfterCompletion(AbstractPlatformTransactionManager.java:986) at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:782) at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactio</code> |
题外话
刚开始碰到这个问题的时候,先想到的是分布式事务管理,也去看了JTA相关的文章,但是好像比较麻烦,而且都是一些老文章,于是想试试自己实现,最后也实现了。所以想知道JTA TransactionManager究竟有什么用呢?
Spring中实现多数据源事务管理的更多相关文章
- 0045 Spring中使用DataSourceTransactionManager进行事务管理的xml配置
在一个业务的实现过程中,可能需要多条sql完成对数据库的操作,比如账户登录,需要匹配用户名和密码,然后要增加积分,还要记录登录的ip和时间,这可能需要三个sql语句,这三个语句应当是一个整体,任意一个 ...
- Spring中使用DataSourceTransactionManager进行事务管理的xml配置
在一个业务的实现过程中,可能需要多条sql完成对数据库的操作,比如账户登录,需要匹配用户名和密码,然后要增加积分,还要记录登录的ip和时间,这可能需要三个sql语句,这三个语句应当是一个整体,任意一个 ...
- 二、spring集成ibatis进行数据源事务管理拦截器环境配置
1.dataSource-applicationContext.xml文件配置理解:(spring1.2.8+ibatis1.5.3)1.1)配置数据源 DriverManagerDataSource ...
- spring+springmvc+mybatis+oracle+atomikos+jta实现多数据源事务管理
---恢复内容开始--- 在做项目过程中,遇到了需要一个项目中访问两个数据库的情况,发现使用常规的spring管理事务,导致事务不能正常回滚,因此,采用了jta+atomikos的分布式数据源方式 ...
- 事务隔离级别与传播机制,spring+mybatis+atomikos实现分布式事务管理
1.事务的定义:事务是指多个操作单元组成的合集,多个单元操作是整体不可分割的,要么都操作不成功,要么都成功.其必须遵循四个原则(ACID). 原子性(Atomicity):即事务是不可分割的最小工作单 ...
- Spring事务隔离级别与传播机制详解,spring+mybatis+atomikos实现分布式事务管理
原创说明:本文为本人原创作品,绝非他处转载,转账请注明出处 1.事务的定义:事务是指多个操作单元组成的合集,多个单元操作是整体不可分割的,要么都操作不成功,要么都成功.其必须遵循四个原则(ACID). ...
- Spring+JTA+Atomikos+mybatis分布式事务管理
我们平时的工作中用到的Spring事务管理是管理一个数据源的.但是如果对多个数据源进行事务管理该怎么办呢?我们可以用JTA和Atomikos结合Spring来实现一个分布式事务管理的功能.了解JTA可 ...
- Spring 简单而强大的事务管理功能
开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...
- springbootdruidmybatismysql多数据源事务管理
springboot+druid+mybatis+mysql+多数据源事务管理 分布式事务在java中的解决方案就是JTA(即Java Transaction API):springboot官方提供了 ...
随机推荐
- [PHP] 算法-找出两个链表的第一个公共结点的PHP实现
输入两个链表,找出它们的第一个公共结点 1.两个单链表,有公共结点,那么必然,尾部公用 2.找出链表1的长度,找出链表2的长度,长的链表减去短的链表得出一个n值 3.长的链表先走n步,两个链表再同时移 ...
- [PHP]算法-最大子数组问题思路
最大子数组问题,股票价格示例: 1.在最高价格开始向左寻找之前的最低价格 2.在最低价格开始向右寻找之后的最高价格 3.暴力求解法,尝试每队可能的买进和卖出组合,保证卖出在买进之后 key buy s ...
- 【开发工具之Spring Tool Suite】6、用Spring Tool Suite简化你的开发
如果你是一个喜欢用spring的人,你可能会在欣赏spring的强大功能外,对其各样的配置比较郁闷,尤其是相差较大的版本在配置文件方面会存在差异,当然你可以去花不少的时间去网上查找相关的资料,当你准备 ...
- 中国MOOC_面向对象程序设计——Java语言_第4章 继承与多态_第4周编程题_将MP3媒体类型存放进Database
本周我们介绍了以继承方式实现的媒体资料库,在课程代码实现的基础上,请实现一个表达MP3的媒体类型,能和CD.DVD一样存放进这个Database.请提交这个MP3类的代码.如果你认为为了能存放MP3, ...
- 使用idea创建一个maven工程
使用idea创建一个maven工程 一.创建maven工程 二.输入工程名 三.指定maven仓库 四.点击finish 五.修改工程结构(file->project structure) 六. ...
- SEO高级技巧
原文地址:http://www.it28.cn/sousuoyinqing/853115.html 现在提起SEO来最少能让一部人感到痛苦,为什么呢,因为他们看不到希望,他们追求的永远是排名,其实SE ...
- 华为交换机MSTP+VRRP配置实例说明文档
华为交换机MSTP+VRRP配置实例说明文档 拓扑图 IP地址规划表 设备名称 设备接口 对端设备 对端接口 VLAN VLAN /接口地址 备注 SW0 GE0/0/23 SW2 GE0/0/23 ...
- Chrome Inspect调试stetho出现空白的解决方法
stetho可以使用chrome调试webview,有网友反映国内不行,亲测了一下是有解决方法的: Chrome://inspect 打开后会发现stetho的页面: 点击inspect,如果没有Fa ...
- [iOS] WSHorizontalPickerView 图片水平滚动封装
之前这篇文章传送门本来是记录自己练手的demo的,后来很多人来问我要代码.今天就抽时间封装了一下,没有考虑太多情况,等我有空再去仔细考虑吧. 代码在:Github 用法很简单,创建对象,设置数据源,记 ...
- 【redis专题(2)】命令语法介绍之string
REDIS有5大数据结构:string,link,sortedset,sets,hash. 这5个结构我将用5篇文章来记录各自是怎么用的,然后再用一篇文章来说一下各自的应用场景: 更多语法请参考: h ...