Spring之事务详解

本文深入解析Spring事务管理机制,涵盖声明式与编程式事务、事务传播机制、状态操作及常见失效情形,探讨自定义事务与分布式事务解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1 Spring事务

1.1 定义

1.1.1 事务概念

1.1.2 事务分类

1.2 声明式事务

1.2.1 @EnableTransactionManagement工作原理

1.2.2 实现原理

1.2.3 Spring事务执行流程图

1.2.4 SpringBoot中不需要声明

1.3 编程式事务

1.3.1 编程式事务使用方式

1.3.1.1 方式一:DataSource

1.3.1.2 方式二:DataSourceTransactionManager

1.3.1.3 方式三:DataSourceTransactionManager+TransactionDefinition

1.3.1.4 方式四:TransactionTemplate

1.3.2 TransactionSynchronizationManager

1.3.3 问题分析完如何解决问题

1.4 Spring事务传播机制

1.4.1 传播机制入门案例

1.4.2 Spring七个事务传播属性

1.4.3 案例分析

1.5 Spring事务状态操作

1.5.1 事务强制回滚

1.5.2  事务部分回滚

1.5.3 监听事务状态

1.6 声明式事务失效情况

1.6.1 @Transactional非public修饰的方法上

1.6.2 方法使用final或者static修饰

1.6.3 @Transactional 注解属性propagation设置错误

1.6.4 @Transactional 注解属性 rollbackFor设置错误

1.6.5 同一个类中方法调用导致@Transactional失效

1.6.6 数据库不支持事务

1.6.7 多线程

1.6.8  @Transaction和@Test联用失效

1.7 @Transaction和Mybatis-Plus的@DS问题

1.7.1 代码示例

1.7.2 问题现象

1.7.3 深入分析原因

1.7.4 解决方法

1.8 分布式事务

1.9 自定义事务

1.9.1 Spring事务

1.9.2 自定义管理事务

1.9.3 定义多事务注解

1.9.4 包装Connection

1.9.4 事务上下文管理

1.9.5 数据源兼容处理

1.9.6 切面处理


1 Spring事务

点击了解数据库之隔离级别,脏读幻读,事务特性 相关信息

1.1 定义

1.1.1 事务概念

事务,就是一组操作数据库的动作集合。事务是现代数据库理论中的核心概念之一。
如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务。当所有的步骤像一个操作一样被完整地执行,我们称该事务被提交
由于其中的一部分或多步执行失败,导致没有步骤被提交,则事务必须回滚到最初的系统状态。

1.1.2 事务分类

Spring 支持编程式事务管理和声明式事务管理两种方式

  1. 编程式事务
    编程式事务管理使用 TransactionTemplate,需要显式执行事务,比如,需要显示调用commit或者rollback方法

  2. 声明式事务
    声明式事务管理建立在 AOP 之上的。其本质是通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务
    优点是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过 @Transactional 注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别

1.2 声明式事务

1.2.1 @EnableTransactionManagement工作原理

开启Spring事务本质上就是增加了一个Advisor,但我们使用@EnableTransactionManagement注解来开启Spring事务是,该注解代理的功能就是向Spring容器中添加了两个Bean:

  • AutoProxyRegistrar
  • ProxyTransactionManagementConfiguration

AutoProxyRegistrar主要的作用是向Spring容器中注册了一个InfrastructureAdvisorAutoProxyCreator的Bean。而InfrastructureAdvisorAutoProxyCreator继承了AbstractAdvisorAutoProxyCreator,所以这个类的主要作用就是开启自动代理的作用,也就是一个BeanPostProcessor,会在初始化后步骤中去寻找Advisor类型的Bean,并判断当前某个Bean是否有匹配的Advisor,是否需要利用动态代理产生一个代理对象。

ProxyTransactionManagementConfiguration是一个配置类,它又定义了另外三个bean:

  • BeanFactoryTransactionAttributeSourceAdvisor:一个Advisor
  • AnnotationTransactionAttributeSource:相当于BeanFactoryTransactionAttributeSourceAdvisor中的Pointcut
  • TransactionInterceptor:相当于BeanFactoryTransactionAttributeSourceAdvisor中的Advice

AnnotationTransactionAttributeSource就是用来判断某个类上是否存在@Transactional注解,或者判断某个方法上是否存在@Transactional注解的。
TransactionInterceptor就是代理逻辑,当某个类中存在@Transactional注解时,到时就产生一个代理对象作为Bean,代理对象在执行某个方法时,最终就会进入到TransactionInterceptor的invoke()方法

1.2.2 实现原理

声明式事务实现原理就是通过AOP/动态代理

  • 在Bean初始化阶段创建代理对象:Spring容器在初始化每个单例bean的时候,会遍历容器中的所有BeanPostProcessor实现类,并执行其postProcessAfterInitialization方法,在执行AbstractAutoProxyCreator类的postProcessAfterInitialization方法时会遍历容器中所有的切面,查找与当前实例化bean匹配的切面,这里会获取事务属性切面,查找@Transactional注解及其属性值,然后根据得到的切面创建一个代理对象,默认是使用JDK动态代理创建代理,如果目标类是接口,则使用JDK动态代理,否则使用Cglib。
    一个Bean在执行Bean的创建生命周期时,会经过InfrastructureAdvisorAutoProxyCreator的初始化后的方法,会判断当前Bean对象是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,匹配逻辑为判断该Bean的类上是否存在@Transactional注解,或者类中的某个方法上是否存在@Transactional注解,如果存在则表示该Bean需要进行动态代理产生一个代理对象作为Bean对象

  • 在执行目标方法时进行事务增强操作:当通过代理对象调用Bean方法的时候,会触发对应的AOP增强拦截器,声明式事务是一种环绕增强,对应接口为MethodInterceptor,事务增强对该接口的实现为TransactionInterceptor,类图如下

