Spring中实现多数据源事务管理 - CSDN博客

标签: | 发表时间:2018-01-06 19:47 | 作者:
出处:http://blog.csdn.net

前言

由于项目中引入了多个数据源,并且需要对多个数据源进行写操作,那么多数据源的事务管理自然成了不可避免的问题,这也让我对 @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来保存 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    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

题外话

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

相关 [spring 数据 管理] 推荐:

Spring中实现多数据源事务管理 - CSDN博客

- -
由于项目中引入了多个数据源,并且需要对多个数据源进行写操作,那么多数据源的事务管理自然成了不可避免的问题,这也让我对. @Transactional注解有了进一步的理解(但实际上也并不是非常深入). 然而这是一个演进的过程,刚开始项目中并没有使用. @Transactional指定具体的. TransactionManager,所以新增一个数据源后,对原有的事务产生了影响了,这也是偶尔在一次测试报错而结果没有回滚之后才发现的,遂对于.

Spring配置多数据源

- - ITeye博客
首先在配置文件中配置多个dataSource. 扩展Spring的AbstractRoutingDataSource抽象类,实现动态数据源. AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是实现数据源的route的核心.这里对该方法进行Override.

Spring多数据源事务

- - 掘金后端
接着上一篇文章 Spring事务基础,本文主要是关于Spring多数据源的情况下如何保证事务正常回滚. 这里也是使用大家广泛使用的 jta-atomikos进行,我只是做一些总结方便以后自己直接拿来用. 如果你非常着急,那么可以直接下载这个项目看看即可:. 网上已经有很多关于jta-atomikos的相关文章,本文可能有点绕,不容易看得懂,所以在此描述一下思路:.

Spring Boot 2 实战:使用 Flyway 管理你数据库的版本变更 - SegmentFault 思否

- -
随着项目的不断迭代,数据库表结构、数据都在发生着变化. 甚至有的业务在多环境版本并行运行. 数据为王的时代,管理好数据库的版本也成为了迫切的需要. Git之类的版本控制工具来管理数据库. Liquibase来管理数据库版本. Flyway相对来说比较受欢迎. Flyway大受欢迎是因为它具有以下优点:.

spring的事务管理笔记

- - 企业架构 - ITeye博客
首先,事务管理是保证数据操作的事务性 ACID(即原子性、一致性、隔离性、持久性).        对于使用支持事务管理的数据库时,普通的jdbc的连接没用配置事务也可以保存变更,原因在于连接的属性autocommit设置了true,即自动提交了事务.        而对于某些数据库本身没有对事务的支持,那么事务管理也是一纸空谈没有必要进行配置(有例外),如MySQL的MyISAM没有事务管理的支持.

spring+hibernate+atomikos 分布式事务管理

- - 企业架构 - ITeye博客
网上有很多的atomikos的分布式事务管理的配置,但是大多数都是同一类型的数据库,并没有跨数据库类型的配置. 使用的数据库是Oracle和mysql. . .

Spring+quartz 实现动态管理任务

- - 寒江孤影
在实际项目应用中经常会用到定时任务,可以通过quartz和spring的简单配置即可完成,但如果要改变任务的执行时间、频率,废弃任务等就需要改变配置甚至代码需要重启服务器,这里介绍一下如何通过quartz与spring的组合实现动态的改变定时任务的状态的一个实现. 本文章适合对quartz和spring有一定了解的读者.

Spring 事务管理的一个 trick

- - 码蜂笔记
最近有同事碰到这个异常信息: Transaction rolled back because it has been marked as rollback-only ,异常栈被吃了,没打印出来. 他期望的是 innerService.innerTx(ex); 调用即使失败了也不会影响 OutterService.outTx 方法上的事务,只回滚了 innerTx 的操作.

spring+hibernate多数据源的应用

- - CSDN博客推荐文章
我有两个数据库test,和test1,两个库里都有一张表TEST_ONE. applicationContext.xml的配置如下. //数据库test1配置.   //整合两个数据源,指定数据源管理类.    //数据库test.    //数据库test1. //这个类是用来管理数据源的,配置文件中.

Spring+MyBatis实践——MyBatis访问数据库

- - 开源软件 - ITeye博客
    在http://dufengx201406163237.iteye.com/blog/2102054中描述了工程的配置,在此记录一下如何使用MyBatis访问数据库;. . .