Spring 内部调用,事务真会失效吗?

Spring 事务失效问题深入分析:非代理内部调用

先说结论。非Spring代理的内部调用,事务本身不会失效。但@Transactional注解会因为代理失效而失效。

代码示例

同一个类TransactionUserService中。

@Transactional
public void a() {
    userMapper.insert("A");
    b();
    throw new RuntimeException("触发回滚");
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void b() {
    userMapper.insert("B");
}

结果:A、B记录均回滚。


你的预期是否是:A 回滚,B 提交?

如果是。此时你应该在思考,b()的事务已失效,为什么b()还会被回滚?但其实此时b()的事务并未失效,失效的只是 @Transactional(propagation = Propagation.REQUIRES_NEW) 注解本身。我们接着往下验证。


通过事务信息验证

a()b() 中使用TransactionSynchronizationManager.*getResourceMap*() 打印事务信息:

// a() 中打印
System.out.println(TransactionSynchronizationManager.getResourceMap());

// b() 中打印
System.out.println(TransactionSynchronizationManager.getResourceMap());

打印结果:

  • a() 事务信息
    {{
    	CreateTime:"2025-02-14 14:10:00",
    	ActiveCount:1,
    	PoolingCount:0,
    	CreateCount:1,
    	DestroyCount:0,
    	CloseCount:0,
    	ConnectCount:1,
    	Connections:[
    	]
    }=org.springframework.jdbc.datasource.ConnectionHolder@4f41cc10}
    
  • b() 事务信息
    {org.apache.ibatis.session.defaults.DefaultSqlSessionFactory@3ffd6f44=org.mybatis.spring.SqlSessionHolder@48585aba, {
    	CreateTime:"2025-02-14 14:10:00",
    	ActiveCount:1,
    	PoolingCount:0,
    	CreateCount:1,
    	DestroyCount:0,
    	CloseCount:0,
    	ConnectCount:1,
    	Connections:[
    	]
    }=org.springframework.jdbc.datasource.ConnectionHolder@4f41cc10}
    

发现了吗?a()b()ConnectionHolder 对象的内存地址一模一样:@4f41cc10


为什么?我们首先理清楚两点:

  1. 事务的本质

    Spring事务基于数据库连接,通过 ThreadLocal 将连接绑定到当前线程,同一线程内的操作默认共享事务。

  2. AOP代理机制

    事务的传播行为(如 REQUIRES_NEW)依赖代理对象拦截方法调用。内部直接调用会绕过代理,导致传播行为失效!

事务存在 ≠ 传播行为生效。所以说内部调用时,即使@Transactional 失效,该方法仍共享父方法事务。


最后,而要使@Transactional(propagation = Propagation.REQUIRES_NEW)生效,只需简单改为由 spring代理的bean对象 调用内部方法即可。

applicationContext.getBean(TransactionUserService.class).b();

Comments

Popular posts from this blog

CompletableFuture 异常处理实践:避免线程池耗尽

优雅摆脱冗长if-else:轻量级策略模式在Java中的简明实践