事务拦截器TransactionInterceptorinvoke方法中,通过调用父类TransactionAspectSupportinvokeWithinTransaction方法进行事务处理,包括开启事务、事务提交、异常回滚 

该代理对象在执行某个方法时,会再次判断当前执行的方法是否和BeanFactoryTransactionAttributeSourceAdvisor匹配,如果匹配则执行该Advisor中的TransactionInterceptor的invoke()方法,执行基本流程为:

  • 利用所配置的PlatformTransactionManager事务管理器新建一个数据库连接
  • 修改数据库连接的autocommit为false
  • 执行MethodInvocation.proceed()方法,简单理解就是执行业务方法,其中就会执行sql
  • 如果没有抛异常,则提交
  • 如果抛了异常,则回滚

1.2.3 Spring事务执行流程图

https://www.processon.com/view/link/5fab6edf1e0853569633cc06

 接上图

1.2.4 SpringBoot中不需要声明

在springboot中使用声明式事务时,不需要使用@EnableTransactionManagement就会生效

springboot在启动的时候会自动装配很多配置类,那根据上面的问题,我们自然而然就想到是springboot在启动的时候就装配了相关Transactional 的配置类了。

点击了解springboot自动流程装配分析

spring.factories文件中已经装配了事务相关信息了

1.3 编程式事务

由于声明式事务并不能解决 多线程环境下也能保持事务一致性 的问题,那么就只能求助于编程式事务了。

1.3.1 编程式事务使用方式

1.3.1.1 方式一:DataSource
//如果是多数据源的情况下,需要指定具体是哪一个数据源
@Autowired
private final DataSource dataSource;
    
public void test() {
     DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
     DefaultTransactionDefinition transactionDef = new DefaultTransactionDefinition();
      transactionDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);       
     TransactionStatus transaction=transactionManager.getTransaction(transactionDef);
     try{
         //todo 业务代码
         transactionManager.commit(transaction);
      }catch(){ //手动回滚
          transactionManager.rollback(transaction);
      }
}  

任务正常都执行完毕,事务进行提交,但是会抛出异常,导致事务回滚:,这里需要再次回顾一下Spring事务实现的小细节:

一次事务的完成通常都是默认在当前线程内完成的,又因为一次事务的执行过程中,涉及到对当前数据库连接Connection的操作,因此为了避免将Connection在事务执行过程中来回传递,我们可以将Connection绑定到当前事务执行线程对应的ThreadLocalMap内部,顺便还可以将一些其他属性也放入其中进行保存,在Spring中,负责保存这些ThreadLocal属性的实现类由TransactionSynchronizationManager承担。

1.3.1.2 方式二:DataSourceTransactionManager

使用DataSourceTransactionManager 来注入管理

@Autowired
private DataSourceTransactionManager transactionManager;

public void test(){
        DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
        defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus transaction = transactionManager.getTransaction(defaultTransactionDefinition);       
        try{
            //todo 业务代码
            transactionManager.commit(transaction);
        }catch(){ //手动回滚
            transactionManager.rollback(transaction);
        }
}
1.3.1.3 方式三:DataSourceTransactionManager+TransactionDefinition

使用 PlatformTransactionManager 这个接口中定义了三个方法 getTransaction创建事务,commit 提交事务,rollback 回滚事务。它的实现类是 AbstractPlatformTransactionManager

@Autowired
DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
TransactionDefinition transactionDefinition;
 
// 手动创建事务
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
 
// 手动提交事务
dataSourceTransactionManager.commit(transactionStatus);
 
// 手动回滚事务。(最好是放在catch 里面,防止程序异常而事务一直卡在哪里未提交)
dataSourceTransactionManager.rollback(transactionStatus);
1.3.1.4 方式四:TransactionTemplate

如果不想手动的commit或者rollback可以用spring推荐的TransactionTemplate。 通常情况下,使用 TransactionTemplate 可以在 Spring 环境中执行事务操作,而无需显式地声明 @Transactional 注解。这对于某些情况下无法使用声明式事务管理的场景非常有用,比如在非 Spring 管理的对象中执行事务操作。

以下是使用 TransactionTemplate 的基本示例:

import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

public class TransactionalService {

    @Autowired   
    private TransactionTemplate transactionTemplate;


    public void doTransactionalOperation() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    // 在事务中执行的业务逻辑
                    // 可能包括数据库操作、文件操作等
                    // 如果发生异常,事务会被回滚
                    // 如果正常执行完成,事务会被提交
                    // 这里只是一个示例
                    // 实际业务逻辑需要根据需求来编写
                    // 比如调用其他服务、持久化数据等
                } catch (Exception e) {
                    status.setRollbackOnly(); // 设置事务为回滚状态
                    throw e; // 抛出异常以使得事务回滚
                }
            }
        });
    }
}

在上面的示例中,TransactionalService 类中使用了 TransactionTemplate 进行事务操作。TransactionTemplateexecute 方法接受一个 TransactionCallback 对象作为参数,用于执行需要事务管理的操作。在 doInTransactionWithoutResult 方法中,你可以执行需要事务管理的操作,如果出现异常,事务将会被回滚;如果没有异常,则事务将会被提交。

在 Spring 的 TransactionTemplate 中,execute 方法和 executeWithoutResult 方法都用于执行事务操作,但它们之间有一些区别。

  1. execute 方法
    execute 方法用于执行具有返回值的事务性操作。在 execute 方法中,可以执行需要在事务中执行的业务逻辑,并返回一个结果对象。
    如果业务逻辑执行成功,返回的结果对象会被 execute 方法返回。
    如果业务逻辑执行失败,execute 方法会捕获异常并将事务标记为回滚状态。在异常被捕获后,事务会回滚,并且异常会被重新抛出
  2. executeWithoutResult 方法
    executeWithoutResult 方法用于执行不需要返回值的事务性操作。在 executeWithoutResult 方法中,可以执行需要在事务中执行的业务逻辑,但不需要返回任何结果。
    如果业务逻辑执行成功,事务会被提交。
    如果业务逻辑执行失败,executeWithoutResult 方法会捕获异常并将事务标记为回滚状态。在异常被捕获后,事务会回滚,但异常不会被重新抛出。相反,异常会被记录到日志中,并通过 TransactionTemplatesetRollbackOnly 方法标记事务为回滚状态。

