springboot-事务

springboot-事务

概述

事务:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;

事务的四大特性(ACID)

  1. 原子性(Atomicity) 事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做 。
  2. 一致性(Consistency) 事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。
  3. 隔离性(Isolation) 一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
  4. 持久性(Durability) 也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。

需要格外注意的是:事务能否生效,取决于数据库引擎是否支持事务,MySQL 的 InnoDB 引擎是支持事务的,但 MyISAM 就不支持

Spring支持两种方式的事务管理

编程式事务管理

什么是TransactionTemplate

TransactionTemplateSpring 事务管理 的编程式事务控制工具。 相比于 @Transactional(声明式事务),TransactionTemplate 让你 显式 编写事务逻辑,并提供了更精细的控制权,比如:

  • 动态决定是否回滚事务
  • 在同一方法中灵活地开启多个事务
  • 在运行时指定事务属性(隔离级别、传播行为等)

基本使用

  • execute(返回结果)

    csharp 复制代码
        /**
        * transactionTemplate
        **/
        @Autowired
        private TransactionTemplate transactionTemplate;
    ​
        /**
        * 事务测试
        **/
        public ApiResult<LoginVO> transactionTemplateTest1() {
            Boolean status = transactionTemplate.execute(s -> {
                // 添加角色
                iRoleGateway.save(new RoleDAO().setName("admin"));
                // 添加主键冲突的用户
                iUserGateway.save(new UserDAO().setEmail("smile").setId(1L));
    ​
                return true;
            });
    ​
            if (BooleanUtils.isTrue(status)) {
                return ApiResult.success(null);
            }
    ​
            return ApiResult.fail(null);
        }
    ​
        /**
        * 事务测试
        **/
        public ApiResult<LoginVO> transactionTemplateTest2() {
            Boolean status = transactionTemplate.execute(s -> {
                try {
                    // 添加角色
                    iRoleGateway.save(new RoleDAO().setName("admin"));
                    // 添加主键冲突的用户
                    iUserGateway.save(new UserDAO().setEmail("smile").setId(1L));
                } catch (Exception e) {
                    log.error(e.getMessage());
                    // 设置回滚
                    s.setRollbackOnly();
                }
    ​
                return true;
            });
    ​
            if (BooleanUtils.isTrue(status)) {
                return ApiResult.success(null);
            }
    ​
            return ApiResult.fail(null);
        }
  1. 必须 返回一个结果(可以是 null
  2. 如果抛出运行时异常,事务会自动回滚
  3. 如果是受检异常,必须需要手动调用 status.setRollbackOnly() 才能回滚
  • executeWithoutResult(无返回值)

    csharp 复制代码
    /**
    * transactionTemplate
    **/
    @Autowired
    private TransactionTemplate transactionTemplate;
    ​
    /**
    * 事务测试
    **/
    public ApiResult<LoginVO> transactionTemplateTest1() {
      transactionTemplate.executeWithoutResult(s -> {
          // 添加角色
          iRoleGateway.save(new RoleDAO().setName("admin"));
          // 添加主键冲突的用户
          iUserGateway.save(new UserDAO().setEmail("smile").setId(1L));
      });
    ​
      return ApiResult.success(null);
    }
    ​
    /**
    * 事务测试
    **/
    public ApiResult<LoginVO> transactionTemplateTest2() {
      transactionTemplate.executeWithoutResult(s -> {
          try {
              // 添加角色
              iRoleGateway.save(new RoleDAO().setName("admin"));
              // 添加主键冲突的用户
              iUserGateway.save(new UserDAO().setEmail("smile").setId(1L));
          } catch (Exception e) {
              log.error(e.getMessage());
              // 设置回滚
              s.setRollbackOnly();
          }
      });
    ​
      return ApiResult.success(null);
    }
  1. 适合只做事务操作、无需返回值的情况
  2. 如果抛出运行时异常,事务会自动回滚
  3. 如果是受检异常,必须需要手动调用 status.setRollbackOnly() 才能回滚

配置事务属性

TransactionTemplate 在创建时可以直接配置:

scss 复制代码
复制编辑
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
transactionTemplate.setTimeout(30); //单位:秒
transactionTemplate.setReadOnly(true);

常见配置说明:

属性 说明
隔离级别 影响事务中数据的可见性,常用 ISOLATION_READ_COMMITTEDISOLATION_REPEATABLE_READ
传播行为 决定事务方法嵌套时的事务管理方式,比如 PROPAGATION_REQUIRES_NEW 会新开一个事务
超时时间 超过时间未完成会回滚
只读事务 setReadOnly(true) 会提示底层数据库优化(但不保证一定优化)

整体方法

来源 方法 功能
TransactionTemplate 自身 <T> T execute(TransactionCallback<T> action) 执行事务并返回结果
TransactionTemplate 自身 void executeWithoutResult(Consumer<TransactionStatus> action) 执行事务无返回值
TransactionTemplate 自身 PlatformTransactionManager getTransactionManager() 获取事务管理器
TransactionTemplate 自身 void setTransactionManager(PlatformTransactionManager transactionManager) 设置事务管理器
TransactionTemplate 自身 void afterPropertiesSet() Spring 属性检查
DefaultTransactionDefinition void setPropagationBehavior(int propagationBehavior) 设置事务传播行为
DefaultTransactionDefinition void setIsolationLevel(int isolationLevel) 设置事务隔离级别
DefaultTransactionDefinition void setTimeout(int timeout) 设置超时
DefaultTransactionDefinition void setReadOnly(boolean readOnly) 设置只读事务
DefaultTransactionDefinition int getPropagationBehavior() 获取传播行为代码
DefaultTransactionDefinition String getPropagationBehaviorName() 获取传播行为名称
DefaultTransactionDefinition int getIsolationLevel() 获取隔离级别代码
DefaultTransactionDefinition String getIsolationLevelName() 获取隔离级别名称
DefaultTransactionDefinition boolean isReadOnly() 是否只读
DefaultTransactionDefinition int getTimeout() 获取超时时间
什么是TransactionManager

TransactionManager 事务管理的核心接口

定义了事务的 开启、提交、回滚 等核心操作,是 Spring 声明式事务(@Transactional)和编程式事务(TransactionTemplate)的底层实

核心方法

java 复制代码
public interface PlatformTransactionManager {
  // 开启事务
  TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
​
  // 提交事务
  void commit(TransactionStatus status) throws TransactionException;
​
  // 回滚事务
  void rollback(TransactionStatus status) throws TransactionException;
}

基础使用

scss 复制代码
  /**
  * transactionManager
  **/
  @Autowired
  private PlatformTransactionManager transactionManager;
​
  public ApiResult<LoginVO> transactionManager1() {
      // 开启事务
      TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
​
      try {
          // 添加角色
          iRoleGateway.save(new RoleDAO().setName("admin"));
          // 添加主键冲突的用户
          iUserGateway.save(new UserDAO().setEmail("smile").setId(1L));
          // 提交事务
          transactionManager.commit(status);
      } catch (Exception e) {
          // 设置回滚
          transactionManager.rollback(status);
      }
​
      return ApiResult.fail(null);
  }
  1. 运行时异常,不会自动回滚,需要你自己在 catch 里调用 rollback

  2. 受检异常:同样不会触发自动回滚,也必须手动调用 rollback

  3. 同一个 TransactionStatus 只能用一次,提交后不能再回滚。

    一定要保证 commit 和 rollback 只能执行一次 ,否则会抛 UnexpectedRollbackExceptionIllegalTransactionStateException

  4. 适用场景有限

    • 适合需要细粒度控制事务边界的情况,比如:

      • 跨多表、跨多个不同数据源时,想自己控制部分成功部分回滚。
      • 需要在事务中分阶段提交,而不是整个方法自动包裹。
    • 一般业务开发,还是用 @TransactionalTransactionTemplate

TransactionTemplate和TransactionManager的区别

TransactionTemplate和TransactionManager是Spring框架中用于管理事务的两个关键组件,它们有不同的职责和功能

  • TransactionTemplate:

    TransactionTemplate是Spring框架提供的一个高级事务管理器的辅助类。它简化了编写事务代码的过程,并提供了一种声明式的事务管理方式。TransactionTemplate封装了事务的开始、提交、回滚等操作,使得在代码中处理事务变得更加简单和直观。

    TransactionTemplate的主要功能包括:

    1. 提供编程式事务管理的支持,允许在代码中显式地控制事务的边界
    2. 处理事务的开始、提交、回滚等操作,隐藏了底层事务管理的复杂性
    3. 提供了异常处理和回退机制,可以根据需要进行事务回滚或提交
  • TransactionManager:

    TransactionManager是Spring框架的事务管理器接口,定义了事务管理的基本操作和功能。它是Spring框架与底层事务管理系统(如JDBC事务、JTA事务等)的适配器,通过与具体的事务管理器实现交互,实现对事务的管理和控制。

    TransactionManager的主要功能包括:

    1. 提供事务的开始、提交、回滚等操作的方法定义。
    2. 管理事务的隔离级别和传播行为,定义了事务的行为特性。
    3. 与底层事务管理系统进行交互,将具体的事务操作委托给底层事务管理器。

在Spring中,TransactionTemplate和TransactionManager通常一起使用。TransactionTemplate使用TransactionManager来执行底层的事务管理操作,通过TransactionTemplate可以更方便地进行事务控制和异常处理。

  • TransactionTemplate是一个高级事务管理器的辅助类,简化了编写事务代码的过程,提供了编程式事务管理的支持。
  • TransactionManager是Spring框架的事务管理器接口,定义了事务管理的基本操作和功能,与底层事务管理系统进行交互。
  • TransactionTemplate使用TransactionManager来执行底层的事务管理操作,提供了更方便的事务控制和异常处理方式。
声明式事务管理

推荐使用(代码侵入性最小) ,实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)。

