一、传播行为分类

传播行为 描述
REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 REQUIRED 类似的操作(创建新事务)。
SUPPORTS 加入当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY 加入当前的事务,如果当前没有事务,就抛出异常。
NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。

In Spring-managed transactions, be aware of the difference between physical and logical transactions, and how the propagation setting applies to this difference.

在Spring管理的事务中,要注意物理事务和逻辑事务之间的差异,以及传播机制的设置如何应用于这种差异。

1、REQUIRED

@Transaction 注解默认设置的传播行为。

如果当前存在事务,则加入当前事务;如果不存在则创建一个新的事务。

This is a fine default in common call stack arrangements within the same thread (for example, a service facade that delegates to several repository methods where all the underlying resources have to participate in the service-level transaction).

在同一线程内的常见调用堆栈安排中,这是一个很好的默认设置。在一个 service 层方法中有多个 DAO 层方法,那么这些对底层数据资源的操作都应该参与服务级别的事务。

When the propagation setting is PROPAGATION_REQUIRED, a logical transaction scope is created for each method upon which the setting is applied. Each such logical transaction scope can determine rollback-only status individually, with an outer transaction scope being logically independent from the inner transaction scope. In the case of standard PROPAGATION_REQUIRED behavior, all these scopes are mapped to the same physical transaction. So a rollback-only marker set in the inner transaction scope does affect the outer transaction’s chance to actually commit.

当事务的传播属性设置为 REQUIRED 时,将为应用该设置的每个方法创建一个逻辑事务。每个这样的逻辑事务可以单独确定仅回滚状态,外部事务作用域在逻辑上独立于内部事务作用域。在标准的 REQUIRED 行为中,所有的作用域都映射到同一物理事务。因此,在内部事务中设置的仅回滚标记会影响外部事务的提交。

However, in the case where an inner transaction scope sets the rollback-only marker, the outer transaction has not decided on the rollback itself, so the rollback (silently triggered by the inner transaction scope) is unexpected. A corresponding UnexpectedRollbackException is thrown at that point. This is expected behavior so that the caller of a transaction can never be misled to assume that a commit was performed when it really was not. So, if an inner transaction (of which the outer caller is not aware) silently marks a transaction as rollback-only, the outer caller still calls commit. The outer caller needs to receive an UnexpectedRollbackException to indicate clearly that a rollback was performed instead.

然而,在内部事务设置为仅回滚时,外部事务本身是尚未决定是否要回滚的,因此这个回滚(由内部事务范围静默触发)是意外发生的。此时会引发 UnexpectedRollbackException 异常。这是预期的行为,因为这样事务的调用方就不会被误导以为提交是在实际不该执行的情况下执行的。所以,如果一个内部事务(外部调用方不知道该事务)以静默方式将事务标记为仅回滚,外部调用方仍然提交事务。外部调用程序需要接收一个 UnexpectedRollbackException 异常,来表示一个回滚执行了。

2、REQUIRES_NEW

创建一个新事务,如果当前存在一个事务则将其挂起。

NOTE: Actual transaction suspension will not work out-of-the-box on all transaction managers. This in particular applies to org.springframework.transaction.jta.JtaTransactionManager, which requires the jakarta.transaction.TransactionManager to be made available to it (which is server-specific in standard Jakarta EE).

注意:实际上事务挂起不会作用于所有的事务管理器。这尤其适用于 org.springframework.transaction.jta.JtaTransactionManager,它需要 jakarta.transaction.TransactionManager 来让它生效。

PROPAGATION_REQUIRES_NEW, in contrast to PROPAGATION_REQUIRED, always uses an independent physical transaction for each affected transaction scope, never participating in an existing transaction for an outer scope. In such an arrangement, the underlying resource transactions are different and, hence, can commit or roll back independently, with an outer transaction not affected by an inner transaction’s rollback status and with an inner transaction’s locks released immediately after its completion. Such an independent inner transaction can also declare its own isolation level, timeout, and read-only settings and not inherit an outer transaction’s characteristics.

REQUIRED 相比,REQUIRES_NEW 始终为每个受影响的事务范围使用独立的物理事务,不会参与到外部范围的现有事务中。在这种情况下,底层资源事务是不同的,因此可以单独地提交或回滚,外部事务不受内部事务回滚状态的影响,内部事务的锁在完成后立即释放。一个独立的内部事务还可以声明自己的隔离级别、超时时间和只读设置,而不继承外部事务的设置。

3、NESTED

如果当前存在事务则在其中执行嵌套事务,其他情况的操作与 REQUIRED 相同(如果不存在事务,则创建新事务)。

Note: Actual creation of a nested transaction will only work on specific transaction managers. Out of the box, this only applies to the JDBC DataSourceTransactionManager. Some JTA providers might support nested transactions as well.

注意:实际上嵌套事务的创建只在某些特定的事务管理器上生效,比如JDBC DataSourceTransactionManager

PROPAGATION_NESTED uses a single physical transaction with multiple savepoints that it can roll back to. Such partial rollbacks let an inner transaction scope trigger a rollback for its scope, with the outer transaction being able to continue the physical transaction despite some operations having been rolled back. This setting is typically mapped onto JDBC savepoints, so it works only with JDBC resource transactions. See Spring’s DataSourceTransactionManager.

NESTED 使用具有多个保存点的单个物理事务,其可以回滚到这些保存点。这种部分回滚使内部事务触发回滚,而外部事务能够继续物理事务,尽管某些操作已回滚。此设置通常映射到JDBC保存点,因此它仅适用于JDBC资源事务。