1.3.2 TransactionSynchronizationManager

TransactionSynchronizationManager类内部默认提供了下面六个ThreadLocal属性,分别保存当前线程对应的不同事务资源:

保存当前事务关联的资源--默认只会在新建事务的时候保存当前获取到的DataSource和当前事务对应Connection的映射关系--当然这里Connection被包装为了ConnectionHolder
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
事务监听者--在事务执行到某个阶段的过程中,会去回调监听者对应的回调接口(典型观察者模式的应用)---默认为空集合
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations");
见名知意: 存放当前事务名字
private static final ThreadLocal<String> currentTransactionName =new NamedThreadLocal<>("Current transaction name");
见名知意: 存放当前事务是否是只读事务
private static final ThreadLocal<Boolean> currentTransactionReadOnly =new NamedThreadLocal<>("Current transaction read-only status");
见名知意: 存放当前事务的隔离级别
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =new NamedThreadLocal<>("Current transaction isolation level");
见名知意: 存放当前事务是否处于激活状态
private static final ThreadLocal<Boolean> actualTransactionActive =new NamedThreadLocal<>("Actual transaction active");

那么抛出的异常的原因也就很清楚了,无法在main线程找到当前事务对应的资源,原因如下 

 开启新事务时,事务相关资源都被绑定到了thread-cache-pool-1线程对应的threadLocalMap内部,而当执行事务提交代码时,commit内部需要从TransactionSynchronizationManager中获取当前事务的资源,显然我们无法从main线程对应的threadLocalMap中获取到对应的事务资源,这也就是异常抛出的原因。

1.3.3 问题分析完如何解决问题

这里给出一个我首先想到的简单粗暴的方法—CopyTransactionResource—将事务资源在两个线程间来回复制,这里给出解决问题的代码示例:

@Component
@RequiredArgsConstructor
public class MultiplyThreadTransactionManager {
    /**
     * 如果是多数据源的情况下,需要指定具体是哪一个数据源
     */
    private final DataSource dataSource;