事务注解属性描述
css 复制代码
noRollbackForClassName
value
transactionManager
label
propagation
isolation
timeout
timeoutString
readOnly
rollbackFor
rollbackForClassName
noRollbackFor

事务传播行为(propagation)

事务传播行为是为了解决业务层方法之间互相调用的事务问题

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行

事务传播行为类型 说明
PROPAGATION_REQUIRED(默认) 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

TransactionDefinition.PROPAGATION_REQUIRED

使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:

  • 如果外部方法没有开启事务的话,Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  • 如果外部方法开启事务并且被Propagation.REQUIRED的话,所有Propagation.REQUIRED修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。

使用场景一:在下面的代码中,aMethod() 没有加事务注解。当它调用 bMethod() 时,Spring 会为 bMethod() 单独开启一个事务 Tx1,并在执行结束后提交;接着调用 cMethod() 时,Spring 会为 cMethod() 开启另一个事务 Tx2,并在执行结束后提交。由于 aMethod() 本身不在事务中,即使 aMethod() 抛出了异常,也不会导致 bMethod()cMethod() 的事务回滚,bMethod()cMethod() 各自的事务是独立的,互不影响

less 复制代码
@Service
Class A {
    @Autowired
    B b;
    
    @Autowired
    C c;
    
    public void aMethod {
        //do something
        b.bMethod();
        c.cMethod();
        // 抛出异常
        throw new Runtime("回滚异常")
    }
}
@Service
Class B {
    @Transactional(propagation = Propagation.REQUIRED)
    public void bMethod {
       //do something
    }
}
​
@Service
Class C {
    @Transactional(propagation = Propagation.REQUIRED)
    public void cMethod {
       //do something
    }
}

