J2EE|Spring中实现多数据源事务管理

Spring中实现多数据源事务管理 前言 由于项目中引入了多个数据源,并且需要对多个数据源进行写操作,那么多数据源的事务管理自然成了不可避免的问题,这也让我对@Transactional注解有了进一步的理解(但实际上也并不是非常深入)
然而这是一个演进的过程,刚开始项目中并没有使用@Transactional指定具体的TransactionManager,所以新增一个数据源后,对原有的事务产生了影响了,这也是偶尔在一次测试报错而结果没有回滚之后才发现的,遂对于@Transactional注解的一些参数项进行了了解。
研究 由于容器中存在两个TransactionManager,那么被@Transactional注解的方法到底使用了哪个TransactionManager来进行事务管理,抑或是同时使用了两个TransactionManager来进行事务管理都是我们需要搞清楚的问题。
首先我们先看看@Transactional注解上有没有提供配置项来指定TransactionManager,果不其然,发现value属性就是用来指定具体TransactionManager的,通过id或者name来指定唯一一个TransactionManager,那么对于只需要一个事务管理的方法,问题就简单多了:

@Transactional(value = "https://www.it610.com/article/database2TransactionManager") public void test(String a) { // business operation }

关于不指定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; } }

我们借用这个逻辑将事务管理相关提取到切面中,并在进入目标方法之前,让多个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 dataSourceTransactionManagerStack = new Stack(); Stack transactionStatuStack = new Stack(); 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 dataSourceTransactionManagerStack, Stack 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 dataSourceTransactionManagerStack, Stack 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 dataSourceTransactionManagerStack, Stack transactionStatuStack) { while (!dataSourceTransactionManagerStack.isEmpty()) { dataSourceTransactionManagerStack.pop().rollback( transactionStatuStack.pop()); } }

整体结构很清晰:
1. 首先根据指定的多个TransactionManager依次开启事务,这个次序不影响,因为其实大家都是平等的。
2. 其次就是调用目标方法执行具体的业务逻辑
3. 若是成功返回则提交每个事务,若中途报错,那么就回滚每个事务
其中为什么要用Stack来保存TransactionManagerTransactionStatus呢?那是因为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

题外话 【J2EE|Spring中实现多数据源事务管理】刚开始碰到这个问题的时候,先想到的是分布式事务管理,也去看了JTA相关的文章,但是好像比较麻烦,而且都是一些老文章,于是想试试自己实现,最后也实现了。所以想知道JTA TransactionManager究竟有什么用呢?

    推荐阅读