    /**
     * 执行的是无返回值的任务
     * @param tasks 异步执行的任务列表
     * @param executor 异步执行任务需要用到的线程池,考虑到线程池需要隔离,这里强制要求传
     */
    public void runAsyncButWaitUntilAllDown(List<Runnable> tasks, Executor executor) {
        if(executor==null){
            throw new IllegalArgumentException("线程池不能为空");
        }
        DataSourceTransactionManager transactionManager = getTransactionManager();
        //是否发生了异常
        AtomicBoolean ex=new AtomicBoolean();

        List<CompletableFuture> taskFutureList=new ArrayList<>(tasks.size());
        List<TransactionStatus> transactionStatusList=new ArrayList<>(tasks.size());
        List<TransactionResource> transactionResources=new ArrayList<>(tasks.size());

        tasks.forEach(task->{
            taskFutureList.add(CompletableFuture.runAsync(
                    () -> {
                        try{
                            //1.开启新事务
                            transactionStatusList.add(openNewTransaction(transactionManager));
                            //2.copy事务资源
                         transactionResources.add(TransactionResource.copyTransactionResource());
                            //3.异步任务执行
                            task.run();
                        }catch (Throwable throwable){
                            //打印异常
                            throwable.printStackTrace();
                            //其中某个异步任务执行出现了异常,进行标记
                            ex.set(Boolean.TRUE);
                            //其他任务还没执行的不需要执行了
                            taskFutureList.forEach(completableFuture -> completableFuture.cancel(true));
                        }
                    }
                    , executor)
            );
        });

        try {
            //阻塞直到所有任务全部执行结束---如果有任务被取消,这里会抛出异常滴,需要捕获
            CompletableFuture.allOf(taskFutureList.toArray(new CompletableFuture[]{})).get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        //发生了异常则进行回滚操作,否则提交
        if(ex.get()){
            System.out.println("发生异常,全部事务回滚");
            for (int i = 0; i < tasks.size(); i++) {
                transactionResources.get(i).autoWiredTransactionResource();
                transactionManager.rollback(transactionStatusList.get(i));
                transactionResources.get(i).removeTransactionResource();
            }
        }else {
            System.out.println("全部事务正常提交");
            for (int i = 0; i < tasks.size(); i++) {
                transactionResources.get(i).autoWiredTransactionResource();
                transactionManager.commit(transactionStatusList.get(i));
                transactionResources.get(i).removeTransactionResource();
            }
        }
    }

    private TransactionStatus openNewTransaction(DataSourceTransactionManager transactionManager) {
        //JdbcTransactionManager根据TransactionDefinition信息来进行一些连接属性的设置
        //包括隔离级别和传播行为等
        DefaultTransactionDefinition transactionDef = new DefaultTransactionDefinition();
        //开启一个新事务---此时autocommit已经被设置为了false,并且当前没有事务,这里创建的是一个新事务
        return transactionManager.getTransaction(transactionDef);
    }

    private DataSourceTransactionManager getTransactionManager() {
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * 保存当前事务资源,用于线程间的事务资源COPY操作
     */
    @Builder
    private static class TransactionResource{
        //事务结束后默认会移除集合中的DataSource作为key关联的资源记录
        private  Map<Object, Object> resources = new HashMap<>();

        //下面五个属性会在事务结束后被自动清理,无需我们手动清理
        private  Set<TransactionSynchronization> synchronizations =new HashSet<>();

        private  String currentTransactionName;

        private Boolean currentTransactionReadOnly;

        private Integer currentTransactionIsolationLevel;

        private Boolean actualTransactionActive;

        public static TransactionResource copyTransactionResource(){
            return TransactionResource.builder()
                    //返回的是不可变集合
                    .resources(TransactionSynchronizationManager.getResourceMap())
                    //如果需要注册事务监听者,这里记得修改--我们这里不需要,就采用默认负责--spring事务内部默认也是这个值
                    .synchronizations(new LinkedHashSet<>())
                    .currentTransactionName(TransactionSynchronizationManager.getCurrentTransactionName())
                    .currentTransactionReadOnly(TransactionSynchronizationManager.isCurrentTransactionReadOnly())
                    .currentTransactionIsolationLevel(TransactionSynchronizationManager.getCurrentTransactionIsolationLevel())
                    .actualTransactionActive(TransactionSynchronizationManager.isActualTransactionActive())
                    .build();
        }

        public void autoWiredTransactionResource(){
             resources.forEach(TransactionSynchronizationManager::bindResource);
             //如果需要注册事务监听者,这里记得修改--我们这里不需要,就采用默认负责--spring事务内部默认也是这个值
             TransactionSynchronizationManager.initSynchronization();
             TransactionSynchronizationManager.setActualTransactionActive(actualTransactionActive);
             TransactionSynchronizationManager.setCurrentTransactionName(currentTransactionName);
             TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(currentTransactionIsolationLevel);
             TransactionSynchronizationManager.setCurrentTransactionReadOnly(currentTransactionReadOnly);
        }

        public void removeTransactionResource() {
            //事务结束后默认会移除集合中的DataSource作为key关联的资源记录
            //DataSource如果重复移除,unbindResource时会因为不存在此key关联的事务资源而报错
            resources.keySet().forEach(key->{
                if(!(key instanceof  DataSource)){
                    TransactionSynchronizationManager.unbindResource(key);
                }
            });
        }
    }
}

测试代码

@SpringBootTest(classes = UserMain.class)
public class Test {
    @Resource
    private UserMapper userMapper;
    @Resource
    private SignMapper signMapper;
    @Resource
    private MultiplyThreadTransactionManager multiplyThreadTransactionManager;

    @SneakyThrows
    @org.junit.jupiter.api.Test
    public void test(){
        List<Runnable> tasks=new ArrayList<>();

        tasks.add(()->{
                userMapper.deleteById(26);
                throw new RuntimeException("我就要抛出异常!");
        });

        tasks.add(()->{
            signMapper.deleteById(10);
        });

        multiplyThreadTransactionManager.runAsyncButWaitUntilAllDown(tasks, Executors.newCachedThreadPool());
    }

}

图片

事务都进行了回滚,数据库数据没变。

1.4 Spring事务传播机制

1.4.1 传播机制入门案例

先来看一种情况,a()在一个事务中执行,调用b()方法时需要新开一个事务执行的情况分析:

  • 首先,代理对象执行a()方法前,先利用事务管理器新建一个数据库连接a
  • 将数据库连接a的autocommit改为false
  • 把数据库连接a设置到ThreadLocal中
  • 执行a()方法中的sql
  • 执行a()方法过程中,调用了b()方法,需要新开一个事务(注意用代理对象调用b()方法
    代理对象执行b()方法前,判断出来了当前线程中已经存在一个数据库连接a了,表示当前线程其实已经拥有一个Spring事务了,则进行挂起
    挂起就是把ThreadLocal中的数据库连接a从ThreadLocal中移除,并放入一个挂起资源对象中
    挂起完成后,再次利用事务管理器新建一个数据库连接b,将数据库连接b的autocommit改为false
    把数据库连接b设置到ThreadLocal中,执行b()方法中的sql
    b()方法正常执行完,则从ThreadLocal中拿到数据库连接b进行提交
    提交之后会恢复所挂起的数据库连接a,这里的恢复,其实只是把在挂起资源对象中所保存的数据库连接a再次设置到ThreadLocal中
  • a()方法正常执行完,则从ThreadLocal中拿到数据库连接a进行提交

这个过程中最为核心的是:在执行某个方法时,判断当前是否已经存在一个事务,就是判断当前线程的ThreadLocal中是否存在一个数据库连接对象,如果存在则表示已经存在一个事务了。

1.4.2 Spring七个事务传播属性

  1. PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
  2. PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。
  3. PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。
  4. PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起。
  5. PROPAGATION_NOT_SUPPORTED – 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  6. PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。
  7. PROPAGATION_NESTED – 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

备注:常用的两个事务传播属性是1和4,即PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW

其中,以非事务方式运行,表示以非Spring事务运行,表示在执行这个方法时,Spring事务管理器不会去建立数据库连接,执行sql时,由Mybatis或JdbcTemplate自己来建立数据库连接来执行sql

1.4.3 案例分析

情况1

@Component
public class UserService {
  @Autowired
  private UserService userService;

  @Transactional
  public void test() {
    // test方法中的sql
    userService.a();
  }

  @Transactional
  public void a() {
    // a方法中的sql
  }
}

默认情况下传播机制为REQUIRED表示当前如果没有事务则新建一个事务,如果有事务则在当前事务中执行

所以上面这种情况的执行流程如下:

  • 新建一个数据库连接conn
  • 设置conn的autocommit为false
  • 执行test方法中的sql
  • 执行a方法中的sql
  • 执行conn的commit()方法进行提交

情况2

假如是这种情况:

@Component
public class UserService {
  @Autowired
  private UserService userService;

  @Transactional
  public void test() {
    // test方法中的sql
    userService.a();
        int result = 100/0;
  }

  @Transactional
  public void a() {
    // a方法中的sql
  }
}

所以上面这种情况的执行流程如下:

  • 新建一个数据库连接conn
  • 设置conn的autocommit为false
  • 执行test方法中的sql
  • 执行a方法中的sql
  • 抛出异常
  • 执行conn的rollback()方法进行回滚,所以两个方法中的sql都会回滚掉

情况3

假如是这种情况:

@Component
public class UserService {
  @Autowired
  private UserService userService;

  @Transactional
  public void test() {
    // test方法中的sql
    userService.a();
  }

  @Transactional
  public void a() {
    // a方法中的sql
        int result = 100/0;
  }
}

所以上面这种情况的执行流程如下:

  • 新建一个数据库连接conn
  • 设置conn的autocommit为false
  • 执行test方法中的sql
  • 执行a方法中的sql
  • 抛出异常
  • 执行conn的rollback()方法进行回滚,所以两个方法中的sql都会回滚掉

情况4

如果是这种情况:

@Component
public class UserService {
  @Autowired
  private UserService userService;

  @Transactional
  public void test() {
    // test方法中的sql
    userService.a();
  }

  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void a() {
    // a方法中的sql
    int result = 100/0;
  }
}

所以上面这种情况的执行流程如下:

  • 新建一个数据库连接conn
  • 设置conn的autocommit为false
  • 执行test方法中的sql
  • 又新建一个数据库连接conn2
  • 执行a方法中的sql
  • 抛出异常
  • 执行conn2的rollback()方法进行回滚
  • 继续抛异常,对于test()方法而言,它会接收到一个异常,然后抛出
  • 执行conn的rollback()方法进行回滚,最终还是两个方法中的sql都回滚了

事务机制案例分析和状态操作参考链接

1.5 Spring事务状态操作

1.5.1 事务强制回滚

正常情况下,a()调用b()方法时,如果b()方法抛了异常,但是在a()方法捕获了,那么a()的事务还是会正常提交的,但是有的时候,我们捕获异常可能仅仅只是不把异常信息返回给客户端,而是为了返回一些更友好的错误信息,而这个时候,我们还是希望事务能回滚的,那这个时候就得告诉Spring把当前事务回滚掉,做法就是:

@Transactional
public void test(){
  
    // 执行sql
  try {
    b();
  } catch (Exception e) {
    // 构造友好的错误信息返回
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  }
    
}

public void b() throws Exception {
  throw new Exception();
}

1.5.2  事务部分回滚

使用 Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); 设置回滚点。
使用 TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint); 回滚到savePoint。

@Override
@Transactional(rollbackFor = Exception.class)
public Object submitOrder (){  
    success();  
    //只回滚以下异常,
    Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
    try {  
        exception(); 
     } catch (Exception e) {  
        e.printStackTrace();     
        // 手工回滚事务
        TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
        return ApiReturnUtil.error();
     }  
    return ApiReturnUtil.success();
}

1.5.3 监听事务状态