嵌套事务,在逻辑上可以理解为内部事务是外部事务的子事务,内部事务回滚不会导致外部事务回滚,但是当外部事务回滚时内部事务也要回滚。

4、SUPPORTS

如果当前存在事务,则加入当前事务;如果不存在,以非事务方式运行。

1
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.

对于有事务同步功能的事务管理器,SUPPORTS 这种传播行为与没有事务的情况有细微的不同,它定义了一个同步功能将应用的事务范围。结果就是,同一个数据源将分享一整个制定的范围。要注意的是这个实际上取决于事务管理器的同步功能配置。

5、MANDATORY

如果当前存在事务,则加入当前事务;如果不存在,则抛出异常。

6、NOT_SUPPORTED

无事务地执行,如果当前存在一个事务则将其挂起。

NOTE: Actual transaction suspension will not work out-of-the-box on all transaction managers. This in particular applies to org.springframework.transaction.jta.JtaTransactionManager, which requires the jakarta.transaction.TransactionManager to be made available to it (which is server-specific in standard Jakarta EE).

注意:事务挂起不会在所有类型的事务管理器上都生效。

7、NEVER

无事务地执行,如果存在事务则抛出异常

二、传播行为验证

1、前言

1.1 为什么会有事务传播机制?

Spring 对于事务的控制,是使用 AOP 切面实现的,不需要关心事务的开始、提交与回滚,只需要在方法上添加 @Transactional 注解即可。但是 service 的方法之间存在调用,当出现以下情况:

  • ServiceA 的方法调用了 ServiceB 的方法,两个方法都有事务,B 方法抛出了异常,A 方法是提交还是回滚?
  • ServiceA 的方法调用了 ServiceB 的方法,只有 A 方法有事务,是否要将 B 方法加入到 A 方法的事务?B 方法抛出异常,A 方法是提交还是回滚?
  • ServiceA 的方法调用了 ServiceB 的方法,两个方法都有事务,B 方法已经执行完成,A 方法抛出了异常,B 方法是提交还是回滚?

1.2 传播机制生效条件之调用代理对象方法

1.2.1 现象

因为 spring 是使用 AOP 来代理事务控制,那么如果在类内部调用类内部的事务方法,调用事务方法的过程并不是通过代理对象来调用的。所以在同一个 service 类中两个方法的调用,绕过了代理对象,传播机制是不生效的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Service
public class ServiceA {

@Autowired
private UserDAO userDAO;

@Autowired
private ServiceB serviceB;

public void a_none_b_required_exception() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
this.insertRequired(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("required exception " + RandomStringUtils.randomAlphabetic(8));
serviceB.insertRequiredException(userDO2);
}

@Transactional(propagation = Propagation.REQUIRED)
public void insertRequired(UserDO userDO) {
userDAO.insert(userDO);
}
}

上面的代码,this.insertRequired(userDO); 这一行,IDEA中会提示 @Transactional self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime ,即 insertRequired 方法的事务是不生效的,因为是类内部方法之间的调用,而 a_none_b_required_exception() 方法上又没有添加事务注解。IDEA给出的进一步提示为:

Using @Transactional: In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.

1.2.2 验证

为了验证事务是否真的失效,需要搞出来点异常,将上面的代码改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Service
public class ServiceA {

@Autowired
private UserDAO userDAO;

@Autowired
private ServiceB serviceB;

public void a_none_b_required_exception() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
this.insertRequiredException(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("required exception " + RandomStringUtils.randomAlphabetic(8));
serviceB.insertRequiredException(userDO2);
}

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void insertRequiredException(UserDO userDO) {
userDAO.insert(userDO);
int a = 1 / 0;
}
}

可以看到 insertRequiredException 方法上面添加了事务注解,并且方法内部在向数据库插入数据之后添加了报运行时异常代码。如果事务生效的话,新增的数据时会回滚的。但是执行完测试代码后,日志中异常如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1de13f34] was not registered for synchronization because synchronization is not active
2024-07-04T14:18:33.146+08:00 INFO 65144 --- [ main] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Starting...
2024-07-04T14:18:33.285+08:00 INFO 65144 --- [ main] com.zaxxer.hikari.pool.HikariPool : SpringBootDemoHikariCP - Added connection org.postgresql.jdbc.PgConnection@6bd92538
2024-07-04T14:18:33.285+08:00 INFO 65144 --- [ main] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Start completed.
JDBC Connection [HikariProxyConnection@456240898 wrapping org.postgresql.jdbc.PgConnection@6bd92538] will not be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808747204613853184(Long), null, required OBJJzzow(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1de13f34]

java.lang.ArithmeticException: / by zero

at cn.z2huo.demo.spring.transactional.service.ServiceA.insertRequiredException(ServiceA.java:70)
at cn.z2huo.demo.spring.transactional.service.ServiceA.a_none_b_required_exception(ServiceA.java:45)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:713)
at cn.z2huo.demo.spring.transactional.service.ServiceA$$SpringCGLIB$$0.a_none_b_required_exception(<generated>)
at cn.z2huo.demo.spring.transactional.service.ServiceATest.a_none_b_required_exception(ServiceATest.java:33)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

而数据库中的数据还存在,并没有回滚。

2、场景验证

2.1 REQUIRED

2.1.1 场景一 ServiceA方法调用ServiceB方法,B方法抛出异常

