Ref: https://www.cnblogs.com/duanxz/p/4746892.html#5028999

一、基本概念

事务的隔离级别,事务传播行为见《事务之二:spring 事务(事务管理方式,事务 5 隔离级别,7 个事务传播行为,spring 事务回滚条件)

二、 嵌套事务示例

image.png

2.1 Propagation.REQUIRED+Propagation.REQUIRES_NEW

  1. package dxz.demo1;
  2. @Service
  3. public class ServiceAImpl implements ServiceA {
  4. @Autowired
  5. private ServiceB serviceB;
  6. @Autowired
  7. private VcSettleMainMapper vcSettleMainMapper;
  8. @Override
  9. @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
  10. public void methodA() {
  11. String id = IdGenerator.generatePayId("A");
  12. VcSettleMain vc = buildModel(id);
  13. vcSettleMainMapper.insertVcSettleMain(vc);
  14. System.out.println("ServiceAImpl VcSettleMain111:" + vc);
  15. serviceB.methodB();
  16. VcSettleMain vc2 = buildModel(id);
  17. vcSettleMainMapper.insertVcSettleMain(vc2);
  18. System.out.println("ServiceAImpl VcSettleMain22222:" + vc2);
  19. }
  20. private VcSettleMain buildModel(String id) {
  21. VcSettleMain vc = new VcSettleMain();
  22. vc.setBatchNo(id);
  23. vc.setCreateBy("dxz");
  24. vc.setCreateTime(LocalDateTime.now());
  25. vc.setTotalCount(11L);
  26. vc.setTotalMoney(BigDecimal.ZERO);
  27. vc.setState("5");
  28. return vc;
  29. }
  30. }

ServiceB

  1. package dxz.demo1;
  2. @Service
  3. public class ServiceBImpl implements ServiceB {
  4. @Autowired
  5. private VcSettleMainMapper vcSettleMainMapper;
  6. @Override
  7. @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = false)
  8. public void methodB() {
  9. String id = IdGenerator.generatePayId("B");
  10. VcSettleMain vc = buildModel(id);
  11. vcSettleMainMapper.insertVcSettleMain(vc);
  12. System.out.println("---ServiceBImpl VcSettleMain:" + vc);
  13. }
  14. }

Controller

  1. package dxz.demo1;
  2. @RestController
  3. @RequestMapping("/dxzdemo1")
  4. @Api(value = "Demo1", description="Demo1")
  5. public class Demo1 {
  6. @Autowired
  7. private ServiceA serviceA;
  8. /**
  9. * 嵌套事务测试
  10. */
  11. @PostMapping(value = "/test1")
  12. public String methodA() throws Exception {
  13. serviceA.methodA();
  14. return "ok";
  15. }
  16. }

结果:
image.png
看数据库表记录:
image.png
这种情况下,因为 ServiceB#methodB 的事务属性为 PROPAGATION_REQUIRES_NEW,ServiceB 是一个独立的事务,与外层事务没有任何关系。如果 ServiceB 执行失败(上面示例中让 ServiceB 的 id 为已经存在的值),ServiceA 的调用出会抛出异常,导致 ServiceA 的事务回滚。
并且, 在 ServiceB#methodB 执行时 ServiceA#methodA 的事务已经挂起了 (关于事务挂起的内容已经超出了本文的讨论范围)。