 Spring事务有可能会提交,回滚、挂起、恢复,所以Spring事务提供了一种机制,可以让程序员来监听当前Spring事务所处于的状态

@Component
public class UserService {

  @Autowired
  private JdbcTemplate jdbcTemplate;

  @Autowired
  private UserService userService;

  @Transactional
  public void test(){
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {

      @Override
      public void suspend() {
        System.out.println("test被挂起了");
      }

      @Override
      public void resume() {
        System.out.println("test被恢复了");
      }

      @Override
      public void beforeCommit(boolean readOnly) {
        System.out.println("test准备要提交了");
      }

      @Override
      public void beforeCompletion() {
        System.out.println("test准备要提交或回滚了");
      }

      @Override
      public void afterCommit() {
        System.out.println("test提交成功了");
      }

      @Override
      public void afterCompletion(int status) {
        System.out.println("test提交或回滚成功了");
      }
    });

    jdbcTemplate.execute("insert into t1 values(1,1,1,1,'1')");
    System.out.println("test");
    userService.a();
  }

  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void a(){
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {

      @Override
      public void suspend() {
        System.out.println("a被挂起了");
      }

      @Override
      public void resume() {
        System.out.println("a被恢复了");
      }

      @Override
      public void beforeCommit(boolean readOnly) {
        System.out.println("a准备要提交了");
      }

      @Override
      public void beforeCompletion() {
        System.out.println("a准备要提交或回滚了");
      }

      @Override
      public void afterCommit() {
        System.out.println("a提交成功了");
      }

      @Override
      public void afterCompletion(int status) {
        System.out.println("a提交或回滚成功了");
      }
    });

    jdbcTemplate.execute("insert into t1 values(2,2,2,2,'2')");
    System.out.println("a");
  }

}

可以使用 TransactionSynchronizationAdapter 类和 TransactionSynchronization 类

  • TransactionSynchronization 是一个接口,定义了事务同步的回调方法。
  • TransactionSynchronizationAdapter TransactionSynchronization 的抽象适配器类,提供了 TransactionSynchronization 接口的默认空实现,使得开发者可以更方便地实现自定义的事务同步逻辑

1.6 声明式事务失效情况

1.6.1 @Transactional非public修饰的方法上

如果Transactional注解应用在非 public 修饰的方法上,Transactional将会失效。

是因为在Spring AOP 代理时,TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy  的内部类)的intercept方法 或  JdkDynamicAopProxy的invoke方法会间接调用AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute方法,获取Transactional 注解的事务配置信息。

protected TransactionAttribute computeTransactionAttribute(Method method,
    Class<?> targetClass) {
        // Don't allow no-public methods as required.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
}

此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息

1.6.2 方法使用final或者static修饰

有时候,某个方法不想被子类重新,这时可以将该方法定义成final的。普通方法这样定义是没问题的,但如果将事务方法定义成final,例如:

@Service
public class OrderServiceImpl {

    @Transactional
    public final void cancel(OrderDTO orderDTO) {
        // 取消订单
        cancelOrder(orderDTO);
    }
}

