Spring事务传播的7种方式:理论与实战探讨
引言
在开发过程中,对数据库数据的新增和修改都涉及到事务的管理。不同的数据库都提供了事务管理的功能,而当我们使用spring
项目进行开发时,一般不需要我们去过多的关心对数据库数据操作的事务管理,只需要使用spring
已经封装好的事务管理功能。
在spring
中,事务管理的方式有两种,分别为编程式事务管理(Programmatic Transaction Management)和声明式事务管理(Declarative Transaction Management)。
-
编程式事务管理
通过代码显式地管理事务,通常使用
TransactionTemplate
或PlatformTransactionManager
来开始、提交或回滚事务。 -
声明式事务管理
通过注解或 XML 配置声明事务,Spring 自动管理事务的开始、提交或回滚。最常用的是通过
@Transactional
注解来管理事务。
我们经常使用的便是后者@Transactional
注解的方式,只需要在方法上加上此注解,就可以保证方法中的数据新增或修改的事务一致性。当然也要留意事务失效的场景,在此就不多过多赘述,网上有很多相关的博客。据我个人经验,接触的一些项目中看到对@Transactional
的使用上,大多都是停留在标注上就完事的层面,并没有去进行一些相关的属性设置,当然这样在大多数场景下足够使用了。但是遇到一些复杂的场景,还是需要了解其中的一些属性设置。
例如:向数据库插入用户信息,先保存主信息,在保存扩展信息(使用的是MybatisPlus
)。addUserExt
方法如果需要单独使用,也加上了事务注解。那么调用adduser
方法,进入方法addUserExt
后当前操作的是处于哪个事务呢?
typescript
代码解读
复制代码
// 处于UserService中 @Transactional public void addUser(User user, UserExt userExt) { super.save(user); userExt.setUBh(user.getBh()); userExtService.addUserExt(userExt); } // 处于UserExtService中 @Transactional public void addUserExt(UserExt ext) { super.save(ext); // 忽略一些 其他数据库操作,例如记录日志 }
事务传播方式
所以这便涉及到事务的传播类型,即事务隔离级别propagation
。本文主要是探讨和实践一下不同的事务传播方式。
csharp
代码解读
复制代码
/**The transaction propagation type. * Defaults to Propagation. REQUIRED. * See Also: * org. springframework. transaction. interceptor. TransactionAttribute. getPropagationBehavior() */ Propagation propagation() default Propagation.REQUIRED;
通过查看源码发现,事务传播方式分为下面几种,我们对以下传播类型进行一一分析和实验。
scss
代码解读
复制代码
public enum Propagation { // 默认方式,支持当前事务,如果不存在,则创建一个新事务。 REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), // 支持当前事务,如果不存在,则执行非事务。 SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), // 支持当前事务;如果当前事务不存在,则抛出异常。 MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), // 创建一个新事务,并挂起当前事务(如果存在) REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), // 非事务性地执行,挂起当前事务(如果存在)。 NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), // 不支持当前事务;如果存在当前事务,则抛出异常。 NEVER(TransactionDefinition.PROPAGATION_NEVER), // 如果当前事务存在,则在嵌套事务中执行,否则像PROPAGATION_REQUIRED一样执行。 NESTED(TransactionDefinition.PROPAGATION_NESTED); }
PROPAGATION_REQUIRED
说明
默认方式,支持当前事务,如果不存在,则创建一个新事务。 上面的例子中,addUserExt
方法加了事务,在单独调用时当前不存在事务,则创建一个事务;在调用addUser
方法时创建了事务A,在进入方法addUserExt
后判断出当前已经存在事务便加入事务A,不在创建新事务。如下,下面两个方法分别在不同的service
中,分别记录日志获取当前事务名称。
typescript
代码解读
复制代码
@Transactional public void addUser(User user, UserExt userExt) { log.info("执行方法addUser当前事务:{}", TransactionSynchronizationManager.getCurrentTransactionName()); super.save(user); userExt.setUBh(user.getBh()); txBservice.addUserExt(userExt); } @Transactional public void addUserExt(UserExt ext) { log.info("执行方法addUserExt当前事务:{}", TransactionSynchronizationManager.getCurrentTransactionName()); super.save(ext); // 忽略一些 其他数据库操作,例如记录日志 }
测试
Test1,调用方法addUserExt
ini
代码解读
复制代码
@Test void testSpringTransaction() { UserExt ext = new UserExt("213412@com", "pujihowx", 180, 65); ext.setUBh(1); userExtService.addUserExt(ext); }
执行日志如下:
yaml
代码解读
复制代码
c.p.p.spring.transaction.UserExtService : 执行方法addUserExt当前事务:com.pujiho.practice.spring.transaction.UserService.addUserExt
Test2,调用方法addUser
ini
代码解读
复制代码
@Test void testSpringTransaction1() { User user = new User("pujiho", 18, "男"); UserExt ext = new UserExt("213412@com", "pujihowx", 180, 65); userService.addUser(user, ext); }
执行日志如下:
yaml
代码解读
复制代码
c.p.p.spring.transaction.UserService : 执行方法addUser当前事务:com.pujiho.practice.spring.transaction.UserService.addUser c.p.p.spring.transaction.UserExtService : 执行方法addUserExt当前事务:com.pujiho.practice.spring.transaction.UserService.addUser
从上面两次测试结果看,addUserExt
方法中打印的当前事务名称是不一样的,第二次为父级方法所创建的事务。由此可印证上述,当前不存在事务创建事务,存在事务则加入当前事务的结论。
我们再通过一个测试,加深对PROPAGATION_REQUIRED
的理解。如下,在外层方法捕获内层方法抛出的异常,最终事务执行的情况是怎么样的呢?
typescript
代码解读
复制代码
@Transactional public void addUser(User user, UserExt userExt) { super.save(user); userExt.setUBh(user.getBh()); try { userExtService.addUserExt(userExt); } catch (Exception e) { log.error("执行addUserExt方法出错", e); } } @Transactional public void addUserExt(UserExt ext) { log.info("执行方法addUserExt当前事务:{}", TransactionSynchronizationManager.getCurrentTransactionName()); super.save(ext); // 抛出异常 int i = 10/0; }
ini
代码解读
复制代码
@Test void testSpringTransaction1() { User user = new User("pujiho2", 18, "男"); UserExt ext = new UserExt("213412@com", "pujihowx", 180, 65); userService.addUser(user, ext); }
我摘取了一些重要的日志信息如下,首先外层和内存是同一个事务,内层方法执行出错并抛出异常;并且还抛出了一个UnexpectedRollbackException
的异常,说事务被回滚。
yaml
代码解读
复制代码
c.p.p.spring.transaction.UserService : 执行方法addUser当前事务:com.pujiho.practice.spring.transaction.UserService.addUser c.p.p.spring.transaction.UserExtService : 执行方法addUserExt当前事务:com.pujiho.practice.spring.transaction.UserService.addUser c.p.p.spring.transaction.UserService : 执行addUserExt方法出错 java.lang.ArithmeticException: / by zero ··· org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
也就是外层方法虽然捕获了异常,但是由于内层方法也使用了当前事务,在执行内层方法出现异常时事务就会回滚了。同样的,如果外层方法抛出异常,内层方法就算没有异常也会回滚。
结论
PROPAGATION_REQUIRED
是默认的事务传播方式,也是我们开发过程中使用最频繁的事务传播方式。特别是在事务嵌套、异常处理的一些场景下,我们能够推算出事务的走向,从而设计健壮的代码结构。
PROPAGATION_SUPPORTS
说明
如果当前存在事务,就加入当前事务,如果不存在,则执行非事务操作。 那这个不就和不加事务注解@Transactional
是一样的效果吗?因为正常情况下,如果当前存在事务,未加事务注解的方法也会加入当前事务。我们看看以下API的描述:
vbnet
代码解读
复制代码
Support a current transaction, execute non-transactionally if none exists. Analogous to EJB transaction attribute of the same name. Note: For transaction managers with transaction synchronization, SUPPORTS is slightly different from no transaction at all, as it defines a transaction scope that synchronization will apply for. As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) will be shared for the entire specified scope. Note that this depends on the actual synchronization configuration of the transaction manager. See Also: org. springframework. transaction. support. AbstractPlatformTransactionManager. setTransactionSynchronization
Note: 对于设置了 transaction synchronization 的事务管理器,两种方式有微小的不同,即使用@Transactional(propagation = Propagation.SUPPORTS)
方式会定义一个事务作用域,等等...。
PROPAGATION_SUPPORTS 不同于不使用事务注解的地方在于资源共享,即在支持事务同步的事务管理器中,PROPAGATION_SUPPORTS
定义了一个事务作用域,其中同步机制(如监听器或回调)可能会应用。因此,在同一作用域内的资源(如JDBC连接、Hibernate会话等)会被共享。然而,具体的行为取决于事务管理器的同步配置。
结论
PROPAGATION_SUPPORTS
提供了一种灵活的事务管理方式,但在使用时需要仔细考虑其对现有事务上下文的影响以及可能的资源共享和同步问题。正确配置事务管理器和使用适当的传播行为对于维护应用程序的稳定性和性能至关重要。所以一般业务开发情况下,这种传播类型使用的场景很少。
使用注意事项
- 谨慎使用:虽然
PROPAGATION_SUPPORTS
提供了灵活性,但在某些情况下可能会导致不可预见的行为,特别是在嵌套事务的情况下。 - 避免嵌套冲突:避免在
PROPAGATION_SUPPORTS
的作用域内使用PROPAGATION_REQUIRED
或PROPAGATION_REQUIRES_NEW
,因为这可能会导致运行时同步冲突。如果必须嵌套,确保正确配置事务管理器,比如切换到“在实际事务上同步”的模式。 - 资源共享和隔离性:在共享资源的情况下,要特别注意数据的隔离性和一致性。虽然资源是共享的,但不同的事务可能需要对数据有不同的修改,这可能导致冲突或数据不一致。
PROPAGATION_MANDATORY
说明
支持当前事务;如果当前事务不存在,则抛出异常。 也就是说,当前上下文中存在事务,就加入事务;不存在则抛出异常。相比于默认的传播方式,区别在于当前不存在事务时会抛出异常阻断流程,也就是强制此业务流程需要事务。
测试
首先修改例子中的addUserExt
方法的事务传播方式。
java
代码解读
复制代码
@Transactional(propagation = Propagation.MANDATORY) public void addUserExt(UserExt ext) { log.info("执行方法addUserExt当前事务:{}", TransactionSynchronizationManager.getCurrentTransactionName()); super.save(ext); }
Test1,调用方法addUserExt
ini
代码解读
复制代码
@Test void testSpringTransaction() { UserExt ext = new UserExt("213412@com", "pujihowx", 180, 65); ext.setUBh(1); userExtService.addUserExt(ext); }
执行日志报错,错误信息如下,翻译为“标记为“强制”的事务没有发现任何存在的事务”。
csharp
代码解读
复制代码
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
所以使用此事务传播方式,上下文中必须存在事务,我们再看看下面的测试。
Test2,调用方法addUser
ini
代码解读
复制代码
@Test void testSpringTransaction1() { User user = new User("pujiho", 18, "男"); UserExt ext = new UserExt("213412@com", "pujihowx", 180, 65); userService.addUser(user, ext); }
截取执行日志如下:
yaml
代码解读
复制代码
c.p.p.spring.transaction.UserService : 执行方法addUser当前事务:com.pujiho.practice.spring.transaction.UserService.addUser c.p.p.spring.transaction.UserExtService : 执行方法addUserExt当前事务:com.pujiho.practice.spring.transaction.UserService.addUser
所以在上下文中存在事务时,使用此事务传播方式会加入当前上下文事务中。
结论
基于PROPAGATION_MANDATORY
的强制事务的特点,适用于一些具有依赖关系一些场景,当前的操作必须在事务中执行。例如转账的场景,转账操作依赖扣款操作,必须保证转账和扣款在事务中进行,要么都成功,要么都失败。
PROPAGATION_REQUIRES_NEW
描述
创建一个新事务,并挂起当前事务(如果存在)。 如果当前不存在事务则创建一个新的事务,如果存在就挂起当前事务,直到新事务完成。上下文没有事务时比较好理解,创建一个新事物即可。存在事务时,是如何执行?我们首先通过下面的例子看看,修改例子中内层方法的事务传播方式。
typescript
代码解读
复制代码
@Transactional public void addUser(User user, UserExt userExt) { log.info("执行方法addUser当前事务:{}", TransactionSynchronizationManager.getCurrentTransactionName()); super.save(user); userExt.setUBh(user.getBh()); txBservice.addUserExt(userExt); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void addUserExt(UserExt ext) { log.info("执行方法addUserExt当前事务:{}", TransactionSynchronizationManager.getCurrentTransactionName()); super.save(ext); // 忽略一些 其他数据库操作,例如记录日志 }
执行addUser
方法,日志如下:
makefile
代码解读
复制代码
c.p.p.spring.transaction.UserService :执行方法addUser当前事务:com.pujiho.practice.spring.transaction.UserService.addUser c.p.p.spring.transaction.UserExtService:执行方法addUserExt当前事务:com.pujiho.practice.spring.transaction.UserExtService.addUserExt
由此可以看出,内层方法并未使用外层方法已经创建的事务,而是创建了自己的事务。那么当外层方法出现了异常,已经执行的内层方法的操作是否会回滚呢?想必你已经有了答案,我们通过测试看看结果。在外层方法中加入异常代码。
java
代码解读
复制代码
@Transactional public void addUser(User user, UserExt userExt) { log.info("执行方法addUser当前事务:{}", TransactionSynchronizationManager.getCurrentTransactionName()); super.save(user); userExt.setUBh(user.getBh()); txBservice.addUserExt(userExt); // 抛出异常 int i = 10/0; }
我们再执行addUser
方法看看结果,插入的数据如下:
ini
代码解读
复制代码
@Test void testSpringTransaction1() { User user = new User("灭霸", 200, "男"); UserExt ext = new UserExt("mieba@com", "mieba", 500, 200); userService.addUser(user, ext); }
从数据库查询结果可以看到,外层方法插入的user信息因为外层方法出现异常得到事务回滚,而内层方法由于是独立的事务,不受影响成功插入。
我们再来测试一下,如果内层方法中出现异常,情况又会怎样呢?去除外层方法的异常,加到内层方法中。
java
代码解读
复制代码
@Transactional public void addUser(User user, UserExt userExt) { log.info("执行方法addUser当前事务:{}", TransactionSynchronizationManager.getCurrentTransactionName()); super.save(user); userExt.setUBh(user.getBh()); txBservice.addUserExt(userExt); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void addUserExt(UserExt ext) { log.info("执行方法addUserExt当前事务:{}", TransactionSynchronizationManager.getCurrentTransactionName()); super.save(ext); // 忽略一些 其他数据库操作,例如记录日志 // 抛出异常 int i = 10/0; } @Test void testSpringTransaction1() { User user = new User("灭霸2号", 200, "男"); UserExt ext = new UserExt("mieba2@com", "mieba", 500, 200); userService.addUser(user, ext); }
查询结果如下,均未查询到结果。虽然处于不同的事务中,但是内层方法的异常会向上抛出,外层方法的也会抛出异常,所以都会回滚事务。
结论
事务传播方式PROPAGATION_REQUIRES_NEW
的特点是,无论上下文中是否存在事务,都会创建自己独立的事务。所以,PROPAGATION_REQUIRES_NEW
的使用场景也就显而易见了,适用于独立的操作,和其他操作互不影响的场景。比如在某些敏感的业务操作时,无论业务操作是否成功,我们希望都要记录系统操作日志。那么就可以在进行具体业务操作前创建独立的事务记录日志,就算业务操作因为一些异常回滚了,也不影响我们记录的操作日志。
PROPAGATION_NOT_SUPPORTED
描述
非事务性地执行,挂起当前事务(如果存在)。
实验
如果当前不存在事务,便以非事务的方式执行。通过下面的例子来看看。
java
代码解读
复制代码
@Test void testSpringTransaction() { UserExt ext = new UserExt("213412@com", "pujihowx", 180, 65); ext.setUBh(5); userExtService.addUserExt(ext); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void addUserExt(UserExt ext) { log.info("执行方法addUserExt当前事务:{}", TransactionSynchronizationManager.getCurrentTransactionName()); super.save(ext); // 抛出异常 int i = 10/0; }
执行后查询数据如下,u_bh
为5的数据成功入库,并没有回滚。所以使用此事务传播方式,在当前上下文没有事务的情况下,当前操作也会以非事务的方式执行。
如果当前存在事务,按照字面意思,当前同样也会以非事务的方式执行。那么内层方法出现的异常向上传递,外层方法的操作会回滚吗?同样我们通过实验看看结果,保留上面子类方法的异常抛出,父类方法不做处理。
java
代码解读
复制代码
@Transactional public void addUser(User user, UserExt userExt) { log.info("执行方法addUser当前事务:{}", TransactionSynchronizationManager.getCurrentTransactionName()); super.save(user); userExt.setUBh(user.getBh()); txBservice.addUserExt(userExt); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void addUserExt(UserExt ext) { log.info("执行方法addUserExt当前事务:{}", TransactionSynchronizationManager.getCurrentTransactionName()); super.save(ext); // 忽略一些 其他数据库操作,例如记录日志 // 抛出异常 int i = 10/0; } @Test void testSpringTransaction1() { User user = new User("灭霸3号", 200, "男"); UserExt ext = new UserExt("mieba3@com", "mieba", 500, 200); userService.addUser(user, ext); }
执行外层方法,插入’灭霸3号‘,看看结果。为了方便查看,使用右连接查询数据。
可以看到t_user
表没有灭霸3号
,所以外层方法因为收到了异常继续抛出,操作得到了回滚,而子类不受影响执行成功。
结论
如果当前已经有一个事务在运行,Spring 会将这个事务挂起,直到该方法以非事务的方式执行完成。以下是一些适合使用 PROPAGATION_NOT_SUPPORTED
的场景:
- 执行不需要事务的操作: 当你希望在方法中执行某些操作,这些操作不需要事务支持,甚至可能由于事务的存在而导致一些性能或逻辑上的问题。例如,记录日志、发送通知、或执行一些不需要保证一致性的读操作时,可以使用
PROPAGATION_NOT_SUPPORTED
来确保这些操作不被包裹在事务中。 - 避免长时间持有事务: 在某些情况下,方法可能会执行一些耗时的操作,比如大量的数据处理或远程服务调用。在这种情况下,如果事务一直保持打开状态,可能会导致数据库资源被长时间占用,影响性能。使用
PROPAGATION_NOT_SUPPORTED
可以在执行这些耗时操作时挂起事务,释放资源。 - 防止事务回滚干扰非事务操作: 如果某个方法中包含了一些不应该被回滚的操作(如发出不可撤回的外部请求),而这些操作与事务控制的其他部分在同一个方法中,那么可以使用
PROPAGATION_NOT_SUPPORTED
将这些操作放在没有事务的环境中执行,以避免因事务回滚导致不良后果。 - 调用非事务性服务: 当一个服务提供的功能不需要事务支持,或者明确希望该服务在无事务的情况下运行时,可以使用
PROPAGATION_NOT_SUPPORTED
来确保调用时不受外部事务的影响。
简而言之,PROPAGATION_NOT_SUPPORTED
适用于那些不需要或不希望在事务上下文中执行的操作。
PROPAGATION_NEVER
说明
不支持当前事务;如果存在当前事务,则抛出异常。 这个比较好理解,我们直接通过测试看看结果。
实验
java
代码解读
复制代码
@Transactional(propagation = Propagation.NEVER) public void addUserExt(UserExt ext) { log.info("执行方法addUserExt当前事务:{}", TransactionSynchronizationManager.getCurrentTransactionName()); super.save(ext); }
将内层方法addUserExt
的事务注解的传播方式修改为Propagation.NEVER
,调用父级的事务方法。执行日志异常信息如下,抛出了IllegalTransactionStateException
异常。
csharp
代码解读
复制代码
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
结论
总的来说,PROPAGATION_NEVER
的使用场景比较特殊,通常是在你明确不希望某些操作受事务管理影响时才会使用。和PROPAGATION_NOT_SUPPORTED
的相比,PROPAGATION_NEVER
会直接抛出异常,这样会导致当前已经存在的事务操作发生回滚,而前者不会受影响。
PROPAGATION_NESTED
说明
如果当前事务存在,则在嵌套事务中执行,否则像PROPAGATION_REQUIRED一样执行。 也就是,如果当前不存在事务 ,就会创建一个事务。如果存在事务,则嵌套事务中执行。如何理解?和REQUIRES_NEW
又有什么区别?还是通过测试看看情况。
实验
修改内层方法的事务传播方式为PROPAGATION_NESTED
,同样执行外层方法,日志如下:
makefile
代码解读
复制代码
org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities
出乎意料,当执行字方法时竟然报错了,异常信息意思是,JpaDialect
不支持保存点。这是因为我所使用的数据库mysql本身就不支持嵌套事务。查看NESTED的源码注释,官方也给出了说明,并不是都支持嵌套实物。
sql
代码解读
复制代码
NOTE: Actual creation of a nested transaction will only work on specific transaction managers. Out of the box, this only applies to the JDBC org. springframework. jdbc. datasource. DataSourceTransactionManager when working on a JDBC 3.0 driver. Some JTA providers might support nested transactions as well.
实际创建的嵌套事务只适用于特定的事务管理器。开箱即用,这只适用于JDBC org、springframework、jdbc。
而NESTED
依赖于 “savepoint” 机制,可以在内层方法执行前创建一个“暂存点”,然后开启嵌套的子事务,如果子事务发生异常,会直接回滚到这个暂存点,而不会导致整体事务的回滚。感兴趣的可以试试其他的数据库,或者基于 “savepoint” 机制去实现。我们可以通过它与PROPAGATION_REQUIRES_NEW
对比,以便于理解嵌套事务NESTED。
结论
不知道你发现没,PROPAGATION_NESTED
的特性和PROPAGATION_REQUIRES_NEW
很类似,不存在事务就创建事务,存在时就新创建一个事务执行。但是仔细一想,肯定是有区别的,不然Spring官方就不会提供这个了。
NESTED
和REQUIRES_NEW
的区别
这两种方式都相当于开了一个新事务,但是它们之间最重要的区别就是,NESTED
是一个嵌套的子事务,如果外层事务回滚,则这个子事务会被一起回滚,而REQUIRES_NEW
的方法则不会。这个在上面对REQUIRES_NEW
的测试代码的第一个例子中得以体现。NESTED虽然也开了一个新事物,但是是一个子事务,最终还是会受父事务的影响。
使用场景
Spring 提供了 7 种事务传播行为,它们定义了事务在方法之间如何传播。以下是每种传播方式及其典型使用场景的简单总结:
-
PROPAGATION_REQUIRED
(默认值)- 使用场景:最常用的传播行为。如果当前存在事务,方法将在该事务中执行;如果没有事务,则启动一个新事务。
- 典型应用:大多数业务操作场景。
-
PROPAGATION_REQUIRES_NEW
- 使用场景:总是启动一个新事务,当前事务(如果存在)会被挂起。
- 典型应用:当你希望方法独立于外层事务运行,并且需要单独提交或回滚,如记录日志、发送通知等。
-
PROPAGATION_SUPPORTS
- 使用场景:实际场景很少。如果当前有事务,则在该事务中运行;如果没有事务,则以非事务方式运行。
- 典型应用:事务性要求不强的场景,可以选择性地加入事务。
-
PROPAGATION_NOT_SUPPORTED
- 使用场景:总是以非事务方式运行,当前事务(如果存在)会被挂起。
- 典型应用:某些方法不需要事务管理,甚至需要确保不在事务中执行,如某些大数据查询或外部 API 调用。
-
PROPAGATION_NEVER
- 使用场景:以非事务方式运行,如果当前存在事务,则抛出异常。
- 典型应用:明确要求方法不允许在事务中运行的场景,确保事务性副作用不会影响到此方法。
-
PROPAGATION_MANDATORY
- 使用场景:要求方法必须在一个现有事务中运行,如果没有事务则抛出异常。
- 典型应用:方法必须依赖外层事务的场景,如强制性依赖事务上下文的数据库操作。
-
PROPAGATION_NESTED
- 使用场景:如果当前有事务,则嵌套事务在该事务中执行,外部事务可以独立回滚;如果没有事务,则行为类似于
REQUIRED
。 - 典型应用:需要局部回滚的场景,例如当方法内部的操作失败时,只回滚该部分操作,而不影响外层事务(通常需要数据库和事务管理器的支持)。
- 使用场景:如果当前有事务,则嵌套事务在该事务中执行,外部事务可以独立回滚;如果没有事务,则行为类似于
总结
REQUIRED
:最常用,适合大部分场景。REQUIRES_NEW
:适用于需要独立事务的场景。SUPPORTS
和NOT_SUPPORTED
:事务可选或明确不需要事务的场景。NEVER
:确保方法不在事务中运行。MANDATORY
:必须在已有事务中运行的方法。NESTED
:需要局部回滚的复杂事务场景。