一、问题背景
线上用户反馈订单数据不对,对方查询我们的订单状态接口时,同一个订单的不同乘机人,给了不同的状态。
排查发现:事务没生效,数据只更新了一半
二、问题代码
@Service public class OrderServiceImpl implements OrderService {
@Override
public void createOrder(OrderDTO dto) {
// 1. 创建订单
orderMapper.insert(order);
// 2. 更新库存 - 事务失效的位置
// 同一个类内部方法调用,绕过了代理!
boolean success = updateStock(dto.getSkuId());
if (!success) {
throw new RuntimeException("库存不足");
}
}
// 这个方法上的@Transactional不起作用!
@Transactional(rollbackFor = Exception.class)
public boolean updateStock(Long skuId) {
// 更新库存
stockMapper.deduct(skuId);
return true;
}
}
三、问题根因
用图解释: 正常调用(经过代理):
OrderServiceImpl.createOrder()
→ 代理对象(事务开启)
→ 目标对象方法
→ 代理对象(事务提交)
✅ 事务生效
内部调用(绕过代理):
OrderServiceImpl.createOrder()
→ this.updateStock() // 直接调目标对象
→ 事务注解无效
❌ 事务失效
关键点:
- Spring 事务基于 AOP 动态代理(ProxyOrderService)
- this.xxx() 是直接调用目标对象,不是代理对象
- 所以 @Transactional 注解被跳过
四、解决方案
方案 1:注入自己 @Service public class OrderServiceImpl implements OrderService {
@Autowired
private OrderService self; // 注入代理对象
@Override
public void createOrder(OrderDTO dto) {
orderMapper.insert(order);
// 用代理对象调用,走事务
boolean success = self.updateStock(dto.getSkuId());
if (!success) {
throw new RuntimeException("库存不足");
}
}
@Transactional(rollbackFor = Exception.class)
public boolean updateStock(Long skuId) {
stockMapper.deduct(skuId);
return true;
}
}
方案 2:类内部方法抽取到另一个 Bean @Service public class StockService {
@Transactional(rollbackFor = Exception.class)
public void deductStock(Long skuId) {
stockMapper.deduct(skuId);
}
}
@Service public class OrderServiceImpl implements OrderService {
@Autowired
private StockService stockService;
@Override
public void createOrder(OrderDTO dto) {
orderMapper.insert(order);
stockService.deductStock(dto.getSkuId());
}
}
方案 3:手动编程式事务(不推荐,最复杂)
五、总结
- Spring 事务基于动态代理
- 类内部方法调用会绕过代理
- 最佳实践:涉及事务的方法,调用方不要在同一类内
- 自注入或拆分到独立 Bean 是标准解法