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

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

前言

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

关于不指定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<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. 首先根据指定的多个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

题外话

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

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-07-30 10:17:40

Spring中实现多数据源事务管理的相关文章

Spring 使用注解方式进行事务管理

使用步骤: 步骤一.在spring配置文件中引入<tx:>命名空间<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation

【转】Spring 使用注解方式进行事务管理

Spring 使用注解方式进行事务管理 原文链接 http://www.cnblogs.com/younggun/archive/2013/07/16/3193800.html#top 使用步骤: 步骤一.在spring配置文件中引入<tx:>命名空间 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-i

Spring事务隔离级别与传播机制,spring+mybatis+atomikos实现分布式事务管理

本文转载于本人另一博客[http://blog.csdn.net/liaohaojian/article/details/68488150] 1.事务的定义:事务是指多个操作单元组成的合集,多个单元操作是整体不可分割的,要么都操作不成功,要么都成功.其必须遵循四个原则(ACID). 原子性(Atomicity):即事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做: 一致性(Consistency):在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据还是应该处于正确

spring事务隔离级别、传播行为以及spring+mybatis+atomikos实现分布式事务管理

转载自:http://blog.csdn.net/liaohaojian/article/details/68488150 1.事务的定义:事务是指多个操作单元组成的合集,多个单元操作是整体不可分割的,要么都操作不成功,要么都成功.其必须遵循四个原则(ACID). 原子性(Atomicity):即事务是不可分割的最小工作单元,事务内的操作要么全做,要么全不做: 一致性(Consistency):在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据还是应该处于正确的状态,即数据完整

spring中使用aop配置事务

spring的事务配置有5种方式,这里记录其中的一种:基于tx/aop声明式事务配置 在之前spring aop介绍和示例这篇所附代码的基础上进行测试 一.添加save方法 1.在testDao类里添加方法: /** * 保存实体 */ public void save(testInfo info) { sessionFactory.getCurrentSession().save(info); } 2.在HelloController类里添加方法: /** * 保存实体 * @param re

spring boot配置mybatis和事务管理

spring boot配置mybatis和事务管理 一.spring boot与mybatis的配置 1.首先,spring boot 配置mybatis需要的全部依赖如下: <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId>

SSM框架中使用Spring的@Transactional注解进行事务管理

一 介绍 在企业级应用中,保护数据的完整性是非常重要的一件事.因此不管应用的性能是多么的高.界面是多么的好看,如果在转账的过程中出现了意外导致用户的账号金额发生错误,那么这样的应用程序也是不可接受的 数据库的事务管理可以有效地保护数据的完整性(PS:关于数据库的事务管理基础可以参考我以前写过的这篇文章:http://www.zifangsky.cn/385.html),但是原生态的事务操作需要写不少的代码,无疑是非常麻烦的.在使用了Spring框架的应用中,我们可以使用@Transactiona

spring JTA多数据源事务管理详细教程

<context:annotation-config /> <!-- 使用注解的包路径 --> <context:component-scan base-package="com.rongli.service,com.rongli.dao,com.rongli.controller" /> <!-- 支持 @Transactional 标记 --> <tx:annotation-driven transaction-manager=

spring+mybatis之声明式事务管理初识(小实例)

前几篇的文章都只是初步学习spring和mybatis框架,所写的实例也都非常简单,所进行的数据访问控制也都很简单,没有加入事务管理.这篇文章将初步接触事务管理. 1.事务管理 理解事务管理之前,先通过一个例子讲一下什么是事务管理:取钱. 比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱:然后ATM出1000元钱.这两个步骤必须是要么都执行要么都不执行.如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元:如果银行卡扣钱失败但是ATM却出