跨类调用下,同类方法循环调用@Transactional方法的事务创建规则咨询
首先明确结论:你当前的代码结构下,每次循环调用MethodB时不会自动创建新事务——甚至如果MethodB的事务注解完全不生效,这是由Spring事务的动态代理机制决定的。
为什么会这样?
Spring的@Transactional注解是基于动态代理实现的:只有当你通过Spring容器管理的代理对象调用方法时,事务切面才会拦截方法调用,根据注解配置创建或加入事务。但当你在B类的MethodA中直接调用同类的MethodB时,本质是this.MethodB(),这里的this是B类的原始对象,而非Spring生成的代理对象,所以MethodB上的@Transactional注解根本不会被Spring的事务管理器处理,自然无法创建新事务。
哪怕MethodA本身带有事务,内部调用MethodB时也只会沿用当前事务,不会开启新的独立事务,这和你调研到的结论一致。
如何实现每次循环都创建新事务?
有两种常用方案,你可以根据业务情况选择:
方案1:将MethodB迁移到其他类(最推荐)
这是最清晰、最符合Spring事务设计理念的做法。比如新建一个C类,把MethodB的逻辑迁移过去,然后在B类中注入C类的实例,在循环中调用C类的MethodB。
示例代码:
// 新建C类 @Component public class C { @Transactional(propagation = Propagation.REQUIRES_NEW) // 明确要求新建事务 public void methodB() { // 你的业务逻辑 } } // 修改B类 @Component public class B { @Autowired private C c; public void methodA() { for (int i = 0; i < N; i++) { c.methodB(); // 每次调用都是通过代理对象,会创建新事务 } } }
这里建议给MethodB加上propagation = Propagation.REQUIRES_NEW,明确要求每次调用都开启新事务,避免受调用方事务的影响。
方案2:在B类内部获取代理对象调用MethodB(适合不想迁移代码的场景)
如果你不想迁移方法,可以通过Spring的API获取B类的代理对象,然后用代理对象调用MethodB。有两种方式:
- 方式一:注入ApplicationContext,获取代理对象
@Component public class B { @Autowired private ApplicationContext context; public void methodA() { B proxyB = context.getBean(B.class); for (int i = 0; i < N; i++) { proxyB.methodB(); // 通过代理对象调用,触发事务 } } @Transactional(propagation = Propagation.REQUIRES_NEW) public void methodB() { // 业务逻辑 } }
- 方式二:使用AopContext获取当前代理(需要开启expose-proxy)
首先在启动类上添加@EnableAspectJAutoProxy(exposeProxy = true),然后修改B类:
@Component public class B { public void methodA() { B proxyB = (B) AopContext.currentProxy(); for (int i = 0; i < N; i++) { proxyB.methodB(); } } @Transactional(propagation = Propagation.REQUIRES_NEW) public void methodB() { // 业务逻辑 } }
这种方式虽然不用迁移代码,但会让代码耦合Spring的AOP API,可读性和可维护性不如方案1,所以更推荐方案1。
最后提醒
无论选择哪种方案,都要确保MethodB的事务传播属性设置为REQUIRES_NEW,这样才能保证每次调用都会开启一个独立的新事务,不受外部事务的影响。
内容的提问来源于stack exchange,提问作者frankdart

