学一学Spring中的@Transactional注解【中】【事务传播特性】

前言


在开发过程中,我们常常会遇到这样的问题:当你在处理复杂的业务逻辑时,如何确保每一个操作都能顺利完成,而不会因为一个小小的错误而引发一场"数据灾难"?这就像在一场盛大的舞会上,你需要确保每位舞者都能完美配合,才能让整个表演毫无瑕疵。

这时,事务传播特性便像一位神秘的指挥家,默默地在幕后调控着这一切。它们帮助我们管理事务的生命周期,无论是让某个方法加入现有事务,还是在需要时优雅地挂起当前事务,甚至是独立开创一段新的舞蹈。不同的传播特性就像是各种舞步,灵活多变、各具特色。


一、样例

java 复制代码
 package com.lazy.snail.service;
 ​
 import com.lazy.snail.dao.UserDao;
 import com.lazy.snail.domain.UserInfo;
 import com.lazy.snail.domain.UserLoginInfo;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 ​
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.sql.Timestamp;
 ​
 /**
  * @author lazysnail
  */
 @Service
 public class UserService {
     private final UserDao userDao;
 ​
     private  final UserLoginService userLoginService;
 ​
     public UserService(UserDao userDao, UserLoginService userLoginService) {
         this.userDao = userDao;
         this.userLoginService = userLoginService;
     }
 ​
 ​
 ​
     @Transactional(rollbackFor = Exception.class)
     public void createUser(UserInfo user) throws UnknownHostException {
         userDao.save(user);
 ​
         UserLoginInfo userLoginInfo = new UserLoginInfo();
         userLoginInfo.setId(user.getUserId());
         userLoginInfo.setUserId(user.getUserId());
         userLoginInfo.setIp(InetAddress.getLocalHost().getHostAddress());
         userLoginInfo.setMac("");
         userLoginInfo.setLoginTime(new Timestamp(System.currentTimeMillis()));
 ​
         userLoginService.saveUserLoginInfo(userLoginInfo);
     }
 }
java 复制代码
 package com.lazy.snail.service;
 ​
 import com.lazy.snail.dao.UserLoginDao;
 import com.lazy.snail.domain.UserLoginInfo;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 ​
 /**
  * @ClassName UserLoginService
  * @Description TODO
  * @Author lazysnail
  * @Date 2024/11/1 10:43
  * @Version 1.0
  */
 @Service
 public class UserLoginService {
     private final UserLoginDao userLoginDao;
 ​
     public UserLoginService(UserLoginDao userLoginDao) {
         this.userLoginDao = userLoginDao;
     }
 ​
     @Transactional(rollbackFor = Exception.class)
     public void saveUserLoginInfo(UserLoginInfo userLoginInfo) {
         userLoginDao.save(userLoginInfo);
     }
 }

二、事务传播特性

2.1PROPAGATION_REQUIRED

支持当前事务;如果不存在则创建事务

  • createUser和saveUserLoginInfo都有@Transactional注解

    1. createUser创建新事务tx1
    2. 由于存在tx1吗,saveUserLoginInfo不会创建新的事务,使用tx1
    3. saveUserLoginInfo出现异常,因在同一个事务,均回滚
less 复制代码
 /**
  * saveUserLoginInfo拦截
  */
 // AbstractPlatformTransactionManager
 public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
             throws TransactionException {
 ​
     // 省略部分代码...
 ​
     if (isExistingTransaction(transaction)) {
         // 已存在事务,handleExistingTransaction中没有对PROPAGATION_REQUIRED进行特殊处理
         return handleExistingTransaction(def, transaction, debugEnabled);
     }
 ​
     // 省略部分代码...
 }
  • createUser无@Transactional注解;saveUserLoginInfo有@Transactional注解

    1. createUser中的DML运行完就提交了
    2. saveUserLoginInfo会创建事务tx
    3. saveUserLoginInfo出现异常,不会回滚createUser中的DML
