Spring中实现多数据源事务管理 - CSDN博客
前言
由于项目中引入了多个数据源,并且需要对多个数据源进行写操作,那么多数据源的事务管理自然成了不可避免的问题,这也让我对 @Transactional
注解有了进一步的理解(但实际上也并不是非常深入)
然而这是一个演进的过程,刚开始项目中并没有使用 @Transactional
指定具体的 TransactionManager
,所以新增一个数据源后,对原有的事务产生了影响了,这也是偶尔在一次测试报错而结果没有回滚之后才发现的,遂对于 @Transactional
注解的一些参数项进行了了解。
研究
由于容器中存在两个 TransactionManager
,那么被 @Transactional
注解的方法到底使用了哪个 TransactionManager
来进行事务管理,抑或是同时使用了两个 TransactionManager
来进行事务管理都是我们需要搞清楚的问题。
首先我们先看看 @Transactional
注解上有没有提供配置项来指定 TransactionManager
,果不其然,发现 value
属性就是用来指定具体 TransactionManager
的,通过 id
或者 name
来指定唯一一个 TransactionManager
,那么对于只需要一个事务管理的方法,问题就简单多了:
@Transactional(value = "database2TransactionManager")
public void test(String a) {
// business operation
}
- 1
- 2
- 3
- 4
关于不指定TransactionManager时会使用哪一个TransactionManager,有兴趣的童鞋可以参考另一篇文章,讲的比较清晰: http://blog.sina.com.cn/s/blog_8f61307b0100ynfb.html
好了,回到我们研究的问题,那么对于需要写入多个数据源的业务方法该怎么办呢?
进一步研究
看来 @Transactional
是没有提供这种功能了,那么就自己写了一个吧。我记得 Spring中的事务管理分编程式事务和声明式事务。我们平时使用的 @Transactional
就是声明式事务,它的好处其实也就是灵活度更高、代码的耦合性更低,最终的事务管理实现还是一样的,只不过将具体逻辑都剥离到了切面中。所以我们可以手写一个切面来写一次“编程式事务”,当然在具体应用时,还是声明式的。
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;
}
}
- 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
我们借用这个逻辑将事务管理相关提取到切面中,并在进入目标方法之前,让多个 TransactionManager
都开启事务,并在成功执行后一并提交或失败后一并回滚,具体代码:
/**
* @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());
}
}
- 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
整体结构很清晰:
1. 首先根据指定的多个 TransactionManager
依次开启事务,这个次序不影响,因为其实大家都是平等的。
2. 其次就是调用目标方法执行具体的业务逻辑
3. 若是成功返回则提交每个事务,若中途报错,那么就回滚每个事务
其中为什么要用 Stack
来保存 TransactionManager
和 TransactionStatus
呢?那是因为 Spring的事务处理是按照 LIFO/stack behavior的方式进行的。如若顺序有误,则会报错:
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
- 1
- 2
- 3
- 4
- 5
- 6
题外话
刚开始碰到这个问题的时候,先想到的是分布式事务管理,也去看了JTA相关的文章,但是好像比较麻烦,而且都是一些老文章,于是想试试自己实现,最后也实现了。所以想知道 JTA TransactionManager
究竟有什么用呢?