Java Spring Transactional 自调用为什么不会开启事务
来源:17golang原创
时间:2026-09-07 12:53:38 129浏览 收藏
很多“@Transactional 没生效”的问题,根源不是注解写错,而是调用没有经过 Spring 代理。最典型的代码是:batchCreate() 在同一个类里调用 this.processOrder()。默认的 proxy 模式只拦截从代理对象进入的外部调用,类内的 this 调用直接落到目标对象,因此不会重新创建事务边界。
- Spring 默认用 AOP 代理处理事务,代理只看得到“从外部进来”的方法调用。
- 自调用不等于没有任何事务:如果外层已有事务,内部方法可能加入外层事务;但它自己的传播、隔离或回滚入口不会被重新应用。
- 优先拆分事务服务;确实不能拆分时再注入代理引用,AspectJ 织入适合有明确基础设施能力的项目。
为什么 this.processOrder() 不会走事务代理
可以把一个 Spring Bean 看成两层:容器暴露的是 OrderService代理,业务代码真正执行的是 OrderService目标对象。外部类调用代理时,事务拦截器有机会读取 @Transactional;进入目标对象后,this.batchCreate() 再调用 this.processOrder(),只是普通的 Java 虚方法调用。
下面这段代码的关键问题在调用路径,而不在注解位置:
@Service
public class OrderService {
@Transactional
public void batchCreate() {
createOrder();
// this 指向目标对象本身,不会再次经过 Spring 代理
this.processOrder();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processOrder() {
// 这里声明了新事务,但自调用时拦截器没有机会读取它
markOrderProcessed();
}
}
因此,如果 batchCreate() 是从代理进入的,processOrder() 可能仍在外层事务里执行;如果外层没有事务,它也不会因为自己的注解而单独开启事务。尤其是 REQUIRES_NEW、独立只读设置或单独的回滚规则,不能靠这次自调用自动生效。

先用调用路径定位问题
排查时先问“谁持有这个对象引用”,不要先改传播级别。可以按下面的表快速判断:
| 现象 | 优先检查 | 说明 |
|---|---|---|
| 外部调用有事务,内部注解像没变化 | 是否用了 this.method() | 内部调用绕过代理,不能重新应用方法级属性 |
| 从 Controller 调用也没有事务 | Bean 是否由 Spring 管理、事务管理是否启用 | 对象若由 new 创建,容器无法为它建立代理 |
| 事务存在但没有回滚 | 异常类型与回滚规则 | 默认对 RuntimeException 和 Error 回滚,检查异常默认不回滚 |
public void logTransactionState(String scene) {
// 只用于定位当前线程是否已经处于实际事务中
boolean active = TransactionSynchronizationManager.isActualTransactionActive();
log.info("scene={}, transactionActive={}", scene, active);
}
这个状态只能证明“当前线程是否已有实际事务”,不能证明某个方法的 REQUIRES_NEW 是否生效。要确认代理边界,应该同时检查注入对象的类型,并把调用入口和异常路径记录下来。
三种修复方式怎么选
最稳妥的做法是把真正需要独立事务的方法拆到另一个 Bean,让调用自然跨过 Bean 边界:
@Service
public class OrderService {
private final OrderTxService orderTxService;
public OrderService(OrderTxService orderTxService) {
this.orderTxService = orderTxService;
}
public void batchCreate() {
createOrder();
// 跨 Bean 调用,OrderTxService 的代理可以接管事务边界
orderTxService.processOrder();
}
}
@Service
public class OrderTxService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processOrder() {
// 独立事务中的写操作
markOrderProcessed();
}
}
如果暂时无法拆分,可以注入当前 Bean 的代理引用,再通过代理调用;这种写法会引入自依赖和生命周期理解成本,使用 @Lazy 能避免部分初始化环问题:
@Service
public class OrderService {
private final OrderService self;
public OrderService(@Lazy OrderService self) {
// Spring 注入的是代理引用,而不是 this
this.self = self;
}
public void batchCreate() {
// 通过代理进入,事务拦截器才有机会执行
self.processOrder();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processOrder() {
markOrderProcessed();
}
}
第三种是 AspectJ 模式。它通过织入改变方法调用的拦截方式,可以覆盖自调用,但需要 spring-aspects、编译期或加载期织入等配套设施,排查成本也更高。不要为了修一处自调用就直接切换全局模式。

用回滚场景做反向验证
不要只看日志里出现了“调用成功”。准备一个可识别的写入,再主动抛出运行时异常,观察数据库状态:
- 从 Spring 注入的外部 Bean 调用入口,不要在测试里直接
new OrderService()。 - 让
processOrder()写入一条带测试标记的记录,然后抛出IllegalStateException。 - 如果声明的是
REQUIRES_NEW,分别测试外层成功、外层失败和内层失败三种组合。 - 检查数据库记录、异常类型和事务活动日志,三者要能互相解释。
验证结果必须结合传播属性阅读:拆分 Bean 后,内层调用确实重新经过代理;但默认回滚规则仍然适用,检查异常是否回滚不能靠“已经走过代理”来推断。
常见问题
把 @Transactional 放到接口上就能解决自调用吗?
不能。接口注解与自调用是两个问题,调用仍然使用 this 时,依旧没有经过代理。事务方法更适合标在具体类的方法上,并先确认调用入口是容器管理的 Bean。
外层已经有事务时,自调用会完全失效吗?
不一定。内部代码可能继续运行在外层事务中,所以“数据库操作成功”不能证明内部注解生效;需要单独验证传播、隔离和回滚边界。
什么时候应该用 AspectJ?
当项目已经具备稳定的织入构建或运行基础设施,且确实需要覆盖类内调用时再考虑。普通业务服务通常优先拆分 Bean,边界更清楚,也更容易测试。
Spring 官方文档将默认事务模式定义为基于代理的拦截:只有经代理进入的调用才会触发事务建议,自调用不会自动产生新的事务边界。遇到类似问题,先画出“调用者—代理—目标对象”的关系,再选择修复方式,通常比继续调整注解参数更快。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
367 收藏
-
文章 · java教程 | 3小时前 | Java · httpclient · BodySubscriber · 响应体大小 · java httpclient BodyHandler BodyHandlers.limiting263 收藏
-
157 收藏
-
373 收藏
-
366 收藏
-
415 收藏
-
310 收藏
-
351 收藏
-
311 收藏
-
188 收藏
-
136 收藏
-
116 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习