scala 复制代码
 /**
  * saveUserLoginInfo拦截
  */
 // AbstractPlatformTransactionManager
 public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
             throws TransactionException {
 ​
     // 省略部分代码...
     
     // PROPAGATION_REQUIRED的处理
     if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                 def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
                 def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
         SuspendedResourcesHolder suspendedResources = suspend(null);
         if (debugEnabled) {
             logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
         }
         try {
             // 创建新事务tx
             return startTransaction(def, transaction, debugEnabled, suspendedResources);
         }
         catch (RuntimeException | Error ex) {
             resume(null, suspendedResources);
             throw ex;
         }
     }
 ​
     // 省略部分代码...
 }
  • createUser有@Transactional注解;saveUserLoginInfo无@Transactional注解

    1. createUser拦截后创建新事务tx
    2. saveUserLoginInfo中DML以非事务形式执行
    3. saveUserLoginInfo对于createUser只是一个普通方法
java 复制代码
 // MethodProxy
 public Object invoke(Object obj, Object[] args) throws Throwable {
     try {
         init();
         FastClassInfo fci = fastClassInfo;
         // 目标方法调用 createUser
         // 由于saveUserLoginInfo方法不会被拦截,createUser方法后直接运行了saveUserLoginInfo,没有任何关于事务的操作
         return fci.f1.invoke(fci.i1, obj, args);
     } catch (InvocationTargetException ex) {
         throw ex.getTargetException();
     } catch (IllegalArgumentException ex) {
         if (fastClassInfo.i1 < 0)
             throw new IllegalArgumentException("Protected method: " + sig1);
         throw ex;
     }
 }

2.2PROPAGATION_SUPPORTS

支持当前事务;如果不存在则按照非事务执行

kotlin 复制代码
 package com.lazy.snail.service;
 ​
 import com.lazy.snail.dao.UserLoginDao;
 import com.lazy.snail.domain.UserLoginInfo;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 ​
 /**
  * @ClassName UserLoginService
  * @Description TODO
  * @Author lazysnail
  * @Date 2024/11/1 10:43
  * @Version 1.0
  */
 @Service
 public class UserLoginService {
     private final UserLoginDao userLoginDao;
 ​
     public UserLoginService(UserLoginDao userLoginDao) {
         this.userLoginDao = userLoginDao;
     }
     
     // 传播特性指定为SUPPORTS
     @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
     public void saveUserLoginInfo(UserLoginInfo userLoginInfo) {
         userLoginDao.save(userLoginInfo);
     }
 }
  • createUser(Propagation.REQUIRED) && saveUserLoginInfo(Propagation.SUPPORTS)

    1. createUser创建了新事务tx1
    2. saveUserLoginInfo使用tx1
less 复制代码
 /**
  * saveUserLoginInfo拦截
  */
 // AbstractPlatformTransactionManager
 public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
             throws TransactionException {
 ​
     // 省略部分代码...
 ​
     if (isExistingTransaction(transaction)) {
         // 已存在事务,handleExistingTransaction中没有对PROPAGATION_SUPPORTS进行特殊处理
         return handleExistingTransaction(def, transaction, debugEnabled);
     }
 ​
     // 省略部分代码...
 }
  • createUser没有@Transactional && saveUserLoginInfo(Propagation.SUPPORTS)

    1. createUser中的DML以非事务形式运行
    2. saveUserLoginInfo被拦截
    3. saveUserLoginInfo中运行两次userLoginDao.save(userLoginInfo),第二次主键冲突异常失败,第一次被提交至数据库
    4. saveUserLoginInfo中以非事务形式运行
scala 复制代码
 /**
  * saveUserLoginInfo拦截
  */
 // AbstractPlatformTransactionManager
 public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
             throws TransactionException {
 ​
     // 省略部分代码...
     
     // PROPAGATION_SUPPORTS满足以下条件,不会创建新事务
     if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                 def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
                 def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
         SuspendedResourcesHolder suspendedResources = suspend(null);
         if (debugEnabled) {
             logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
         }
         try {
             // 创建开启新事务tx
             return startTransaction(def, transaction, debugEnabled, suspendedResources);
         }
         catch (RuntimeException | Error ex) {
             resume(null, suspendedResources);
             throw ex;
         }
     }
 ​
     // 省略部分代码...
 }

2.3PROPAGATION_MANDATORY

支持当前事务;如果不存在则抛出异常

  • createUser没有@Transactional && saveUserLoginInfo(Propagation.MANDATORY)

    1. saveUserLoginInfo被拦截后发现没有事务,直接抛出异常
