在 Spring Boot 开发中,事务是一个至关重要的概念,尤其是在涉及多层业务逻辑或者多个数据库操作时。Spring 提供了强大的事务管理功能,使得开发者可以方便地控制事务的行为。事务传播机制作为 Spring 事务管理的一部分,是 Spring 事务管理中一个非常重要的概念。
本文将介绍 Spring Boot 中事务传播机制的原理及其常用配置,以帮助开发者更好地理解事务传播的工作方式。
事务传播机制定义了在多个方法中调用事务时,事务的行为是如何传播的。换句话说,它决定了一个事务方法在被另一个方法调用时应该如何处理事务的开启、提交、回滚等操作。
事务传播机制通过 @Transactional 注解的 propagation 属性来配置,它有多个传播行为,开发者可以根据具体的需求来选择合适的传播方式。常见的传播行为包括:
传播行为: 如果当前没有事务,则创建一个新的事务;如果当前已经存在事务,则加入到现有事务中。
应用场景: 这是最常用的传播行为,通常在业务方法调用中使用,确保调用方法的一致性。
1 2 3 4 5 6 7 8 9 |
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { // 业务逻辑 }
@Transactional(propagation = Propagation.REQUIRED) public void methodB() { // 业务逻辑 } |
传播行为: 总是新建一个事务。如果当前有事务存在,则将当前事务挂起,等新事务提交或回滚后再恢复当前事务。
应用场景: 当我们希望某个方法独立于当前事务进行处理,通常用于一些不希望受到外部事务影响的操作,例如日志记录、通知等。
1 2 3 4 |
@Transactional(propagation = Propagation.REQUIRES_NEW) public void methodC() { // 业务逻辑 } |
传播行为: 如果当前有事务,则加入到现有事务中;如果当前没有事务,则以非事务方式执行。
应用场景: 当方法支持事务,但不强制要求事务存在时,可以使用 SUPPORTS。例如,一些方法可能不需要事务,但如果存在事务,它们会加入其中。
1 2 3 4 |
@Transactional(propagation = Propagation.SUPPORTS) public void methodD() { // 业务逻辑 } |
传播行为: 如果当前有事务,则加入到现有事务中;如果没有事务,则抛出异常。
应用场景: 如果方法依赖事务执行,但又不希望自行创建事务,则可以使用 MANDATORY。如果没有现有事务,将抛出 TransactionRequiredException 异常。
1 2 3 4 |
@Transactional(propagation = Propagation.MANDATORY) public void methodE() { // 业务逻辑 } |
传播行为: 如果当前有事务,则将当前事务挂起,并以非事务方式执行方法。
应用场景: 当某个方法不希望参与事务操作时,可以使用 NOT_SUPPORTED,例如一些查询操作,它们无需事务支持。
1 2 3 4 |
@Transactional(propagation = Propagation.NOT_SUPPORTED) public void methodF() { // 业务逻辑 } |
传播行为: 如果当前有事务,则抛出异常;如果没有事务,则以非事务方式执行。
应用场景: 当一个方法不允许在事务中运行时使用。例如,一些特定的检查方法,它们要求事务完全不存在。
1 2 3 4 |
@Transactional(propagation = Propagation.NEVER) public void methodG() { // 业务逻辑 } |
传播行为: 如果当前没有事务,则新建一个事务;如果当前已有事务,则在当前事务中嵌套一个事务。嵌套事务可以独立提交或回滚。
应用场景: 如果你希望事务能够嵌套,并且在嵌套事务回滚时不会影响外部事务的提交,可以使用 NESTED。
1 2 3 4 |
@Transactional(propagation = Propagation.NESTED) public void methodH() { // 业务逻辑 } |
Spring 的事务传播机制实际上是通过 AOP(面向切面编程)来实现的。Spring 在运行时会生成一个代理对象(通常是 JDK 动态代理或 CGLIB 代理),在事务方法执行时,代理会负责判断事务的传播行为并根据行为决定是否开启新的事务或加入到现有事务中。
假设方法 A 调用方法 B,方法 B 使用 REQUIRES_NEW 传播行为:
这就是事务传播机制在嵌套调用中的行为。
在 Spring 中,事务传播机制的实现依赖于 AOP(面向切面编程),而 AOP 只会应用于通过 Spring 管理的 bean。如果我们直接调用同一个类中的方法(即同一个实例的方法),则事务传播机制可能会失效,因为 Spring 的代理对象并未被应用到这些内部方法调用中。以下是关于事务传播机制的一些代码示例,并且会展示事务传播机制失效的场景。
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 |
@Service public class UserService {
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { System.out.println("methodA: 开始事务"); // 模拟数据库操作 try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodA: 完成事务"); }
@Transactional(propagation = Propagation.REQUIRED) public void methodB() { System.out.println("methodB: 开始事务"); // 模拟数据库操作 try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodB: 完成事务"); }
public void testRequiredPropagation() { methodA(); // 这会触发事务的传播机制 methodB(); // 也会加入到当前事务中 } } |
预期输出:
methodA: 开始事务
methodA: 完成事务
methodB: 开始事务
methodB: 完成事务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Service public class UserService {
@Transactional(propagation = Propagation.REQUIRES_NEW) public void methodC() { System.out.println("methodC: 开始新事务"); // 模拟数据库操作 try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodC: 完成新事务"); }
public void testRequiresNewPropagation() { methodC(); // 新事务会独立执行 } } |
预期输出:
methodC: 开始新事务
methodC: 完成新事务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Service public class UserService {
@Transactional(propagation = Propagation.SUPPORTS) public void methodD() { System.out.println("methodD: 支持事务(如果有)"); // 模拟数据库操作 try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodD: 完成"); }
public void testSupportsPropagation() { methodD(); // 如果存在事务,方法会加入到当前事务中 } } |
预期输出:
如果没有事务:
methodD: 支持事务(如果有)
methodD: 完成
如果有事务:
methodD: 支持事务(如果有)
methodD: 完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Service public class UserService {
@Transactional(propagation = Propagation.MANDATORY) public void methodE() { System.out.println("methodE: 必须加入事务"); // 模拟数据库操作 try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodE: 完成事务"); }
public void testMandatoryPropagation() { methodE(); // 调用时必须有事务存在,否则会抛出异常 } } |
预期输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Service public class UserService {
@Transactional(propagation = Propagation.NOT_SUPPORTED) public void methodF() { System.out.println("methodF: 当前事务被挂起"); // 模拟数据库操作 try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodF: 完成"); }
public void testNotSupportedPropagation() { methodF(); // 如果有事务,会被挂起,执行非事务操作 } } |
预期输出:
如果方法在事务中调用,则事务会被挂起,并执行非事务操作:
methodF: 当前事务被挂起
methodF: 完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Service public class UserService {
@Transactional(propagation = Propagation.NESTED) public void methodG() { System.out.println("methodG: 开始嵌套事务"); // 模拟数据库操作 try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodG: 完成嵌套事务"); }
public void testNestedPropagation() { methodG(); // 方法会启动一个嵌套事务 } } |
预期输出:
methodG: 开始嵌套事务
methodG: 完成嵌套事务
如果我们在一个类的实例中直接调用另一个被 @Transactional 注解的方法,事务传播机制可能会失效,因为事务代理是基于 Spring AOP 的,而 AOP 仅对外部方法调用起作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@Service public class UserService {
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { System.out.println("methodA: 开始事务"); // 模拟数据库操作 try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodA: 完成事务"); }
public void methodB() { System.out.println("methodB: 事务不生效(直接调用)"); methodA(); // 直接调用 methodA(),事务不会传播 } } |
问题:
methodB() 会直接调用 methodA(),但是因为事务注解依赖 AOP 代理,而 methodB() 没有通过 Spring 代理调用 methodA(),因此事务不会生效。
预期输出:
methodB: 事务不生效(直接调用)
methodA: 开始事务
methodA: 完成事务
事务应该在 methodA() 中生效,但因为是直接调用,所以没有生效。
为了让事务传播机制生效,方法应该通过 Spring 容器中的代理对象进行调用,可以通过 @Autowired 注入当前类实例并调用其方法,或者通过使用外部类实例来间接调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@Service public class UserService {
@Autowired private UserService self; // 注入当前类的代理实例
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { System.out.println("methodA: 开始事务"); // 模拟数据库操作 try { Thread.sleep(1000); // 模拟耗时操作 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("methodA: 完成事务"); }
public void methodB() { System.out.println("methodB: 事务会生效(通过代理调用)"); self.methodA(); // 通过代理调用 methodA() } } |
Spring Boot 的事务传播机制为开发者提供了灵活的事务管理方式,确保在复杂的业务逻辑中能够精准地控制事务的行为。通过合理选择事务传播行为,我们可以在多层业务逻辑中实现事务的一致性和隔离性。