核心背景:spring 声明式事务基于 aop 动态代理实现,在分布式事务场景下,经常出现 @transactional 注解不生效,数据库不回滚问题。本文从代理机制、异常捕获、多数据源三个根因,给出可直接落地修复代码、问题分析、mermaid架构流程图。
一、三大失效根因总览
- 代理机制问题:类内部方法调用,不走aop代理对象,
@transactional完全失效(最常见) - 异常捕获问题:业务手动 try‑catch 吃掉异常,事务切面无法感知异常,不会触发回滚;或者抛出非受检异常以外异常
- 多数据源分布式事务问题:单数据源本地事务无法跨多个数据库,普通
@transactional只能管控当前数据源,其他库sql异常无法回滚
二、问题1:代理机制失效(内部方法调用)
原理
spring事务是aop生成代理对象执行事务增强;this.xxx()内部调用,调用的是原始对象,不是代理对象,事务增强逻辑不会执行。
错误代码(失效示例)
@service
public class orderservice {
@transactional(rollbackfor = exception.class)
public void createorder(){
// 内部调用,this调用原始对象,事务注解无效
this.insertorderdb();
}
public void insertorderdb(){
// db操作
ordermapper.insert(new order());
int i = 1/0; // 抛出异常,不会回滚!
}
}
修复方案两种可选
方案a:自我注入获取代理对象(推荐,无侵入)
@service
public class orderservice {
// 自引用拿到代理对象
@autowired
private orderservice selfproxy;
public void createorder(){
// 使用代理对象调用事务方法,aop增强生效
selfproxy.insertorderdb();
}
@transactional(rollbackfor = exception.class)
public void insertorderdb(){
ordermapper.insert(new order());
int i = 1/0;
}
}
方案b:拆分方法到独立service类
把带事务的方法抽取到另一个service,跨类调用天然走代理。
mermaid流程图:代理失效对比
flowchart lr
a[controller调用orderservice.createorder] --> b{调用对象?}
subgraph ❌错误:this内部调用
b -->|this原始对象| c[执行createorder]
c --> d[this.insertorderdb]
d --> e[直接执行业务逻辑<br/>无aop事务拦截器]
e --> f[抛出异常]
f --> g[事务不会回滚]
end
subgraph ✅正确:代理对象调用
b -->|代理proxy对象| h[执行createorder]
h --> i[selfproxy.insertorderdb]
i --> j[aop事务拦截器进入<br/>开启事务]
j --> k[执行业务逻辑抛出异常]
k --> l[拦截器捕获异常触发回滚]
end
三、问题2:try‑catch捕获异常,事务无法回滚
原理
@transactional回滚依赖aop拦截器捕获方法抛出的异常;如果业务代码try‑catch吞掉异常,方法正常返回,切面感知不到异常,不会回滚。
错误代码(失效示例)
@service
public class payservice {
@transactional(rollbackfor = exception.class)
public void pay(){
try {
paymapper.insertpaylog();
int i = 1/0;
}catch (exception e){
// 异常被吃掉,方法无向外抛出异常 → 事务不回滚
log.error("异常",e);
}
}
}
修复方案
- catch中重新抛出异常;
- 或者手动编程式事务触发回滚
transactionaspectsupport.currenttransactionstatus().setrollbackonly();
@service
public class payservice {
@transactional(rollbackfor = exception.class)
public void pay(){
try {
paymapper.insertpaylog();
int i = 1/0;
}catch (exception e){
log.error("支付异常",e);
// 方式1:手动标记回滚,不抛出异常也可以回滚
transactionaspectsupport.currenttransactionstatus().setrollbackonly();
// 方式2:向外抛出异常,aop切面捕获做回滚
// throw new runtimeexception(e);
}
}
}
注意:
rollbackfor = exception.class必须配置,spring默认只对runtimeexception、error回滚。
mermaid流程:异常捕获事务流转
flowchart td
a[进入@transactional方法] --> b[aop开启事务]
b --> c[执行业务代码]
c --> d{发生异常?}
d --是--> e{是否被try‑catch吃掉?}
e --✅向外抛出异常--> f[aop拦截捕获异常 → 事务回滚]
e --❌catch吞掉异常,方法正常返回--> g[aop无感知 → 事务提交,数据脏写]
e --catch内setrollbackonly()--> h[标记事务回滚,最终回滚]
d --否--> i[正常提交事务]
四、问题3:多数据源分布式场景,普通声明式事务失效
场景:业务同时操作 order库、pay库两个不同数据源,@transactional只能控制其中一个数据源;一个库成功,另一个库失败,出现数据不一致。
普通spring事务是单数据源本地事务,无法跨库。
错误示例
// 配置两个数据源 ds1(order库) ds2(pay库)
@transactional(rollbackfor = exception.class)
public void multidbbiz(){
// 操作数据源1 order库
ordermapper.insertorder();
// 操作数据源2 pay库
paymapper.insertpay();
int i = 1/0;
// 此时只会回滚当前绑定数据源,另一个数据源已经提交,数据错乱
}
修复方案:3种落地选型
- 方案1:seata at模式(推荐,分布式事务)
- 方案2:jta+atomikos 两阶段提交
- 方案3:saga模式,业务补偿(长事务场景)
seata at最简代码示例
- 引入seata依赖,配置注册中心、数据源代理
@service
public class multidbservice {
// 使用seata全局事务注解,替代普通@transactional
@globaltransactional
public void multidbbiz(){
ordermapper.insertorder();
paymapper.insertpay();
int i = 1/0;
// 发生异常,两个数据源全部回滚
}
}
mermaid架构图 seata at分布式事务

五、总结对照表
| 失效场景 | 根因 | 修复要点 |
|---|---|---|
| 内部this调用方法 | aop代理对象未使用 | 自我注入代理对象 / 拆分service |
| try‑catch吞异常 | 切面捕获不到异常 | 重抛异常 / setrollbackonly() |
| 多数据源跨库操作 | 本地事务只能单库 | seata @globaltransactional / jta / saga补偿 |
关键提醒:@transactional 本质只是aop代理增强,不是魔法,只有代理对象调用、异常向外抛出、单数据源前提下才能生效;分布式场景必须引入分布式事务组件。
以上就是分布式事务下spring声明式事务失效的3种修复方案的详细内容,更多关于spring声明式事务失效修复的资料请关注代码网其它相关文章!
发表评论