less 复制代码
 // AbstractPlatformTransactionManager
 public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
             throws TransactionException {
     if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
         throw new IllegalTransactionStateException(
                 "No existing transaction found for transaction marked with propagation 'mandatory'");
     }
 }

2.4PROPAGATION_REQUIRES_NEW

创建新事务,如果存在事务则挂起

  • createUser有@Transactional && saveUserLoginInfo(Propagation.REQUIRES_NEW)

    1. saveUserLoginInfo被拦截后发现有事务,挂起之前的事务

      • 获取当前事务对象,包含事务的相关信息
      • 修改事务的激活状态
      • 保存当前事务的状态,如事务的传播特性、隔离级别等,便于后续能恢复到当前状态
      • 从当前线程的上下文移除事务相关的信息
    2. 创建新的事务

    3. 新事务完成后,恢复挂起事务

      • 从之前保存的状态中恢复事务的状态信息
      • 更新事务的激活状态
      • 恢复之前的上下文状态
scss 复制代码
 // AbstractPlatformTransactionManager
 private TransactionStatus handleExistingTransaction(
             TransactionDefinition definition, Object transaction, boolean debugEnabled)
             throws TransactionException {
     if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
         // 挂起事务
         SuspendedResourcesHolder suspendedResources = suspend(transaction);
         try {
             // 开启新的事务
             return startTransaction(definition, transaction, debugEnabled, suspendedResources);
         } catch (RuntimeException | Error beginEx) {
             resumeAfterBeginException(transaction, suspendedResources, beginEx);
             throw beginEx;
         }
     }
 }
 ​
 /**
  * 新事物正常结束提交时,恢复之前的事务
  */
 private void cleanupAfterCompletion(DefaultTransactionStatus status) {
     status.setCompleted();
     if (status.isNewSynchronization()) {
         TransactionSynchronizationManager.clear();
     }
     if (status.isNewTransaction()) {
         doCleanupAfterCompletion(status.getTransaction());
     }
     if (status.getSuspendedResources() != null) {
         if (status.isDebug()) {
             logger.debug("Resuming suspended transaction after completion of inner transaction");
         }
         Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
         // 恢复事务
         resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
     }
 }

2.5PROPAGATION_NOT_SUPPORTED

不支持当前事务;始终以非事务形式执行

  • createUser有@Transactional && saveUserLoginInfo(Propagation.NOT_SUPPORTED)

    1. saveUserLoginInfo被拦截后,挂起createUser的事务
    2. saveUserLoginInfo中的DML以非事务的形式执行
    3. 恢复createUser的事务
  • createUser没有@Transactional && saveUserLoginInfo(Propagation.NOT_SUPPORTED)

    1. saveUserLoginInfo中的DML以非事务的形式执行
java 复制代码
 // AbstractPlatformTransactionManager
 private TransactionStatus handleExistingTransaction(
             TransactionDefinition definition, Object transaction, boolean debugEnabled)
             throws TransactionException {
     if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
         if (debugEnabled) {
             logger.debug("Suspending current transaction");
         }
         // 挂起事务,并没有开启新的事务
         Object suspendedResources = suspend(transaction);
         boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
         return prepareTransactionStatus(
                 definition, null, false, newSynchronization, debugEnabled, suspendedResources);
     }
 }

2.6PROPAGATION_NEVER

不支持当前事务;如果存在事务抛出异常

  • createUser有@Transactional && saveUserLoginInfo(Propagation.NEVER)

    1. saveUserLoginInfo被拦截后,发现有活动的事务,直接抛出异常
  • createUser没有@Transactional && saveUserLoginInfo(Propagation.NEVER)

    1. saveUserLoginInfo被拦截后,发现没有活动的事务,以非事务形式执行DML
java 复制代码
 // AbstractPlatformTransactionManager
 private TransactionStatus handleExistingTransaction(
             TransactionDefinition definition, Object transaction, boolean debugEnabled)
             throws TransactionException {
     
     // 抛出异常
     if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
         throw new IllegalTransactionStateException(
                 "Existing transaction found for transaction marked with propagation 'never'");
     }
 }

2.7PROPAGATION_NESTED

