学一学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
相关推荐
程序猿麦小七41 分钟前
基于springboot的音乐网站的设计与实现(源码+lw+调试)
java·spring boot·后端·音乐网站
Ares-Wang3 小时前
ASP.NET Core 路由规则,自定义特性路由 ,IActionConstraint 路由约束 总结 mvc
后端·asp.net·mvc
暂时先用这个名字3 小时前
常见 HTTP 状态码分类和解释及服务端向前端返回响应时的最完整格式
前端·后端·网络协议·http·状态码·国产化·响应
幺零九零零6 小时前
【Golang】validator库的使用
开发语言·后端·golang
果粒陈爱写代码7 小时前
SpringBoot day 1105
java·spring boot·后端
GraduationDesign7 小时前
基于SpringBoot的免税商品优选购物商城的设计与实现
java·vue.js·spring boot·后端·html5
海绵宝宝de派小星8 小时前
Go:接口和反射
开发语言·后端·golang
techdashen8 小时前
Go Modules和 雅典项目
开发语言·后端·golang
杜杜的man8 小时前
【go从零单排】go三种结构体:for循环、if-else、switch
开发语言·后端·golang
->yjy8 小时前
springboot - 定时任务
java·spring boot·后端