使用场景二:如果我们下面的aMethod()bMethod()使用的都是PROPAGATION_REQUIRED传播行为的话,两者使用的就是同一个事务,只要其中一个方法回滚,整个事务均回滚。

java 复制代码
@Service
Class A {
    @Autowired
    B b;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void aMethod {
        //do something
        b.bMethod();
    }
}
@Service
Class B {
    @Transactional(propagation = Propagation.REQUIRED)
    public void bMethod {
       //do something
    }
}

2.TransactionDefinition.PROPAGATION_REQUIRES_NEW

创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

举个例子:如果我们下面的bMethod()使用PROPAGATION_REQUIRES_NEW事务传播行为修饰,aMethod还是用PROPAGATION_REQUIRED修饰的话。如果aMethod()发生异常回滚,bMethod()不会跟着回滚,因为 bMethod()开启了独立的事务。但是,如果 bMethod()抛出了未被捕获的异常并且这个异常满足事务回滚规则的话,aMethod()同样也会回滚,因为这个异常被 aMethod()的事务管理机制检测到了。

java 复制代码
@Service
Class A {
    @Autowired
    
    B b;
    @Transactional(propagation = Propagation.REQUIRED)
    public void aMethod {
        //do something
        b.bMethod();
    }
}
​
@Service
Class B {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void bMethod {
       //do something
    }
}