2.2 Propagation.REQUIRED+Propagation.REQUIRED

  1. //ServiceA
  2. //...
  3. @Override
  4. @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
  5. public void methodA() {
  6. //ServiceB
  7. //...
  8. @Override
  9. @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
  10. public void methodB(String id) {

—“1” 可插入,“2” 可插入,“3” 不可插入:
结果是 “1”,“2”,“3” 都不能插入,“1”,“2” 被回滚。
—“1” 可插入,“2” 不可插入,“3” 可插入:
结果是 “1”,“2”,“3” 都不能插入,“1”,“2” 被回滚。

2.3 Propagation.REQUIRED + 无事务注解

  1. //ServiceA
  2. //...
  3. @Override
  4. @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
  5. public void methodA() {
  6. //ServiceB
  7. //...
  8. @Override
  9. //没有加事务注解
  10. public void methodB(String id) {

—“1” 可插入,“2” 可插入,“3” 不可插入:
结果是 “1”,“2”,“3” 都不能插入,“1”,“2” 被回滚。

2.4 内层事务被 try-catch

2.4.1 trycatch+Propagation.REQUIRED+Propagation.REQUIRED

  1. //ServiceA
  2. //...
  3. @Override
  4. @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
  5. public void methodA() {
  6. try {
  7. serviceB.methodB(id);
  8. } catch (Exception e) {
  9. System.out.println("内层事务出错啦。");
  10. }
  11. }
  12. //ServiceB
  13. //...
  14. @Override
  15. @Transactional(propagation = Propagation.NESTED, readOnly = false)
  16. public void methodB(String id) {

—“1” 可插入,“2” 不可插入,“3” 可插入:
结果是 “1”,“2”,“3” 都不能插入,“1” 被回滚。
事务设置为 Propagation.REQUIRED 时,如果内层方法抛出 Exception,外层方法中捕获 Exception 但是并没有继续向外抛出,最后出现 “Transaction rolled back because it has been marked as rollback-only” 的错误。外层的方法也将会回滚。
其原因是:内层方法抛异常返回时,transacation 被设置为 rollback-only 了,但是外层方法将异常消化掉,没有继续向外抛,那么外层方法正常结束时,transaction 会执行 commit 操作,但是 transaction 已经被设置为 rollback-only 了。所以,出现 “Transaction rolled back because it has been marked as rollback-only” 错误。

2.4.2 trycatch+Propagation.REQUIRED+Propagation.NESTED

—“1” 可插入,“2” 不可插入,“3” 可插入:
结果是 “1”,“3” 记录插入成功,“2” 记录插入失败。
说明:
当内层配置成 PROPAGATION_NESTED, 此时两者之间又将如何协作呢?从 Juergen Hoeller 的原话中我们可以找到答案,ServiceB#methodB 如果 rollback, 那么内部事务 (即 ServiceB#methodB) 将回滚到它执行前的 SavePoint (注意,这是本文中第一次提到它,潜套事务中最核心的概念), 而外部事务 (即 ServiceA#methodA) 可以有以下两种处理方式:
1、内层失败,外层调用其它分支,代码如下

  1. ServiceA {
  2. /**
  3. * 事务属性配置为 PROPAGATION_REQUIRED
  4. */
  5. void methodA() {
  6. try {
  7. ServiceB.methodB();
  8. } catch (SomeException) {
  9. // 执行其他业务, 如 ServiceC.methodC();
  10. }
  11. }
  12. }

这种方式也是嵌套事务最有价值的地方,它起到了分支执行的效果,如果 ServiceB.methodB 失败,那么执行 ServiceC.methodC (), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据 (相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中,而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。

  1. 代码不做任何修改,那么如果内部事务 (即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint (在任何情况下都会如此), 外部事务 (即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback。

    三、嵌套事务总结

    使用嵌套事务的场景有两点需求:
  • 需要事务 BC 与事务 AD 一起 commit,即:作为事务 AD 的子事务,事务 BC 只有在事务 AD 成功 commit 时(阶段 3 成功)才 commit。这个需求简单称之为 “联合成功”。这一点 PROPAGATION_NESTED 和 PROPAGATION_REQUIRED 可以做到。
  • 需要事务 BC 的 rollback 不(无条件的)影响事务 AD 的 commit。这个需求简单称之为 “隔离失败”。这一点 PROPAGATION_NESTED 和 PROPAGATION_REQUIRES_NEW 可以做到。

分解下,可知 PROPAGATION_NESTED 的特殊性有:
1、使用 PROPAGATION_REQUIRED 满足需求 1,但子事务 BC 的 rollback 会无条件地使父事务 AD 也 rollback,不能满足需求 2。即使对子事务进行了 try-catch,父事务 AD 也不能 commit。示例见 2.4.1、trycatch+Propagation.REQUIRED+Propagation.REQUIRED
2、使用 PROPAGATION_REQUIRES_NEW 满足需求 2,但子事务(这时不应该称之为子事务)BC 是完全新的事务上下文,父事务(这时也不应该称之为父事务)AD 的成功与否完全不影响 BC 的提交,不能满足需求 1。

同时满足上述两条需求就要用到 PROPAGATION_NESTED 了。PROPAGATION_NESTED 在事务 AD 执行到 B 点时,设置了 savePoint(关键)。
当 BC 事务成功 commit 时,PROPAGATION_NESTED 的行为与 PROPAGATION_REQUIRED 一样。只有当事务 AD 在 D 点成功 commit 时,事务 BC 才真正 commit,如果阶段 3 执行异常,导致事务 AD rollback,事务 BC 也将一起 rollback ,从而满足了 “联合成功”。
当阶段 2 执行异常,导致 BC 事务 rollback 时,因为设置了 savePoint,AD 事务可以选择与 BC 一起 rollback 或继续阶段 3 的执行并保留阶段 1 的执行结果,从而满足了 “隔离失败”。
当然,要明确一点,事务传播策略的定义是在声明或事务管理范围内的(首先是在 EJB CMT 规范中定义,Spring 事务框架补充了 PROPAGATION_NESTED),编程式的事务管理不存在事务传播的问题。

四、PROPAGATION_NESTED 的必要条件

上面大致讲述了潜套事务的使用场景,下面我们来看如何在 spring 中使用 PROPAGATION_NESTED, 首先来看 AbstractPlatformTransactionManager

  1. /**
  2. * Create a TransactionStatus for an existing transaction.
  3. */
  4. private TransactionStatus handleExistingTransaction(
  5. TransactionDefinition definition, Object transaction, boolean debugEnabled)
  6. throws TransactionException {
  7. ... 省略
  8. if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
  9. if (!isNestedTransactionAllowed()) {
  10. throw new NestedTransactionNotSupportedException(
  11. "Transaction manager does not allow nested transactions by default - " +
  12. "specify 'nestedTransactionAllowed' property with value 'true'");
  13. }
  14. if (debugEnabled) {
  15. logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
  16. }
  17. if (useSavepointForNestedTransaction()) {
  18. // Create savepoint within existing Spring-managed transaction,
  19. // through the SavepointManager API implemented by TransactionStatus.
  20. // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
  21. DefaultTransactionStatus status =
  22. newTransactionStatus(definition, transaction, false, false, debugEnabled, null);
  23. status.createAndHoldSavepoint();
  24. return status;
  25. }
  26. else {
  27. // Nested transaction through nested begin and commit/rollback calls.
  28. // Usually only for JTA: Spring synchronization might get activated here
  29. // in case of a pre-existing JTA transaction.
  30. doBegin(transaction, definition);
  31. boolean newSynchronization = (this.transactionSynchronization != SYNCHRONIZATION_NEVER);
  32. return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);
  33. }
  34. }
  35. }
  1. 我们要设置 transactionManager 的 nestedTransactionAllowed 属性为 true, 注意,此属性默认为 false!!
    再看 AbstractTransactionStatus#createAndHoldSavepoint () 方法

    1. /**
    2. * Create a savepoint and hold it for the transaction.
    3. * @throws org.springframework.transaction.NestedTransactionNotSupportedException
    4. * if the underlying transaction does not support savepoints
    5. */
    6. public void createAndHoldSavepoint() throws TransactionException {
    7. setSavepoint(getSavepointManager().createSavepoint());
    8. }

    可以看到 Savepoint 是 SavepointManager.createSavepoint 实现的,再看 SavepointManager 的层次结构,发现 其 Template 实现是 JdbcTransactionObjectSupport, 常用的 DatasourceTransactionManager, HibernateTransactionManager 中的 TransactonObject 都是它的子类 : JdbcTransactionObjectSupport 告诉我们必须要满足两个条件才能 createSavepoint.

  2. java.sql.Savepoint 必须存在,即 jdk 版本要 1.4+

  3. Connection.getMetaData ().supportsSavepoints () 必须为 true, 即 jdbc drive 必须支持 JDBC 3.0

确保以上条件都满足后,你就可以尝试使用 PROPAGATION_NESTED 了。