ServiceA 和 ServiceB 中的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
@Transactional(propagation = Propagation.REQUIRED)  
public void a_required_b_required_exception() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("required exception " + RandomStringUtils.randomAlphabetic(8));
serviceB.insertRequiredException(userDO2);
}
1
2
3
4
5
@Transactional(propagation = Propagation.REQUIRED)  
public void insertRequiredException(UserDO userDO) {
userDAO.insert(userDO);
int a = 1 / 0;
}

输出日志如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
2024-07-04T15:07:20.980+08:00  INFO 57472 --- [           main] com.zaxxer.hikari.HikariDataSource       : SpringBootDemoHikariCP - Starting...
2024-07-04T15:07:21.125+08:00 INFO 57472 --- [ main] com.zaxxer.hikari.pool.HikariPool : SpringBootDemoHikariCP - Added connection org.postgresql.jdbc.PgConnection@661e1399
2024-07-04T15:07:21.126+08:00 INFO 57472 --- [ main] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Start completed.
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5940b14e]
JDBC Connection [HikariProxyConnection@52513709 wrapping org.postgresql.jdbc.PgConnection@661e1399] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808759485670162432(Long), null, required wBwMZyGI(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5940b14e]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5940b14e] from current transaction
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808759485863100416(Long), null, required exception pLgLzeqh(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5940b14e]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5940b14e]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5940b14e]

java.lang.ArithmeticException: / by zero

at cn.z2huo.demo.spring.transactional.service.ServiceB.insertRequiredException(ServiceB.java:40)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:392)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717)
at cn.z2huo.demo.spring.transactional.service.ServiceB$$SpringCGLIB$$0.insertRequiredException(<generated>)
at cn.z2huo.demo.spring.transactional.service.ServiceA.a_required_b_required_exception(ServiceA.java:40)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:392)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717)
at cn.z2huo.demo.spring.transactional.service.ServiceA$$SpringCGLIB$$0.a_required_b_required_exception(<generated>)
at cn.z2huo.demo.spring.transactional.service.ServiceATest.a_required_b_required_exception(ServiceATest.java:28)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

2024-07-04T15:07:21.254+08:00 INFO 57472 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Shutdown initiated...
2024-07-04T15:07:21.274+08:00 INFO 57472 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Shutdown completed.

结果:两条数据都没有插入成功。两个方法都是 REQUIRED 的,会加入到同一个事务中。B方法抛出了异常,该异常没有被捕获导致A方法也抛出了异常,事务发生回滚。

2.1.2 场景二 ServiceA方法调用ServiceB方法,B方法抛出异常但在方法内部捕获

ServiceA 和 ServiceB 中的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
@Transactional(propagation = Propagation.REQUIRED)  
public void a_required_b_required_exception_catch() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("required exception catch " + RandomStringUtils.randomAlphabetic(8));
serviceB.insertRequiredExceptionCatch(userDO2);
}
1
2
3
4
5
6
7
8
9
@Transactional(propagation = Propagation.REQUIRED)  
public void insertRequiredExceptionCatch(UserDO userDO) {
try {
userDAO.insert(userDO);
int a = 1 / 0;
} catch (Exception e) {
log.error("serviceB insert required exception catch");
}
}

输出日志如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2024-07-04T14:41:02.375+08:00  INFO 67924 --- [           main] com.zaxxer.hikari.HikariDataSource       : SpringBootDemoHikariCP - Starting...
2024-07-04T14:41:02.526+08:00 INFO 67924 --- [ main] com.zaxxer.hikari.pool.HikariPool : SpringBootDemoHikariCP - Added connection org.postgresql.jdbc.PgConnection@3cc3f13e
2024-07-04T14:41:02.527+08:00 INFO 67924 --- [ main] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Start completed.
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1cba0321]
JDBC Connection [HikariProxyConnection@1389865849 wrapping org.postgresql.jdbc.PgConnection@3cc3f13e] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808752864529338368(Long), null, required TkRufniA(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1cba0321]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1cba0321] from current transaction
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808752864755830784(Long), null, required exception catch iDHorxbF(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1cba0321]
2024-07-04T14:41:02.619+08:00 ERROR 67924 --- [ main] c.z.d.s.transactional.service.ServiceB : serviceB insert required exception catch
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1cba0321]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1cba0321]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1cba0321]

结果:两条数据都插入成功。因为两个方法都没有抛出异常,所以不会发生回滚。

2.1.3 场景三 ServiceA方法调用ServiceB方法,B方法抛出异常,异常在A方法中捕获

ServiceA 和 ServiceB 中的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Transactional(propagation = Propagation.REQUIRED)  
public void a_required_b_required_exception_catch_in_a() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("required exception " + RandomStringUtils.randomAlphabetic(8));
try {
serviceB.insertRequiredException(userDO2);
} catch (Exception e) {
log.error("serviceB insert required exception, catch in A");
}
}
1
2
3
4
5
@Transactional(propagation = Propagation.REQUIRED)  
public void insertRequiredException(UserDO userDO) {
userDAO.insert(userDO);
int a = 1 / 0;
}