TransactionDefinition.PROPAGATION_NESTED:

如果当前存在事务,则创建一个事务作为当前事务的嵌套事务执行; 如果当前没有事务,就执行与TransactionDefinition.PROPAGATION_REQUIRED类似的操作。也就是说:

  • 在外部方法开启事务的情况下,在内部开启一个新的事务,作为嵌套事务存在。
  • 如果外部方法无事务,则单独开启一个事务,与 PROPAGATION_REQUIRED 类似。

TransactionDefinition.PROPAGATION_NESTED代表的嵌套事务以父子关系呈现,其核心理念是子事务不会独立提交,依赖于父事务,在父事务中运行;当父事务提交时,子事务也会随着提交,理所当然的,当父事务回滚时,子事务也会回滚;

TransactionDefinition.PROPAGATION_REQUIRES_NEW区别于:PROPAGATION_REQUIRES_NEW是独立事务,不依赖于外部事务,以平级关系呈现,执行完就会立即提交,与外部事务无关;

子事务也有自己的特性,可以独立进行回滚,不会引发父事务的回滚,但是前提是需要处理子事务的异常,避免异常被父事务感知导致外部事务回滚;

举个例子:

  • 如果 aMethod() 回滚的话,作为嵌套事务的bMethod()会回滚。

  • 如果 bMethod()回滚的话, aMethod()是否回滚,要看bMethod()的异常是否被处理:

    bMethod()的异常没有被处理,即bMethod()内部没有处理异常,且aMethod()也没有处理异常,那么aMethod()将感知异常致使整体回滚

java 复制代码
@Service
Class A {
    @Autowired
    B b;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void aMethod (){
        //do something
        b.bMethod();
    }
}
​
@Service
Class B {
    @Transactional(propagation = Propagation.NESTED)
    public void bMethod (){
       //do something and throw an exception
    }
}

bMethod()处理异常或aMethod()处理异常,aMethod()不会回滚。

java 复制代码
@Service
Class A {
    @Autowired
    B b;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void aMethod (){
        //do something
        try {
            b.bMethod();
        } catch (Exception e) {
            System.out.println("方法回滚");
        }
    }
}
​
@Service
Class B {
    @Transactional(propagation = Propagation.NESTED)
    public void bMethod {
       //do something and throw an exception
    }
}

TransactionDefinition.PROPAGATION_MANDATORY

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

这个使用的很少,就不举例子来说了。

若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚,这里不对照案例讲解了,使用的很少。

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

更多关于事务传播行为的内容请看这篇文章:《太难了~面试官让我结合案例讲讲自己对 Spring 事务传播行为的理解。》

事务隔离级别(isolation):
  • TransactionDefinition.ISOLATION_DEFAULT :使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别 Oracle 默认采用的 READ_COMMITTED 隔离级别.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED :最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • TransactionDefinition.ISOLATION_READ_COMMITTED : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • TransactionDefinition.ISOLATION_REPEATABLE_READ : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