比如:OrderServiceImpl的cancel取消订单方法被final修饰符修饰,Spring事务底层使用了AOP,也就是通过JDK动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,从而无法添加事务功能。这种情况事务就会在Spring中失效。

根据这个原理可知,如果某个方法是static的,同样无法通过动态代理将方法声明为事务方法。

1.6.3 @Transactional 注解属性propagation设置错误

TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

1.6.4 @Transactional 注解属性 rollbackFor设置错误

rollbackFor可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException的异常)或者 Error才回滚事务,其他异常不会触发回滚事务。

Spring默认支持的异常回滚

// 希望自定义的异常可以进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class)

若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。

或者在@Transactional 设置 rollbackFor = Exception.class 

注意:如果异常被内部catch,程序生吞异常 

1.6.5 同一个类中方法调用导致@Transactional失效

被外部调用的公共方法A有两个进行了数据操作的子方法B和子方法C的事务注解说明:

  • 被外部调用的公共方法A未声明事务@Transactional,子方法B和C若是其他类的方法且各自声明事务,则事务由子方法B和C各自控制
  • 被外部调用的公共方法A未声明事务@Transactional,子方法B和C若是本类的方法,则无论子方法B和C是否声明事务,事务均不会生效
  • 被外部调用的公共方法A声明事务@Transactional,无论子方法B和C是不是本类的方法,无论子方法B和C是否声明事务,事务均由公共方法A控制
  • 被外部调用的公共方法A声明事务@Transactional,子方法运行异常,但运行异常被子方法自己 try-catch 处理了,则事务回滚是不会生效的

类内部访问:A 类的 a1 方法没有标注 @Transactional,a2 方法标注 @Transactional,在 a1 里面调用 a2,则不生效

开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。

那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理

 //@Transactional
     @GetMapping("/test")
     private Integer A() throws Exception {
         CityInfoDict cityInfoDict = new CityInfoDict();
         cityInfoDict.setCityName("2");
         /**
          * B 插入字段为 3的数据
          */
         this.insertB();
        /**
         * A 插入字段为 2的数据
         */
        int insert = cityInfoDictMapper.insert(cityInfoDict);
        return insert;
    }

    @Transactional()
    public Integer insertB() throws Exception {
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setCityName("3");
        cityInfoDict.setParentCityId(3);

        return cityInfoDictMapper.insert(cityInfoDict);
    }

这种情况是最常见的一种@Transactional注解失效场景