日志输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
2024-07-04T15:20:51.265+08:00  INFO 44772 --- [           main] com.zaxxer.hikari.HikariDataSource       : SpringBootDemoHikariCP - Starting...
2024-07-04T15:20:51.410+08:00 INFO 44772 --- [ main] com.zaxxer.hikari.pool.HikariPool : SpringBootDemoHikariCP - Added connection org.postgresql.jdbc.PgConnection@250967f1
2024-07-04T15:20:51.411+08:00 INFO 44772 --- [ main] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Start completed.
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1af677f8]
JDBC Connection [HikariProxyConnection@853186557 wrapping org.postgresql.jdbc.PgConnection@250967f1] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808762884243410944(Long), null, required fRLtqAag(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1af677f8]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1af677f8] from current transaction
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808762884419571712(Long), null, required exception PDGTdaBQ(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1af677f8]
2024-07-04T15:20:51.493+08:00 ERROR 44772 --- [ main] c.z.d.s.transactional.service.ServiceA : serviceB insert required exception, catch in A

java.lang.ArithmeticException: / by zero
at cn.z2huo.demo.spring.transactional.service.ServiceB.insertRequiredException(ServiceB.java:40) ~[classes/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:392) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717) ~[spring-aop-6.1.5.jar:6.1.5]
at cn.z2huo.demo.spring.transactional.service.ServiceB$$SpringCGLIB$$0.insertRequiredException(<generated>) ~[classes/:na]
at cn.z2huo.demo.spring.transactional.service.ServiceA.a_required_b_required_exception_catch_in_a(ServiceA.java:97) ~[classes/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:392) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717) ~[spring-aop-6.1.5.jar:6.1.5]
at cn.z2huo.demo.spring.transactional.service.ServiceA$$SpringCGLIB$$0.a_required_b_required_exception_catch_in_a(<generated>) ~[classes/:na]
at cn.z2huo.demo.spring.transactional.service.ServiceATest.a_required_b_required_exception_catch_in_a(ServiceATest.java:58) ~[test-classes/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:728) ~[junit-platform-commons-1.10.2.jar:1.10.2]
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:218) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:214) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:139) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) ~[na:na]
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) ~[na:na]
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:198) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:169) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:93) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:58) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:141) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:57) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:63) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57) ~[junit5-rt.jar:na]
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) ~[junit-rt.jar:na]
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) ~[idea_rt.jar:na]
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) ~[junit-rt.jar:na]
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232) ~[junit-rt.jar:na]
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55) ~[junit-rt.jar:na]

Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1af677f8]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1af677f8]

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:937)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:753)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:676)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:426)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717)
at cn.z2huo.demo.spring.transactional.service.ServiceA$$SpringCGLIB$$0.a_required_b_required_exception_catch_in_a(<generated>)
at cn.z2huo.demo.spring.transactional.service.ServiceATest.a_required_b_required_exception_catch_in_a(ServiceATest.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

2024-07-04T15:20:51.522+08:00 INFO 44772 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Shutdown initiated...
2024-07-04T15:20:51.564+08:00 INFO 44772 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Shutdown completed.

结果:两条数据都没有插入成功。两个方法都是 REQUIRED 的,会加入到同一个事务中。B方法抛出了异常,该异常被方法A捕获,A方法未抛出异常。B方法的内部事务回滚,外部事务和内部事务为逻辑事务,属于同一个物理事务,最终两个方法的插入操作都会回滚。

关于 UnexpectedRollbackException 异常,参考 [[Propagation 传播机制#1、REQUIRED]] 中描述。

2.1.4 场景四 ServiceA方法调用ServiceB方法,A方法在B方法执行完成后抛出异常

结果:两条数据都插入失败。

2.2 REQUIRES_NEW

2.2.1 场景一 ServiceA方法调用ServiceB方法,B方法抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
@Transactional(propagation = Propagation.REQUIRES_NEW)  
public void a_required_new_b_required_new_exception() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("required new " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("required new exception " + RandomStringUtils.randomAlphabetic(8));
serviceB.insertRequiredNewException(userDO2);
}
1
2
3
4
5
@Transactional(propagation = Propagation.REQUIRES_NEW)  
public void insertRequiredNewException(UserDO userDO) {
userDAO.insert(userDO);
int a = 1 / 0;
}

日志输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2024-07-04T16:22:14.040+08:00  INFO 51248 --- [           main] com.zaxxer.hikari.HikariDataSource       : SpringBootDemoHikariCP - Starting...
2024-07-04T16:22:14.198+08:00 INFO 51248 --- [ main] com.zaxxer.hikari.pool.HikariPool : SpringBootDemoHikariCP - Added connection org.postgresql.jdbc.PgConnection@5ebbde60
2024-07-04T16:22:14.199+08:00 INFO 51248 --- [ main] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Start completed.
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6060146b]
JDBC Connection [HikariProxyConnection@1979980170 wrapping org.postgresql.jdbc.PgConnection@5ebbde60] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808778330984271872(Long), null, required new ZBOKHJNz(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6060146b]
Transaction synchronization suspending SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6060146b]
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5bb2fb2b]
JDBC Connection [HikariProxyConnection@2037147839 wrapping org.postgresql.jdbc.PgConnection@56d6a1b1] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808778331185598464(Long), null, required new exception YdZJdVXv(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5bb2fb2b]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5bb2fb2b]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5bb2fb2b]
Transaction synchronization resuming SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6060146b]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6060146b]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6060146b]

java.lang.ArithmeticException: / by zero