超时(timeout)

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒,默认值为-1,这表示事务的超时时间取决于底层事务系统或者没有超时时间。

rollbackFor

指定 哪些异常类型会触发事务回滚

默认情况下:

  • Spring 只会在 运行时异常(RuntimeException)和 Error 时才回滚。
  • 对于 受检异常(Checked Exception) ,默认不会回滚。

如果业务场景要求某些 受检异常 也要回滚,就需要用 rollbackFor 来声明

示例 1:默认情况(RuntimeException 回滚)

typescript 复制代码
@Transactional
public void testDefaultRollback() {
    // ...
    throw new RuntimeException("运行时异常,默认回滚");
}

示例 2:指定受检异常回滚

java 复制代码
@Transactional(rollbackFor = Exception.class)
public void testCheckedRollback() throws Exception {
    // ...
    throw new Exception("受检异常,rollbackFor 指定后回滚");
}

示例 3:指定多个异常

csharp 复制代码
@Transactional(rollbackFor = {IOException.class, SQLException.class})
public void testMultiRollback() throws IOException, SQLException {
    // ...
    throw new IOException("触发回滚");
}

@Transactional 里和回滚相关的主要有两个:

  • rollbackFor:指定需要回滚的异常类型(类)。
  • rollbackForClassName:指定需要回滚的异常类型(类名字符串)。

另外还有对应的"不回滚"控制:

  • noRollbackFor:指定不回滚的异常类型(类)。
  • noRollbackForClassName:指定不回滚的异常类型(类名字符串)。

实际场景举例

  • 业务校验异常需要回滚
scala 复制代码
public class BusinessException extends Exception {}
​
@Transactional(rollbackFor = BusinessException.class)
public void updateOrder() throws BusinessException {
    // ...
    throw new BusinessException();
}

默认情况下 BusinessException 是受检异常不会触发回滚,这里加上 rollbackFor 后才能回滚,实际这里BusinessException,RuntimeException,Error都会导致事务回滚。

  • 忽略部分异常
csharp 复制代码
@Transactional(noRollbackFor = IllegalArgumentException.class)
public void process() {
    // ...
    throw new IllegalArgumentException("不回滚");
}
readOnly(默认为false)

readOnly = true 的主要目的是 性能优化和语义声明,告诉底层数据库和事务管理器:

  • 这个事务中只会有查询操作,不会有写入操作(INSERT / UPDATE / DELETE)。
  • 事务管理器和数据库可以做相应优化。
Transactional的作用范围
  • 方法 :推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。
  • :如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。
  • 接口:不推荐在接口上使用。
