@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
@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);
}
}
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[正常提交事务]
// 配置两个数据源 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;
// 发生异常,两个数据源全部回滚
}
}