at cn.z2huo.demo.spring.transactional.service.ServiceB.insertRequiredNewException(ServiceB.java:61)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:392)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717)
at cn.z2huo.demo.spring.transactional.service.ServiceB$$SpringCGLIB$$0.insertRequiredNewException(<generated>)
at cn.z2huo.demo.spring.transactional.service.ServiceA.a_required_new_b_required_new_exception(ServiceA.java:40)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:392)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717)
at cn.z2huo.demo.spring.transactional.service.ServiceA$$SpringCGLIB$$0.a_required_new_b_required_new_exception(<generated>)
at cn.z2huo.demo.spring.transactional.service.ServiceATest.a_required_new_b_required_new_exception(ServiceATest.java:28)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

结果:两条数据都没有插入成功。两个方法都是 REQUIRED_NEW 的,不在同一个事务当中。B方法抛出了异常,该异常没有被捕获导致A方法也抛出了异常,两个事务都发生回滚。

题外话:从上面 MyBatis 打印的日志中看出,当传播行为是 REQUIRED_NEW 时,不同于 REQUIRED,会创建两个 SqlSession

2.2.2 场景二 ServiceA方法调用ServiceB方法,B方法抛出异常但在方法内部捕获
1
2
3
4
5
6
7
8
9
10
11
12
@Transactional(propagation = Propagation.REQUIRES_NEW)  
public void a_required_new_b_required_new_exception_catch() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("required new " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("required new exception " + RandomStringUtils.randomAlphabetic(8));
serviceB.insertRequiredNewExceptionCatch(userDO2);
}
1
2
3
4
5
6
7
8
9
@Transactional(propagation = Propagation.REQUIRES_NEW)  
public void insertRequiredNewExceptionCatch(UserDO userDO) {
try {
userDAO.insert(userDO);
int a = 1 / 0;
} catch (Exception e) {
log.error("serviceB insert required new exception, catch in B");
}
}

日志输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2024-07-04T16:30:45.014+08:00  INFO 3668 --- [           main] com.zaxxer.hikari.HikariDataSource       : SpringBootDemoHikariCP - Starting...
2024-07-04T16:30:45.166+08:00 INFO 3668 --- [ main] com.zaxxer.hikari.pool.HikariPool : SpringBootDemoHikariCP - Added connection org.postgresql.jdbc.PgConnection@15a8cebd
2024-07-04T16:30:45.167+08:00 INFO 3668 --- [ main] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Start completed.
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@34cd65ac]
JDBC Connection [HikariProxyConnection@1179314953 wrapping org.postgresql.jdbc.PgConnection@15a8cebd] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808780474114121728(Long), null, required new tOFDRiyS(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@34cd65ac]
Transaction synchronization suspending SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@34cd65ac]
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3d512652]
JDBC Connection [HikariProxyConnection@999749808 wrapping org.postgresql.jdbc.PgConnection@2b170932] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808780474336419840(Long), null, required new exception OJMBgCUT(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3d512652]
2024-07-04T16:30:45.269+08:00 ERROR 3668 --- [ main] c.z.d.s.transactional.service.ServiceB : serviceB insert required new exception, catch in B
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3d512652]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3d512652]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3d512652]
Transaction synchronization resuming SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@34cd65ac]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@34cd65ac]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@34cd65ac]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@34cd65ac]

结果:两条数据都插入成功。因为两个方法都没有抛出异常,所以不会发生回滚。

2.2.3 场景三 ServiceA方法调用ServiceB方法,B方法抛出异常,异常在A方法中捕获
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Transactional(propagation = Propagation.REQUIRES_NEW)  
public void a_required_new_b_required_new_exception_catch_in_a() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("required new " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("required new exception " + RandomStringUtils.randomAlphabetic(8));
try {
serviceB.insertRequiredNewException(userDO2);
} catch (Exception e) {
log.error("serviceB insert required new exception, catch in A", e);
}
}
1
2
3
4
5
@Transactional(propagation = Propagation.REQUIRES_NEW)  
public void insertRequiredNewException(UserDO userDO) {
userDAO.insert(userDO);
int a = 1 / 0;
}

日志输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
2024-07-04T16:33:54.836+08:00  INFO 69812 --- [           main] com.zaxxer.hikari.HikariDataSource       : SpringBootDemoHikariCP - Starting...
2024-07-04T16:33:54.970+08:00 INFO 69812 --- [ main] com.zaxxer.hikari.pool.HikariPool : SpringBootDemoHikariCP - Added connection org.postgresql.jdbc.PgConnection@370c7cc5
2024-07-04T16:33:54.971+08:00 INFO 69812 --- [ main] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Start completed.
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7574d4ad]
JDBC Connection [HikariProxyConnection@1708779306 wrapping org.postgresql.jdbc.PgConnection@370c7cc5] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808781270218280960(Long), null, required new taTKXynQ(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7574d4ad]
Transaction synchronization suspending SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7574d4ad]
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@23ea8830]
JDBC Connection [HikariProxyConnection@2030748640 wrapping org.postgresql.jdbc.PgConnection@5bb2fb2b] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808781270457356288(Long), null, required new exception LJlMrPjU(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@23ea8830]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@23ea8830]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@23ea8830]
Transaction synchronization resuming SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7574d4ad]
2024-07-04T16:33:55.087+08:00 ERROR 69812 --- [ main] c.z.d.s.transactional.service.ServiceA : serviceB insert required new exception, catch in A