事务失效的场景
  • 事务方法非public修饰

    由于Spring的事务是基于AOP的方式结合动态代理来实现的。因此事务方法一定要是public的,这样才能便于被Spring做事务的代理和增强

    在Spring内部也会有一个 org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource类,去检查事务方法的修饰符,当方法非public,会导致直接返回null

    less 复制代码
    @Nullable
    protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
        // Don't allow non-public methods, as configured.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
          return null;
        }
     }
  • 同类中非事务方法调用事务方法

    scss 复制代码
    @Service
    public class OrderService {
        
        public void createOrder(){
            // ... 准备订单数据
            
            // 生成订单并扣减库存
            insertOrderAndReduceStock();
        }
        
        @Transactional
        public void insertOrderAndReduceStock(){
            // 生成订单
            insertOrder();
            // 扣减库存
            reduceStock();
        }
    }

    可以看到,insertOrderAndReduceStock方法是一个事务方法,肯定会被Spring事务管理。Spring会给OrderService类生成一个动态代理对象,对insertOrderAndReduceStock方法做增加,实现事务效果。

    但是现在createOrder方法是一个非事务方法,在其中调用了insertOrderAndReduceStock方法,这个调用其实隐含了一个this.的前缀。也就是说,这里相当于是直接调用原始的OrderService中的普通方法,而非被Spring代理对象的代理方法。那事务肯定就失效

  • 事务方法的异常被捕获了

    异常被捕获了但是没有往外抛异常,所以事务没有发现方法中出现错误,所以也就没有回滚

    typescript 复制代码
     @Transactional
        public void createOrder(){
            // ... 准备订单数据
            // 生成订单
            insertOrder();
            // 扣减库存
            reduceStock();
        }
    ​
        private void reduceStock() {
            try {
                // ...扣库存
            } catch (Exception e) {
                // 处理异常
            }
        }

    在这段代码中,reduceStock方法内部直接捕获了Exception类型的异常,也就是说方法执行过程中即便出现了异常也不会向外抛出。

    而Spring的事务管理就是要感知业务方法的异常,当捕获到异常后才会回滚事务。

    现在事务被捕获,就会导致Spring无法感知事务异常,自然不会回滚,事务就失效了。

    less 复制代码
      @Nullable
      protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
          final InvocationCallback invocation) throws Throwable {
        ...
         TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
    ​
          Object retVal;
          try {
            // This is an around advice: Invoke the next interceptor in the chain.
            // This will normally result in a target object being invoked.
            retVal = invocation.proceedWithInvocation();
          }
          catch (Throwable ex) {
            // target invocation exception
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
          }
          finally {
            cleanupTransactionInfo(txInfo);
          }
      }
    ​
      // 如果目标方法 抛出异常,进入 catch。
      // 调用 completeTransactionAfterThrowing(...) 来决定是否回滚事务。
      //关键点:最后 throw ex; 继续把异常往外抛。这样 Spring 才会在方法退出时回滚
    ​
      protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
        if (txInfo != null && txInfo.getTransactionStatus() != null) {
          if (logger.isTraceEnabled()) {
            logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
                "] after exception: " + ex);
          }
          if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
            try {
              txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
            }
            catch (TransactionSystemException ex2) {
              logger.error("Application exception overridden by rollback exception", ex);
              ex2.initApplicationException(ex);
              throw ex2;
            }
            catch (RuntimeException | Error ex2) {
              logger.error("Application exception overridden by rollback exception", ex);
              throw ex2;
            }
          }
        }
  • 事务异常类型不对

    java 复制代码
    @Transactional(rollbackFor = RuntimeException.class)
    public void createOrder() throws IOException {
        // ... 准备订单数据
    ​
        // 生成订单
        insertOrder();
        // 扣减库存
        reduceStock();
    ​
        throw new IOException();
    }

    Spring的事务管理默认感知的异常类型是RuntimeException和Error,当事务方法内部抛出了一个IOException时,不会被Spring捕获,因此就不会触发事务回滚,事务就失效了。

    因此,当我们的业务中会抛出RuntimeException以外的异常时,应该通过@Transactional注解中的rollbackFor属性来指定异常类型:

  • 事务传播行为不对

  • 没有被Spring管理

    即当前类没有被SpringBoot扫描

  • 底层使用的数据库不支持事务机制

相关推荐
绝无仅有几秒前
常用 Kubernetes (K8s) 命令指南
后端·面试·github
long3168 分钟前
代理设计模式
java·学习·程序人生·设计模式·代理模式
bobz96512 分钟前
ovs 桥接了 bond0.1234, 链路层功能还在,但 IP 层功能无法使用
后端
渣哥19 分钟前
惊呆!Java深拷贝 vs 浅拷贝,区别竟然这么大!
java
用户27079129381820 分钟前
为什么在 Java 中字符串是不可变的?
java
似水流年流不尽思念21 分钟前
Spring Bean有哪些生命周期回调方法?有哪几种实现方式?
后端·spring·面试
Moonbit28 分钟前
提交即有奖!MGPIC 游戏赛道官方推荐框架上线,直播同步解读赛题。 MoonBit MoonBit
后端·微信·程序员
郝同学的测开笔记28 分钟前
打通回家之路:OpenVPN,你的企业网络万能钥匙(一)
运维·后端·测试
whitepure28 分钟前
万字详解Java代码块
java·后端
忘带键盘了41 分钟前
Dish、DishVO 和 DishDTO
java