文章目录
在 Spring 中,事务管理是通过 AOP(面向切面编程) 实现的,它基于 代理模式 来处理事务的开启、提交、回滚等操作。在日常开发中,我们经常会用到 @Transactional 注解来管理方法的事务。然而,很多开发者在使用事务时,遇到一个常见的问题:当一个事务方法内部调用另一个事务方法时,事务是否会生效?
问题背景:
假设我们有一个类
UserService,它有两个方法methodA()和methodB(),并且这两个方法都被标注了@Transactional注解。问题是:如果methodA()调用methodB(),事务是否会生效?为什么呢?如果不生效,我们该如何确保事务生效?
Spring 事务传播行为 + 事务失效原因 + 传播行为为什么不用其他模式
一、事务管理和 AOP 代理
Spring 事务管理是如何工作的?
在 Spring 中,事务管理是通过 AOP(面向切面编程) 机制来实现的。具体来说,Spring 会通过 代理模式 来为需要进行事务管理的方法生成代理对象,这个代理对象会在方法执行前后执行一些额外的逻辑(如开启事务、提交事务、回滚事务等)。
- 当方法上标注了
@Transactional注解时,Spring 会自动为该方法生成一个 代理对象,并且事务控制(如开启、提交、回滚)是通过该代理对象来管理的。 - 代理对象会拦截对目标方法的调用,并在目标方法执行前后插入事务的控制逻辑。
AOP 代理的两种方式:
- JDK 动态代理:基于接口的代理。目标类必须实现接口,Spring 会创建一个接口的代理对象。
- CGLIB 代理:基于类的代理。即使目标类没有实现接口,Spring 也可以通过字节码技术为目标类创建一个代理对象。
关键点:
- 代理对象 只会在 外部方法调用时生效。当方法是通过代理对象被调用时,Spring 的事务管理才会插入事务逻辑。
- 内部方法调用 由于不经过代理对象,因此事务不会生效。
二、问题的根本原因
事务管理不会生效的原因 是 内部方法调用不会经过代理。
假设我们有如下代码:
java
@Service
public class UserService {
@Transactional
public void methodA() {
System.out.println("Inside methodA, transaction started.");
// 调用同一类中的 methodB
this.methodB(); // 事务不会生效
System.out.println("Inside methodA, transaction committed.");
}
@Transactional
public void methodB() {
System.out.println("Inside methodB, transaction started.");
// 模拟数据库操作
System.out.println("Inside methodB, transaction committed.");
}
}
在这个例子中,methodA() 调用 methodB(),并且这两个方法都标注了 @Transactional 注解。理论上,我们希望 methodB() 也在 methodA() 的事务中执行。
问题所在:
methodA()调用this.methodB(),即内部方法调用。- Spring 的事务管理是基于代理的 ,事务的控制依赖于 代理对象 。在这种情况下,
methodB()是直接通过this调用的,因此事务管理的代理不会生效。 - 只有外部调用才会触发代理 ,所以即使
methodA()和methodB()上都标注了@Transactional注解,methodB()的事务并不会生效 ,因为它是通过this调用的。
三、如何使事务生效
为了确保 methodB() 的事务管理生效,我们需要确保 methodB() 是通过 外部调用 的方式进行调用,这样才能触发事务代理。通常有以下两种方法来解决这个问题:
方法一:将 methodB() 移到不同的类中
将 methodB() 移到不同的类中,并通过 依赖注入 的方式调用 methodB(),这样可以确保 Spring 会通过代理来管理事务。
java
@Service
public class UserServiceA {
@Autowired
private UserServiceB userServiceB; // 依赖注入 UserServiceB
@Transactional
public void methodA() {
System.out.println("Inside methodA, transaction started.");
// 调用 UserServiceB 中的 methodB,这样会通过代理生效
userServiceB.methodB(); // 事务会生效
System.out.println("Inside methodA, transaction committed.");
}
}
@Service
public class UserServiceB {
@Transactional
public void methodB() {
System.out.println("Inside methodB, transaction started.");
// 模拟数据库操作
System.out.println("Inside methodB, transaction committed.");
}
}
为什么这样可以生效?
- 在
UserServiceA中,methodA()通过 依赖注入 调用了UserServiceB的methodB()方法。由于methodB是通过代理对象进行调用的,因此事务会在methodB()中生效。
方法二:使用 @Transactional 注解的 propagation 属性
另一种方法是利用 @Transactional 的 propagation 属性来调整事务的传播行为。默认情况下,事务传播行为是 REQUIRED,即如果方法 A 已经有一个事务,那么方法 B 也会在同一个事务中执行。但这种方式 仅适用于外部方法调用,而在同一类中的方法内部调用,事务管理仍然不会生效。
java
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// methodB 的事务管理
}
但如果 methodB() 是 同一类内部调用 的,Spring 仍然不会创建事务代理,事务不会生效。
四、总结
-
为什么事务不生效 :当你在同一个类中调用
@Transactional标注的方法时,事务 不会生效 ,因为 Spring 使用的事务代理是基于 AOP(代理模式) 的,而 内部调用不会经过代理 。事务代理只会在 外部方法调用 时生效。 -
如何确保事务生效:
- 将
@Transactional标注的方法放到不同的类中,并通过 依赖注入 来调用。这样方法的调用会通过代理对象,事务管理会生效。 - 如果不希望将方法放到不同的类中,可以使用
@Transactional的传播属性,但这仍然仅适用于外部调用。
- 将
Spring 的事务管理依赖于 AOP 代理来控制事务的开始、提交和回滚,因此只有在通过代理调用方法时,事务才能生效。