java.lang.ArithmeticException: / by zero
at cn.z2huo.demo.spring.transactional.service.ServiceB.insertRequiredNewException(ServiceB.java:61) ~[classes/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:392) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717) ~[spring-aop-6.1.5.jar:6.1.5]
at cn.z2huo.demo.spring.transactional.service.ServiceB$$SpringCGLIB$$0.insertRequiredNewException(<generated>) ~[classes/:na]
at cn.z2huo.demo.spring.transactional.service.ServiceA.a_required_new_b_required_new_exception_catch_in_a(ServiceA.java:67) ~[classes/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:392) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765) ~[spring-aop-6.1.5.jar:6.1.5]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717) ~[spring-aop-6.1.5.jar:6.1.5]
at cn.z2huo.demo.spring.transactional.service.ServiceA$$SpringCGLIB$$0.a_required_new_b_required_new_exception_catch_in_a(<generated>) ~[classes/:na]
at cn.z2huo.demo.spring.transactional.service.ServiceATest.a_required_new_b_required_new_exception_catch_in_a(ServiceATest.java:38) ~[test-classes/:na]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:728) ~[junit-platform-commons-1.10.2.jar:1.10.2]
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:218) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:214) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:139) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69) ~[junit-jupiter-engine-5.10.2.jar:5.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) ~[na:na]
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) ~[na:na]
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) ~[junit-platform-engine-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:198) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:169) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:93) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:58) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:141) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:57) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:63) ~[junit-platform-launcher-1.10.2.jar:1.10.2]
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57) ~[junit5-rt.jar:na]
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) ~[junit-rt.jar:na]
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) ~[idea_rt.jar:na]
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) ~[junit-rt.jar:na]
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232) ~[junit-rt.jar:na]
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55) ~[junit-rt.jar:na]

Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7574d4ad]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7574d4ad]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7574d4ad]

结果:方法A中数据插入成功。方法B中数据插入失败。两个方法都是 REQUIRED_NEW 的,属于两个不同的事务。B方法抛出了异常,该异常被方法A捕获,A方法未抛出异常。B方法的事务回滚,但是B方法的事务和A方法的事务不属于同一个物理事务,所以A方法的事务不会回滚。

2.2.4 场景四 ServiceA方法调用ServiceB方法,A方法在B方法执行完成后抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Transactional(propagation = Propagation.REQUIRES_NEW)  
public void a_required_new_exception_b_required_new() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("required new exception " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("required new " + RandomStringUtils.randomAlphabetic(8));
serviceB.insertRequiredNew(userDO2);

int a = 1/0;
}
1
2
3
4
@Transactional(propagation = Propagation.REQUIRES_NEW)  
public void insertRequiredNew(UserDO userDO) {
userDAO.insert(userDO);
}

日志输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2024-07-04T17:56:20.013+08:00  INFO 39520 --- [           main] com.zaxxer.hikari.HikariDataSource       : SpringBootDemoHikariCP - Starting...
2024-07-04T17:56:20.145+08:00 INFO 39520 --- [ main] com.zaxxer.hikari.pool.HikariPool : SpringBootDemoHikariCP - Added connection org.postgresql.jdbc.PgConnection@5ebbde60
2024-07-04T17:56:20.147+08:00 INFO 39520 --- [ main] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Start completed.
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6060146b]
JDBC Connection [HikariProxyConnection@1979980170 wrapping org.postgresql.jdbc.PgConnection@5ebbde60] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808802011772944384(Long), null, required new exception NBgHRfWz(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6060146b]
Transaction synchronization suspending SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6060146b]
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5bb2fb2b]
JDBC Connection [HikariProxyConnection@2037147839 wrapping org.postgresql.jdbc.PgConnection@56d6a1b1] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808802011940716544(Long), null, required new ezWOMCRh(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5bb2fb2b]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5bb2fb2b]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5bb2fb2b]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5bb2fb2b]
Transaction synchronization resuming SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6060146b]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6060146b]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6060146b]

java.lang.ArithmeticException: / by zero

at cn.z2huo.demo.spring.transactional.service.ServiceA.a_required_new_exception_b_required_new(ServiceA.java:116)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:392)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717)
at cn.z2huo.demo.spring.transactional.service.ServiceA$$SpringCGLIB$$0.a_required_new_exception_b_required_new(<generated>)
at cn.z2huo.demo.spring.transactional.service.ServiceATest.a_required_new_exception_b_required_new(ServiceATest.java:55)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

结果:B方法数据插入成功,A方法数据插入失败。两个事务是相互独立的,B方法的事务不会发生回滚,A方法抛出异常事务发生回滚。

2.3 MANDATORY

2.3.1 场景一 ServiceA没有事务
1
2
3
4
5
6
7
8
9
10
11
public void a_none_b_mandatory() {  
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("mandatory " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("mandatory " + RandomStringUtils.randomAlphabetic(8));
serviceB.insertMandatory(userDO2);
}
1
2
3
4
@Transactional(propagation = Propagation.MANDATORY)  
public void insertMandatory(UserDO userDO) {
userDAO.insert(userDO);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@36d582cb] was not registered for synchronization because synchronization is not active
2024-07-04T16:51:07.290+08:00 INFO 43260 --- [ main] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Starting...
2024-07-04T16:51:07.419+08:00 INFO 43260 --- [ main] com.zaxxer.hikari.pool.HikariPool : SpringBootDemoHikariCP - Added connection org.postgresql.jdbc.PgConnection@6df11e91
2024-07-04T16:51:07.421+08:00 INFO 43260 --- [ main] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Start completed.
JDBC Connection [HikariProxyConnection@1821989981 wrapping org.postgresql.jdbc.PgConnection@6df11e91] will not be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808785599885017088(Long), null, mandatory qEfDvLGe(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@36d582cb]

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:394)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:617)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:386)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717)
at cn.z2huo.demo.spring.transactional.service.ServiceB$$SpringCGLIB$$0.insertMandatory(<generated>)
at cn.z2huo.demo.spring.transactional.service.ServiceA.a_none_b_mandatory(ServiceA.java:37)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:713)
at cn.z2huo.demo.spring.transactional.service.ServiceA$$SpringCGLIB$$0.a_none_b_mandatory(<generated>)
at cn.z2huo.demo.spring.transactional.service.ServiceATest.a_none_b_mandatory(ServiceATest.java:25)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