如果存在事务,则以嵌套形式执行,否则像PROPAGATION_REQUIRED一样

  • createUser有@Transactional && saveUserLoginInfo(Propagation.NESTED)

    1. saveUserLoginInfo被拦截后,发现已存在活动事务,创建一个新的嵌套事务(创建一个保存点)
    2. 共享外部事务的数据库连接
    3. 嵌套事务的回滚提交独立于外部事务
  • createUser没有@Transactional && saveUserLoginInfo(Propagation.NESTED)

    1. saveUserLoginInfo被拦截后,发现没有活动事务
    2. 创建一个新事务,与PROPAGATION_REQUIRED相同
java 复制代码
 // AbstractPlatformTransactionManager
 // 存在活动事务时
 private TransactionStatus handleExistingTransaction(
             TransactionDefinition definition, Object transaction, boolean debugEnabled)
             throws TransactionException {
     if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
         if (!isNestedTransactionAllowed()) {
             throw new NestedTransactionNotSupportedException(
                     "Transaction manager does not allow nested transactions by default - " +
                     "specify 'nestedTransactionAllowed' property with value 'true'");
         }
         if (debugEnabled) {
             logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
         }
         if (useSavepointForNestedTransaction()) {
             // 创建保存点
             DefaultTransactionStatus status =
                     prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
             status.createAndHoldSavepoint();
             return status;
         } else {
             return startTransaction(definition, transaction, debugEnabled, null);
         }
     }   
 }
 ​
 public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
             throws TransactionException {
     // 没有活动事务时,PROPAGATION_NESTED与PROPAGATION_REQUIRED一样
     if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                 def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
                 def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
         SuspendedResourcesHolder suspendedResources = suspend(null);
         if (debugEnabled) {
             logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
         }
         try {
             return startTransaction(def, transaction, debugEnabled, suspendedResources);
         }
         catch (RuntimeException | Error ex) {
             resume(null, suspendedResources);
             throw ex;
         }
     }
 }
 ​

三、总结

3.1七大特性总结

传播特性 描述 适用场景
PROPAGATION_REQUIRED 加入现有事务或新建事务 最常用的特性
PROPAGATION_REQUIRES_NEW 总是新建事务,挂起当前事务 独立执行某些操作
PROPAGATION_NESTED 创建嵌套事务,依赖于保存点 复杂的业务逻辑,需要部分回滚
PROPAGATION_NOT_SUPPORTED 非事务方式执行,挂起当前事务 确保在没有事务上下文中执行
PROPAGATION_NEVER 非事务方式执行,若有事务则抛出异常 明确要求不使用事务
PROPAGATION_SUPPORTS 加入事务或非事务方式执行 灵活的环境,支持可选事务
PROPAGATION_MANDATORY 加入现有事务,若无事务则抛出异常 强制在事务上下文中运行

3.2事务挂起与嵌套事务的区别

特性 事务挂起 嵌套事务
目的 暂时中断当前事务的执行 在一个事务上下文中创建一个独立的事务
状态管理 保持挂起事务的状态,稍后恢复 使用保存点管理嵌套事务的状态
事务提交/回滚 挂起后,当前事务继续执行,之后可以恢复 嵌套事务可以独立提交或回滚,外部事务不受影响
传播特性 适用于 PROPAGATION_NOT_SUPPORTEDPROPAGATION_REQUIRES_NEW 使用 PROPAGATION_NESTED
相关推荐
夜半无声5 分钟前
spring boot 多数据源集成mysql、postgresql、phoenix、doris等
java·spring boot·后端
小猪佩奇TONY19 分钟前
Vulkan 学习(12)---- Vulkan pipeline 创建
java·学习·spring
编程小筑42 分钟前
TypeScript语言的软件工程
开发语言·后端·golang
2401_898410691 小时前
CSS语言的软件工程
开发语言·后端·golang
DevOpsDojo1 小时前
Bash语言的函数实现
开发语言·后端·golang
Upuping1 小时前
Servlet详解
java·后端·web
DevOpsDojo1 小时前
Bash语言的软件工程
开发语言·后端·golang
华年源码1 小时前
基于springboot的房屋租赁系统(源码+数据库+文档)
java·数据库·spring boot·后端·毕业设计·源码·springboot
云端 架构师1 小时前
Elixir语言的正则表达式
开发语言·后端·golang
2401_898410692 小时前
Bash语言的编程范式
开发语言·后端·golang