相关推荐recommended
Spring控制事务回滚
作者:mmseoamin日期:2023-12-25

Spring控制事务回滚

  • 1、说明
  • 2、声明式性事务@Transacational
  • 3、@Transactional不适用场景
  • 4、@Transactional注解事务失效的几种场景及原因
    • 4.1、数据库引擎不支持事务
    • 4.2、添加事务的类没有被Spring管理
    • 4.3、@Transactional作用的方法不是public修饰的
    • 4.4、@Transactional的rollbackFor属性设置错误
    • 4.5、@Transactional的propagation属性设置错误
    • 4.6、调用同类的方法,事务失效
    • 4.7、异常被捕获,事务无法回滚
    • 5、spring事务控制手动回滚
    • 6、AOP配置全局事务管理

      1、说明

      1、Spring中开启事务的方式主要有两种:编程式事务和声明式事务。

      2、事务是我们开发过程中经常会使用到的,为了在业务执行过程中出现异常时,回滚到原始状态。而事务的回滚在大多数情况下都是靠着 exception(异常)来触发回滚的,当事务机制捕捉到异常,就会开始回滚。

      3、但往往也会出现情况:在业务代码中,需要对异常单独进行处理,异常不会抛出,但需要事务回滚的情况,这个时候就需要手动调用回滚

      2、声明式性事务@Transacational

      @Transactional 声明式事务,是开发过程中最常用的开启事务的方式。

      也可以通过切面,对整个业务层的方法进行事务控制

      优点:使用方便,而且对代码的侵入性低

      这种方式,默认是通过 捕获 RunTimeException 来触发事务回滚,只要是 RuntimeException 以及 它的子类,都可以触发事务回滚。也可以修改触发事务回滚的异常范围,可以通过修改 @Transactional 中的 rollbackFor 属性,修改异常范围。比如:

      @Transactional(rollbackFor = Exception.class),修改完成后,只要是 Exception 类的子类,都可以触发事务回滚

      3、@Transactional不适用场景

      有时候需要对异常进行特殊处理,异常被捕获无法抛出时,声明式事务就失效不可用。

      示例:抛出异常被捕获,无法触发事务回滚。

          //业务代码
          try{ 
              //业务处理出现异常
          } catch (Exception e) {
              // 捕获异常,打印异常,或其他处理。但不抛出新的异常
              e.printStackTrace();
              //可以将捕获后的异常,封装为新的业务异常抛出(正常回滚)
              throw new xxxxException(e.getMessage);
          }
          //此时return语句能够执行
          return  xxx;
      

      此处,可以将捕获后的异常,封装为新的业务异常抛出

      4、@Transactional注解事务失效的几种场景及原因

      在开发过程中,我们需要同时进行多个对数据的操作,这时候需要使用事务去控制多个操作的一致性。

      通过声明式事务**@Transactional** 注解修饰方法的形式开启事务,需要注意到以下这些情况会出现事务失效。

      4.1、数据库引擎不支持事务

      例如我们使用Mysql数据,在 5.5 版本之前,Mysql 默认的数据引擎都是 MyISAM 的,而 MyISAM 是不支持事务的;在 5.5 版本之后,默认的数据引擎是 InnoDB 的,它是支持事务的。

      Spring 对事务的管理实现又是基于数据库的,所以当数据库引擎不支持事务的时候,自然就不会有起作用的事务机制了,所有我们在选择数据库的时候,需要去选择支持事务的数据引擎。

      4.2、添加事务的类没有被Spring管理

      当添加了注解或者全局事务配置了路径,但是需要用到事务的方法所在的类没有注入到Spring容器中,这样事务也不会生效,通常我们都是添加到业务逻辑处理层,通常都是添加@Service,将当前类注入到Spring中。

      4.3、@Transactional作用的方法不是public修饰的

      @Transactional注解应用在非public修饰的方法上,Transactional将会失效。这是因为在Spring AOP代理时,事务拦截器只能拦截public方法,而非public方法将直接被执行,不会经过代理。因此,建议将@Transactional注解只应用在public方法上。

      4.4、@Transactional的rollbackFor属性设置错误

      rollbackFor是@Transactional注解的一个属性,用于指定能够触发事务回滚的异常类型。Spring默认只在遇到未检查unchecked异常(继承自RuntimeException的异常)或者Error时才回滚事务;其他异常不会触发回滚事务。如果想让其他异常也能触发回滚事务,可以在@Transactional注解中加上rollbackFor属性,设置Exception异常就回滚,这样不管是Exception还是RuntimeException,spring都能帮助我们去回滚数据了。

      示例:当抛出Exception异常时,想要触发事务回滚,就要设置@Transactional(rollbackFor = Exception.class)

          @Transactional(rollbackFor = Exception.class)
          public void test() {
              // 业务代码
              if(1 == 1) {
                  throw new Exception("Exception 异常");
              }
          }
      

      4.5、@Transactional的propagation属性设置错误

      设置 @Transactional(propagation = xxxx)可以配置 Spring 的事务传播机制的,当事务传播机制设置为不支持事务是,事务也是不会生效的。propagation参数指定了事务的传播行为,即在一个事务方法中调用另一个事务方法时,两个事务如何关联。

      示例:当设置为 propagation = Propagation.NOT_SUPPORTED 时,表示不以事务运行,当前若存在事务则挂起。

          @Transactional(propagation = Propagation.NOT_SUPPORTED)
          public void test1() {
              // 业务代码
          }
      

      propagation有以下几种取值:

      • REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
      • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
      • MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
      • REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
      • NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
      • NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
      • NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则与REQUIRED类似,创建一个新的事务。

        propagation的默认值是REQUIRED。

      4.6、调用同类的方法,事务失效

      调用本类方法导致传播行为失效,同一个 Service 的两个方法之间调用,就会出现这个问题,原因还是在代理对象这里,我们期待的调用是一个代理类的调用,但是我们若是直接在方法中内部调用,不好意思,被调用的方法的事务失效,没有被 AOP 增强。

      **示例:**方法addStudents调用本类中的方法addStudent

      @Service
      public class StudentService {
          @Transactional
          public void addStudent(Student student) {
              // do something
          }
          public void addStudents(List students) {
              for (Student student : students) {
                  // 这里直接调用同类的方法,事务不会生效
                  addStudent(student);
              }
          }
      }
      

      解决方案一:自己调用自己,自己注入自己。你可能看见过这样的代码,就是为了解决这个问题的。这种解决方案最常见。

          @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES)
          public void a (){
              service.b();
          }
      

      方案二:还有一种方法可以在同类调用的方法上,通过AOP代理调用,即使用AopContext.currentProxy()方法获取当前类的代理对象,然后通过代理对象调用目标方法,这样就可以执行事务切面,进行事务增强。

      @Service
      public class StudentService {
          @Transactional
          public void addStudent(Student student) {
              // do something
          }
          public void addStudents(List students) {
              for (Student student : students) {
                  // 这里通过代理对象调用同类的方法,事务会生效
                  ((StudentService)AopContext.currentProxy()).addStudent(student);
              }
          }
      }
      

      方案三:在同类调用的方法上,使用@Async注解,这样就可以在异步线程中执行目标方法,而不是在当前线程中执行,这样就可以避免事务失效的问题。

      @Service
      public class StudentService {
          @Transactional
          @Async
          public void addStudent(Student student) {
              // do something
          }
          public void addStudents(List students) {
              for (Student student : students) {
                  // 这里在异步线程中调用同类的方法,事务会生效
                  addStudent(student);
              }
          }
      }
      

      4.7、异常被捕获,事务无法回滚

      事务机制的回滚,是 通过异常来触发事务回滚的。在开发过程中,会出现异常被捕获处理了,而且没有再抛出新的异常,就会导致异常丢失,无法触发回滚。

      **示例1:**不会触发回滚的情况

          @Transactional
          public void test1() {
              // 演示直接抛出异常被捕获
              try {
                  throw new  RuntimeException();
              } catch (RuntimeException e) {
                  // 不做处理,虽然有异常,但异常丢失,无法触发事务回滚
              }
          }
      

      **示例2:**catch 捕获异常后,再抛出

          @Transactional
          public void test1() {
              // test code
              try {
                  throw new  RuntimeException();
              } catch (RuntimeException e) {
                  // 不做处理,虽然有异常,但异常丢失,无法触发事务回滚
                  throw e;
              }
          }
      

      5、spring事务控制手动回滚

      Spring事务控制手动回滚是指在某些情况下,我们需要主动触发事务的回滚,而不是依赖于Spring的默认回滚策略。Spring的默认回滚策略是对非检查型异常和运行时异常进行事务回滚,而对检查型异常则不进行回滚操作。有两种常用的方法可以实现手动回滚:

      • 手动抛出一个运行时异常,例如throw new RuntimeException(),这样Spring就会捕获到这个异常,并执行事务回滚。
      • 通过Spring提供的事务切面支持类TransactionAspectSupport,调用其currentTransactionStatus().setRollbackOnly()方法,这样就可以标记当前事务为回滚状态,而不需要抛出异常。

        使用手动回滚的方法时,需要注意以下几点:

        • 被回滚的方法必须使用@Transactional注解,否则无法开启事务管理,也就无法回滚。
        • 被回滚的方法不能在同一个类中直接调用,否则无法走代理类,也就无法回滚,需要通过AOP代理调用或者注入自身的bean来调用。
        • 被回滚的方法不能使用try catch来捕获异常,否则无法触发回滚,需要在方法签名上声明throws或者在catch块中重新抛出异常。

      **示例:**手动回滚事务

          //假设这是一个service类的片段
          try{ 
              //出现异常
          } catch (Exception e) {
              // 捕获异常,打印异常,或其他处理。但不抛出新的异常
              e.printStackTrace();
              // 手动回滚
              TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
          }
          //此时return语句能够执行
          return  xxx;
      

      6、AOP配置全局事务管理

      示例:

      package com.gd.gd_service.config;
      import org.aspectj.lang.annotation.Aspect;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.aop.Advisor;
      import org.springframework.aop.aspectj.AspectJExpressionPointcut;
      import org.springframework.aop.support.DefaultPointcutAdvisor;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.transaction.TransactionDefinition;
      import org.springframework.transaction.TransactionManager;
      import org.springframework.transaction.interceptor.*;
      import java.util.Collections;
      import java.util.HashMap;
      import java.util.Map;
      /**
       * @Auther: hippoDocker
       * @Date:2021年12月21日20:51:27
       * @Description: TODO 全局事务配置
       */
      @Configuration
      @Aspect
      public class TransactionManagerConfig {
          private static final Logger logger = LoggerFactory.getLogger(TransactionManagerConfig.class);
          /**
           * 配置方法过期时间,默认-1,永不超时,单位秒
           */
          @Value("${spring.application.transactiontimeout}")
          private int AOP_TIME_OUT;
          /**
           * 配置切入点表达式 : 指定哪些包中的类使用事务
           */
          private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.gd.gd_service.service.impl.*.*(..)))";
          //事务管理器
          @Autowired
          private TransactionManager transactionManager;
          /**
           * 全局事务配置
           * REQUIRED :如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
           * SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
           * MANDATORY :如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
           * REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。
           * NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起。
           * NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。
           * NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED 。
           * 指定方法:通过使用 propagation 属性设置,例如:@Transactional(propagation = Propagation.REQUIRED)
           */
          @Bean
          public TransactionInterceptor txAdvice(){
              /**
               * 配置事务管理规则
               * 这里配置只读事务
               * 查询方法, 只读事务,不做更新操作
               */
              RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
              readOnlyTx.setReadOnly(true);
              readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
              /**
               * 增、删、改 需要的事务
               * 必须带事务
               * 当前存在事务就使用当前事务,当前不存在事务,就开启一个新的事务
               */
              RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
              // 设置回滚规则:什么异常都需要回滚
              requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
              // 当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务
              requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
              // 设置超时时间,超时则抛出异常回滚
              requiredTx.setTimeout(AOP_TIME_OUT);
              System.out.println("=====>>事务超时时间:"+requiredTx.getTimeout());
              /**
               * 无事务地执行,挂起任何存在的事务
               */
              RuleBasedTransactionAttribute noTx = new RuleBasedTransactionAttribute();
              noTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
              /**
               * 设置方法对应的事务
               */
              Map methodMap = new HashMap<>();
              // 可以提及事务或回滚事务的方法
              //只读事务
              methodMap.put("get*", readOnlyTx);
              methodMap.put("query*", readOnlyTx);
              methodMap.put("find*", readOnlyTx);
              methodMap.put("list*", readOnlyTx);
              methodMap.put("count*", readOnlyTx);
              methodMap.put("exist*", readOnlyTx);
              methodMap.put("search*", readOnlyTx);
              methodMap.put("fetch*", readOnlyTx);
              //写事务
              methodMap.put("add*", requiredTx);
              methodMap.put("save*", requiredTx);
              methodMap.put("insert*", requiredTx);
              methodMap.put("update*", requiredTx);
              methodMap.put("modify*", requiredTx);
              methodMap.put("delete*", requiredTx);
              methodMap.put("creat*", requiredTx);
              methodMap.put("edit*", requiredTx);
              methodMap.put("remove*", requiredTx);
              methodMap.put("repair*", requiredTx);
              methodMap.put("binding*", requiredTx);
              methodMap.put("clean*", requiredTx);
              methodMap.put("upload*",requiredTx);
              //无事务
              methodMap.put("noTx*", noTx);
              // 其他方法无事务,只读
              methodMap.put("*", readOnlyTx);
              //声明一个通过方法名字配置事务属性的对象
              NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
              source.setNameMap(methodMap);
              //返回事务拦截器
              TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, source);
              return txAdvice;
          }
          @Bean(name = "txAdviceAdvisor")
          public Advisor txAdviceAdvisor(TransactionInterceptor txAdvice) {
              logger.info("=====>>创建txAdviceAdvisor");
              //配置事务切入点表达式
              AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
              pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
              //增强事务,关联切入点和事务属性
              return new DefaultPointcutAdvisor(pointcut, txAdvice);
          }
      }