结果:ServiceA中方法插入数据成功,ServiceB中方法插入数据失败。

2.3.2 场景二 ServiceA有事务
1
2
3
4
5
6
7
8
9
10
11
12
@Transactional(propagation = Propagation.REQUIRED)  
public void a_required_b_mandatory() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("mandatory " + RandomStringUtils.randomAlphabetic(8));
serviceB.insertMandatory(userDO2);
}
1
2
3
4
@Transactional(propagation = Propagation.MANDATORY)  
public void insertMandatory(UserDO userDO) {
userDAO.insert(userDO);
}

日志输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2024-07-04T16:57:54.447+08:00  INFO 3824 --- [           main] com.zaxxer.hikari.HikariDataSource       : SpringBootDemoHikariCP - Starting...
2024-07-04T16:57:54.594+08:00 INFO 3824 --- [ main] com.zaxxer.hikari.pool.HikariPool : SpringBootDemoHikariCP - Added connection org.postgresql.jdbc.PgConnection@250967f1
2024-07-04T16:57:54.595+08:00 INFO 3824 --- [ main] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Start completed.
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1af677f8]
JDBC Connection [HikariProxyConnection@853186557 wrapping org.postgresql.jdbc.PgConnection@250967f1] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808787308422119424(Long), null, required kKwTNKuP(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1af677f8]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1af677f8] from current transaction
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808787308598280192(Long), null, mandatory gQvAhiPf(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1af677f8]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1af677f8]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1af677f8]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1af677f8]

结果:两条数据都插入成功。

2.4 NOT_SUPPORTED

2.4.1 场景一 ServiceA方法调用ServiceB方法,A方法有事务,B方法抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
@Transactional(propagation = Propagation.REQUIRED)  
public void a_required_b_not_supported_exception() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("not supported " + RandomStringUtils.randomAlphabetic(8));
serviceB.insertNotSupportedException(userDO2);
}
1
2
3
4
5
@Transactional(propagation = Propagation.NOT_SUPPORTED)  
public void insertNotSupportedException(UserDO userDO) {
userDAO.insert(userDO);
int a = 1/0;
}

日志输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2024-07-04T17:16:52.287+08:00  INFO 64412 --- [           main] com.zaxxer.hikari.HikariDataSource       : SpringBootDemoHikariCP - Starting...
2024-07-04T17:16:52.439+08:00 INFO 64412 --- [ main] com.zaxxer.hikari.pool.HikariPool : SpringBootDemoHikariCP - Added connection org.postgresql.jdbc.PgConnection@5d7f8467
2024-07-04T17:16:52.440+08:00 INFO 64412 --- [ main] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Start completed.
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c995c5d]
JDBC Connection [HikariProxyConnection@1311933430 wrapping org.postgresql.jdbc.PgConnection@5d7f8467] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808792080898433024(Long), null, required atrRcHxo(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c995c5d]
Transaction synchronization suspending SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c995c5d]
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@650a1aff]
JDBC Connection [HikariProxyConnection@1978471002 wrapping org.postgresql.jdbc.PgConnection@653a5967] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808792081091371008(Long), null, not supported abRjYJHJ(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@650a1aff]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@650a1aff]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@650a1aff]
Transaction synchronization resuming SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c995c5d]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c995c5d]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6c995c5d]

java.lang.ArithmeticException: / by zero

at cn.z2huo.demo.spring.transactional.service.ServiceB.insertNotSupportedException(ServiceB.java:87)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:392)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717)
at cn.z2huo.demo.spring.transactional.service.ServiceB$$SpringCGLIB$$0.insertNotSupportedException(<generated>)
at cn.z2huo.demo.spring.transactional.service.ServiceA.a_required_b_not_supported_exception(ServiceA.java:63)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:392)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717)
at cn.z2huo.demo.spring.transactional.service.ServiceA$$SpringCGLIB$$0.a_required_b_not_supported_exception(<generated>)
at cn.z2huo.demo.spring.transactional.service.ServiceATest.a_required_b_not_supported_exception(ServiceATest.java:25)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

结果:A方法数据没有插入成功,B方法数据插入成功。因为B方法以非事务方法执行,插入数据之后虽然报错但是不会有回滚发生。B方法将异常抛给了A方法,A方法事务回滚。

2.5 NEVER

2.5.1 场景一 ServiceA方法调用ServiceB方法,A方法有事务
1
2
3
4
5
6
7
8
9
10
11
12
@Transactional(propagation = Propagation.REQUIRED)  
public void a_required_b_never() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("required " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("never " + RandomStringUtils.randomAlphabetic(8));
serviceB.insertNever(userDO2);
}
1
2
3
4
@Transactional(propagation = Propagation.NEVER)  
public void insertNever(UserDO userDO) {
userDAO.insert(userDO);
}