@Transactional
private Integer A() throws Exception {
    int insert = 0;
    try {
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setCityName("2");
        cityInfoDict.setParentCityId(2);
        /**
         * A 插入字段为 2的数据
         */
        insert = cityInfoDictMapper.insert(cityInfoDict);
        /**
         * B 插入字段为 3的数据
        */
        b.insertB();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务就不能正常回滚了,会抛出异常:

org.springframework.transaction.UnexpectedRollbackException: 
Transaction rolled back because it has been marked as rollback-only

1.6.6 数据库不支持事务

Spring事务生效的前提是连接的数据库支持事务,如果底层的数据库都不支持事务,则Spring事务肯定会失效的

例如:使用MySQL数据库,选用MyISAM存储引擎,因为MyISAM存储引擎本身不支持事务,因此事务毫无疑问会失效。 

1.6.7 多线程

多线程:主线程和子线程的调用,线程抛出异常

下面给出两个不同的姿势,一个是子线程抛异常,主线程 ok;一个是子线程 ok,主线程抛异常

父线程抛出异常,子线程不抛出异常
父线程抛出线程,事务回滚,因为子线程是独立存在,和父线程不在同一个事务中,所以子线程的修改并不会被回滚

public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
}
@Transactional(rollbackFor = Exception.class)
public void testMultThread() throws Exception {
    new Thread(new Runnable() {
        @SneakyThrows
        @Override
        public void run() {
            testSuccess();
        }
    }).start();
    throw new Exception("测试事务不生效");
}

父线程不抛出异常,子线程抛出异常:
由于子线程的异常不会被外部的线程捕获,所以父线程不抛异常,事务回滚没有生效

public void testSuccess() throws Exception {
    Integer id = 1;
    MyUser user = query(id);
    System.out.println("原记录:" + user);
    update(id);
    throw new Exception("测试事务不生效");
}
@Transactional(rollbackFor = Exception.class)
public void testMultThread() throws Exception {
    new Thread(new Runnable() {
        @SneakyThrows
        @Override
        public void run() {
            testSuccess();
        }
    }).start();
}

1.6.8  @Transaction和@Test联用失效

当在测试类中直接调用Spring Data JPA的接口时,需要需要在测试类中添加注解@Transactional,但是@Transactional和@Test一起连用会导致事务自动回滚,这时候需要指定事务不回滚@Rollback(false)

@Test
@Transactional
@Rollback(false)
public void updateById() {
    dao11.updateById("张三",1);
}

1.7 @Transaction和Mybatis-Plus的@DS问题

点击了解springboot多数据源配置

1.7.1 代码示例

主库

@Service
@DS("Master")
public class MasterService {
    @Autowired
    UserService userService;
    @Autowired
    BookService bookService;

    /**必须master库方法先执行,才能回滚,达到事务效果*/
    @Transactional(rollbackFor = Exception.class)
    public void upload(ReqDto reqDto){
        userService.save(reqDto);  //@DS("Master")
        bookService.save(reqDto);  //@DS("Slave")
    }
}

@Service
@DS("Slave")
public class BookService extends ServiceImpl<BookMapper, Book> {
    @Resource
    private BookMapper bookMapper;
    
    public void save(ReqDto reqDto) {
        bookMapper.save(reqDto);
    }
}

1.7.2 问题现象

使用动态数据源(@DS)时,@Transactional使用不当会照成@DS失效。

  • BookService的save上面加@Transactional,数据源没有切换
  • 开启事务的同时,会从数据库连接池获取数据库连接;
  • 如果内层的service使用@DS切换数据源,只是又做了一层拦截,但是并没有改变整个事务的连接;
  • 在这个事务内的所有数据库操作,都是在事务连接建立之后,所以会产生数据源没有切换的问题;
  • 为了使@DS起作用,必须替换数据库连接,也就是改变事务的传播机制,产生新的事务,获取新的数据库连接
     

1.7.3 深入分析原因

@Transactional执行流程

  • service的 upload方法上添加了 @Transactional 注解,Spring事务就会生效。此时,Spring TransactionInterceptor会通过AOP拦截该方法,创建事务。而创建事务,势必就会获得数据源。那么,TransactionInterceptor (事务拦截器) 会使用 Spring DataSourceTransactionManager (数据源事务管理) 创建事务,并将事务信息通过 ThreadLocal 绑定在当前线程。而事务信息,就包括事务对应的 Connection 连接。所以还没走到 Mapper 的查询操作,Connection 就已经被创建出来了。并且,因为事务信息会和当前线程绑定在一起,在 Mapper 在查询操作需要获得 Connection 时,就直接拿到当前线程绑定的 Connection ,而不是 Mapper 添加 @DS 注解所对应的 DataSource 所对应的 Connection 。
  • 现在可以把问题聚焦到 DataSourceTransactionManager 是怎么获取 DataSource 从而获得 Connection 的了。对于每个 DataSourceTransactionManager 数据库事务管理器,创建时都会传入其需要管理的 DataSource 数据源。在使用 dynamic-datasource-spring-boot-starter 时,它创建了一个 DynamicRoutingDataSource ,传入到 DataSourceTransactionManager 中。
    而 DynamicRoutingDataSource 负责管理我们配置的多个数据源。例如说,本示例中就管理了master、slave 两个数据源,并且默认使用 master 数据源。那么在当前场景下,DynamicRoutingDataSource 需要基于 @DS 获得数据源名,从而获得对应的 DataSource ,如果在 Service 方法上没有添加 @DS 注解,所以它只好返回默认数据源,也就是 master

当BookService的save上面加@Transactional(propagation =Propagation.REQUIRES_NEW),这样在调用另一个事务方法时,TransactionInterceptor会将原事务挂起,开启一个新事务,暂时性的将原事务信息和当前线程解绑

开启新事物对原来外部事务影响:内影响外,外不影响内

  • REQUIRES_NEW 会新开启事务,外层事务不会影响内部事务的提交/回滚
  • REQUIRES_NEW 的内部事务的异常,会影响外部事务的回滚

1.7.4 解决方法

去除MasterService.upload上面的@Transactional,数据源切换正常,虽然可以解决,但是事务无效。
BookService的save上面加@Transactional(propagation=Propagation.REQUIRES_NEW),数据源切换,且事务有效。完美解决。它会重新创建新事务,获取新的数据库连接,从而得到@DS的数据源

最终代码如下,只需要修改的是bookService,其他不变

@Service
@DS("Slave")
public class BookService extends ServiceImpl<BookMapper, Book> {
    @Resource
    private BookMapper bookMapper;
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void save(ReqDto reqDto) {
        bookMapper.save(reqDto);
    }
}

注意:

  • Slave数据库的操作,需要在master之后,这样当bookService.save失败,会使得userService回滚;
  • 如果Slave的操作在前,那当userService失败,无法使bookService回滚,这是因为Propagation.REQUIRES_NEW原因

所以会有调用顺序限制,那么使用@DSTransactional注解,就可以解决调用顺序和事务回滚问题,但是@DSTransactional只适用于单服务,对于分布式服务可以用springcloud里面的seata框架

1.8 分布式事务

点击了解分布式事务

1.9 自定义事务

1.9.1 Spring事务

Spring事务的定义包括:begin、commit、rollback、close、suspend、resume等动作。

  • begin(事务开始): 可以认为存在于数据库的命令中,比如Mysql的start transaction命令,但是在JDBC编程方式中不存在。
  • close(事务关闭):Spring事务的close()方法,是把Connection对象归还给数据库连接池,与事务无关。关闭一个数据库连接,这已经不再是事务的方法了
  • suspend(事务挂起):Spring中事务挂起的语义是:需要新事务时,将现有的Connection保存起来(还有尚未提交的事务),然后创建新的Connection2Connection2提交、回滚、关闭完毕后,再把Connection1取出来继续执行。
  • resume(事务恢复): 嵌套事务执行完毕,返回上层事务重新绑定连接对象到事务管理器的过程。

实际上,只有commit、rollback、close是在JDBC真实存在的,而其他动作都是应用的语意,而非JDBC事务的真实命令。因此,事务真实存在的方法是:setAutoCommit()commit()rollback()

1.9.2 自定义管理事务

为了保证在多个数据源中事务的一致性,我们可以手动管理Connetion的事务提交和回滚。考虑到不同ORM框架的事务管理实现差异,要求实现自定义事务管理不影响框架层的事务。

这可以通过使用装饰器设计模式,对Connection进行包装重写commit和rolllback屏蔽其默认行为,这样就不会影响到原生Connection和ORM框架的默认事务行为。其整体思路如下图所示:

图片

1.9.3 定义多事务注解

@Target({ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
public @interface MultiTransaction {  
    String transactionManager() default "multiTransactionManager";  
    // 默认数据隔离级别,随数据库本身默认值  
    IsolationLevel isolationLevel() default IsolationLevel.DEFAULT;  
    // 默认为主库数据源  
    String datasourceId() default "default";  
    // 只读事务,若有更新操作会抛出异常  
    boolean readOnly() default false;  

业务方法只需使用该注解即可开启事务,datasourceId指定事务用到的数据源,不指定默认为主库。

1.9.4 包装Connection

自定义事务我们使用包装过的Connection,屏蔽其中的commit&close方法。这样我们就可以在主事务里进行统一的事务提交和回滚操作。

public class ConnectionProxy implements Connection {  
   
    private final Connection connection;  
   
    public ConnectionProxy(Connection connection) {  
        this.connection = connection;  
    }  
   
    @Override  
    public void commit() throws SQLException {  
        // connection.commit();  
    }  
   
    public void realCommit() throws SQLException {  
        connection.commit();  
    }  
   
    @Override  
    public void close() throws SQLException {  
        //connection.close();  
    }  
   
    public void realClose() throws SQLException {  
        if (!connection.getAutoCommit()) {  
            connection.setAutoCommit(true);  
        }  
        connection.close();  
    }  
   
    @Override  
    public void rollback() throws SQLException {  
        if(!connection.isClosed())  
            connection.rollback();  
    }  
    ...  
}  

这里commit&close方法不执行操作,rollback执行的前提是连接执行close才生效。这样不管是使用哪个ORM框架,其自身事务管理都将失效。事务的控制就交由MultiTransaction控制了。

1.9.4 事务上下文管理

public class TransactionHolder {  
    // 是否开启了一个MultiTransaction  
    private boolean isOpen;  
    // 是否只读事务  
    private boolean readOnly;  
    // 事务隔离级别  
    private IsolationLevel isolationLevel;  
    // 维护当前线程事务ID和连接关系  
    private ConcurrentHashMap<String, ConnectionProxy> connectionMap;  
    // 事务执行栈  
    private Stack<String> executeStack;  
    // 数据源切换栈  
    private Stack<String> datasourceKeyStack;  
    // 主事务ID  
    private String mainTransactionId;  
    // 执行次数  
    private AtomicInteger transCount;  
   
    // 事务和数据源key关系  
    private ConcurrentHashMap<String, String> executeIdDatasourceKeyMap;  
   
}  

每开启一个事物,生成一个事务ID并绑定一个ConnectionProxy。事务嵌套调用,保存事务ID和lookupKey在栈中,当内层事务执行完毕执行pop。这样的话,外层事务只需在栈中执行peek即可获取事务ID和lookupKey。

1.9.5 数据源兼容处理

为了不影响原生事务的使用,需要重写javax.sql.DataSource下的getConnection方法。当前线程没有启动自定义事务,则直接从数据源中返回连接。

@Override  
public Connection getConnection() throws SQLException {  
    TransactionHolder transactionHolder = MultiTransactionManager.TRANSACTION_HOLDER_THREAD_LOCAL.get();  
    if (Objects.isNull(transactionHolder)) {  
        return determineTargetDataSource().getConnection();  
    }  
    ConnectionProxy ConnectionProxy = transactionHolder.getConnectionMap()  
            .get(transactionHolder.getExecuteStack().peek());  
    if (ConnectionProxy == null) {  
        // 没开跨库事务,直接返回  
        return determineTargetDataSource().getConnection();  
    } else {  
        transactionHolder.addCount();  
        // 开了跨库事务,从当前线程中拿包装过的Connection  
        return ConnectionProxy;  
    }  
}  

1.9.6 切面处理

切面处理的核心逻辑是:维护一个嵌套事务栈,当业务方法执行结束,或者发生异常时,判断当前栈顶事务ID是否为主事务ID。如果是的话这时候已经到了最外层事务,这时才执行提交和回滚。详细流程如下图所示:

图片

 具体代码示例:

package com.github.mtxn.transaction.aop;  
@Aspect  
@Component  
@Slf4j  
@Order(99999)  
public class MultiTransactionAop {  
   
    @Pointcut("@annotation(com.github.mtxn.transaction.annotation.MultiTransaction)")  
    public void pointcut() {  
        if (log.isDebugEnabled()) {  
            log.debug("start in transaction pointcut...");  
        }  
    }  
   
   
    @Around("pointcut()")  
    public Object aroundTransaction(ProceedingJoinPoint point) throws Throwable {  
        MethodSignature signature = (MethodSignature) point.getSignature();  
        // 从切面中获取当前方法  
        Method method = signature.getMethod();  
        MultiTransaction multiTransaction = method.getAnnotation(MultiTransaction.class);  
        if (multiTransaction == null) {  
            return point.proceed();  
        }  
        IsolationLevel isolationLevel = multiTransaction.isolationLevel();  
        boolean readOnly = multiTransaction.readOnly();  
        String prevKey = DataSourceContextHolder.getKey();  
        MultiTransactionManager multiTransactionManager = Application.resolve(multiTransaction.transactionManager());  
        // 切数据源,如果失败使用默认库  
        if (multiTransactionManager.switchDataSource(point, signature, multiTransaction)) return point.proceed();  
        // 开启事务栈  
        TransactionHolder transactionHolder = multiTransactionManager.startTransaction(prevKey, isolationLevel, readOnly, multiTransactionManager);  
        Object proceed;  
   
        try {  
            proceed = point.proceed();  
            multiTransactionManager.commit();  
        } catch (Throwable ex) {  
            log.error("execute method:{}#{},err:", method.getDeclaringClass(), method.getName(), ex);  
            multiTransactionManager.rollback();  
            throw ExceptionUtils.api(ex, "系统异常:%s", ex.getMessage());  
        } finally {  
            // 当前事务结束出栈  
            String transId = multiTransactionManager.getTrans().getExecuteStack().pop();  
            transactionHolder.getDatasourceKeyStack().pop();  
            // 恢复上一层事务  
            DataSourceContextHolder.setKey(transactionHolder.getDatasourceKeyStack().peek());  
            // 最后回到主事务,关闭此次事务  
            multiTransactionManager.close(transId);  
        }  
        return proceed;  
   
    }  
   
   
}  

注意:这种方式只适用于单体架构的应用。因为多个库的事务参与者都是运行在同一个JVM进行。如果是在微服务架构的应用中,则需要使用分布式事务管理(譬如:Seata)。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值