输出日志如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2024-07-04T17:37:30.192+08:00  INFO 63736 --- [           main] com.zaxxer.hikari.HikariDataSource       : SpringBootDemoHikariCP - Starting...
2024-07-04T17:37:30.343+08:00 INFO 63736 --- [ main] com.zaxxer.hikari.pool.HikariPool : SpringBootDemoHikariCP - Added connection org.postgresql.jdbc.PgConnection@3ffd4b12
2024-07-04T17:37:30.345+08:00 INFO 63736 --- [ main] com.zaxxer.hikari.HikariDataSource : SpringBootDemoHikariCP - Start completed.
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7272ee51]
JDBC Connection [HikariProxyConnection@668948486 wrapping org.postgresql.jdbc.PgConnection@3ffd4b12] will be managed by Spring
==> Preparing: insert into z2huo_user (id, user_code, user_name, valid_date, invalid_date, valid_flag, delete_flag, company_code, department_code, create_time, operate_time, create_by_code, operate_by_code) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
==> Parameters: 1808797273048219648(Long), null, required agmvRmqn(String), null, null, null, null, null, null, null, null, null, null
<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7272ee51]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7272ee51]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7272ee51]

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

at org.springframework.transaction.support.AbstractPlatformTransactionManager.handleExistingTransaction(AbstractPlatformTransactionManager.java:431)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:384)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:617)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:386)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717)
at cn.z2huo.demo.spring.transactional.service.ServiceB$$SpringCGLIB$$0.insertNever(<generated>)
at cn.z2huo.demo.spring.transactional.service.ServiceA.a_required_b_never(ServiceA.java:38)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:392)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:765)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:717)
at cn.z2huo.demo.spring.transactional.service.ServiceA$$SpringCGLIB$$0.a_required_b_never(<generated>)
at cn.z2huo.demo.spring.transactional.service.ServiceATest.a_required_b_never(ServiceATest.java:25)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

结果:两条数据都没有成功插入。A方法存在事务,B方法在执行插入操作前就抛出异常,并且将异常抛给A方法,A方法事务回滚,两条数据都没有插入成功。

2.6 NESTED

2.6.1 场景一 ServiceA方法调用ServiceB方法,B方法抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
@Transactional(propagation = Propagation.NESTED)  
public void a_nested_b_nested_exception() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("nested " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("nested exception " + RandomStringUtils.randomAlphabetic(8));
serviceB.insertNestedException(userDO2);
}
1
2
3
4
5
@Transactional(propagation = Propagation.NESTED)  
public void insertNestedException(UserDO userDO) {
userDAO.insert(userDO);
int a = 1/0;
}

结果:两条数据都插入失败。

2.6.2 场景二 ServiceA方法调用ServiceB方法,B方法抛出异常但在方法内部捕获
1
2
3
4
5
6
7
8
9
10
11
12
@Transactional(propagation = Propagation.NESTED)  
public void a_nested_b_nested_exception_catch() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("nested " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("nested exception " + RandomStringUtils.randomAlphabetic(8));
serviceB.insertNestedExceptionCatch(userDO2);
}
1
2
3
4
5
6
7
8
9
@Transactional(propagation = Propagation.NESTED)  
public void insertNestedExceptionCatch(UserDO userDO) {
try {
userDAO.insert(userDO);
int a = 1/0;
} catch (Exception e) {
log.error("serviceB insert nested exception, catch in B", e);
}
}

结果:两条数据都插入成功。

2.6.3 场景三 ServiceA方法调用ServiceB方法,B方法抛出异常,异常在A方法中捕获
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Transactional(propagation = Propagation.NESTED)  
public void a_nested_b_nested_exception_catch_in_a() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("nested " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("nested exception " + RandomStringUtils.randomAlphabetic(8));
try {
serviceB.insertNestedException(userDO2);
} catch (Exception e) {
log.error("serviceB insert nested exception, catch in A", e);
}
}
1
2
3
4
5
@Transactional(propagation = Propagation.NESTED)  
public void insertNestedException(UserDO userDO) {
userDAO.insert(userDO);
int a = 1/0;
}

结果:A方法插入数据成功。B方法插入数据失败。

2.6.4 场景四 ServiceA方法调用ServiceB方法,A方法在B方法执行完成后抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Transactional(propagation = Propagation.NESTED)  
public void a_nested_exception_b_nested() {
UserDO userDO = new UserDO();
userDO.setId(IdGeneratorUtils.nextId());
userDO.setUserName("nested " + RandomStringUtils.randomAlphabetic(8));
userDAO.insert(userDO);

UserDO userDO2 = new UserDO();
userDO2.setId(IdGeneratorUtils.nextId());
userDO2.setUserName("nested exception " + RandomStringUtils.randomAlphabetic(8));
serviceB.insertNested(userDO2);

int a = 1/0;
}
1
2
3
4
@Transactional(propagation = Propagation.NESTED)  
public void insertNested(UserDO userDO) {
userDAO.insert(userDO);
}

结果:两条数据都插入失败。

3、总结

3.1 REQUIREDREQUIRES_NEWNESTED 三者区别

内部事务回滚时:

  • REQUIRED,外部事务回滚
  • NESTED,外部事务不回滚
  • REQUIRES_NEW,外部事务不回滚

外部事务回滚时:

  • REQUIRED,内部事务回滚
  • NESTED,内部事务回滚
  • REQUIRES_NEW,内部事务不回滚

相关链接

Transaction Propagation :: Spring Framework

OB tags

#事务 #Spring