在日常开发中,你是否遇到过这些场景?
- 数据库操作明明抛了异常,数据却没回滚?
- 微服务调用多个接口,部分成功部分失败,数据不一致?
- 加了
@Transactional 注解,事务依然不生效?
这些问题往往源于对 @Transactional 注解的误解或使用不当。今天我们就来彻底拆解它在 Spring Cloud 环境下的核心逻辑。
一、@Transactional 基础认知
1. 本质作用
在 Spring 管理的 Bean 方法上添加 @Transactional,声明该方法需要事务管理:
- 方法执行前开启事务
- 成功执行后提交事务
- 抛出未捕获异常时回滚事务
2. 核心代码示例
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
// 声明事务:方法内所有数据库操作作为原子单元
@Transactional
public void createOrder(Order order) {
orderDao.insert(order); // 插入订单
inventoryService.deductStock(); // 扣减库存(假设是另一个Service)
// 若此处抛出RuntimeException,订单和库存操作一起回滚!
}
}
二、分布式场景下的典型应用
✅ 场景1:单服务多数据源操作
@Transactional
public void transfer(Account from, Account to, BigDecimal amount) {
accountDao.deduct(from, amount); // 操作数据源A
accountDao.add(to, amount); // 操作数据源B
// 需配合分布式事务管理器(如Atomikos)实现跨库事务
}
✅ 场景2:微服务间调用(需谨慎!)
@Transactional
public void createOrder(OrderDTO dto) {
// 1. 本地订单库操作(可回滚)
orderDao.save(dto);
// 2. 调用库存服务(远程RPC)
Response stockResp = inventoryFeignClient.deduct(dto.getSkuId());
// 问题:若此处库存服务失败,本地事务不会回滚!
// 解决方案:引入最终一致性方案(如本地消息表、Seata AT模式)
}
📌 关键结论:
@Transactional 默认只能管理本地事务,跨服务调用需结合分布式事务方案!
三、必须掌握的6大注意事项
1. 默认只回滚 RuntimeException
@Transactional
public void update() throws Exception {
// 抛出受检异常不会触发回滚!
throw new Exception("受检异常");
}
// 解决方案:指定回滚异常类型
@Transactional(rollbackFor = Exception.class)
在日常开发中,你是否遇到过这些场景?
- 数据库操作明明抛了异常,数据却没回滚?
- 微服务调用多个接口,部分成功部分失败,数据不一致?
- 加了
@Transactional 注解,事务依然不生效?
这些问题往往源于对 @Transactional 注解的误解或使用不当。今天我们就来彻底拆解它在 Spring Cloud 环境下的核心逻辑。
一、@Transactional 基础认知
1. 本质作用
在 Spring 管理的 Bean 方法上添加 @Transactional,声明该方法需要事务管理:
- 方法执行前开启事务
- 成功执行后提交事务
- 抛出未捕获异常时回滚事务
2. 核心代码示例
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
// 声明事务:方法内所有数据库操作作为原子单元
@Transactional
public void createOrder(Order order) {
orderDao.insert(order); // 插入订单
inventoryService.deductStock(); // 扣减库存(假设是另一个Service)
// 若此处抛出RuntimeException,订单和库存操作一起回滚!
}
}
二、分布式场景下的典型应用
✅ 场景1:单服务多数据源操作
@Transactional
public void transfer(Account from, Account to, BigDecimal amount) {
accountDao.deduct(from, amount); // 操作数据源A
accountDao.add(to, amount); // 操作数据源B
// 需配合分布式事务管理器(如Atomikos)实现跨库事务
}
✅ 场景2:微服务间调用(需谨慎!)
@Transactional
public void createOrder(OrderDTO dto) {
// 1. 本地订单库操作(可回滚)
orderDao.save(dto);
// 2. 调用库存服务(远程RPC)
Response stockResp = inventoryFeignClient.deduct(dto.getSkuId());
// 问题:若此处库存服务失败,本地事务不会回滚!
// 解决方案:引入最终一致性方案(如本地消息表、Seata AT模式)
}
📌 关键结论:
@Transactional 默认只能管理本地事务,跨服务调用需结合分布式事务方案!
三、必须掌握的6大注意事项
1. 默认只回滚 RuntimeException
@Transactional
public void update() throws Exception {
// 抛出受检异常不会触发回滚!
throw new Exception("受检异常");
}
// 解决方案:指定回滚异常类型
@Transactional(rollbackFor = Exception.class)
2. 事务生效条件
- 必须通过代理对象调用(直接调用内部方法失效)
- 方法必须是 public(private方法不生效)
- 避免在同一个类内方法互相调用(AOP代理失效)
3. 事务传播行为(Propagation)
当多个事务方法嵌套调用时,通过propagation控制行为:
@Transactional(propagation = Propagation.REQUIRED) // 默认:存在父事务则加入
@Transactional(propagation = Propagation.REQUIRES_NEW) // 始终新建事务
4. 事务超时设置
避免事务长时间阻塞:
@Transactional(timeout = 5) // 单位:秒
5. 只读事务优化
@Transactional(readOnly = true) // 提升查询性能
6. 隔离级别防脏读
@Transactional(isolation = Isolation.REPEATABLE_READ) // 避免幻读
四、最佳实践总结
| 场景 |
推荐策略 |
| 本地数据库操作 |
直接使用 @Transactional |
| 跨服务调用 |
本地事务+消息队列/Seata |
| 高并发场景 |
尽量缩短事务执行时间 |
| 查询为主 |
添加 readOnly=true |
结语:
事务管理是分布式系统的基石,但@Transactional不是万能钥匙。理解原理 + 对症下药才能避免生产事故。下期我们将详解如何整合 Seata 实现分布式事务,欢迎持续关注!
✍️ 实践建议:开发完成后务必通过以下方式验证事务:
- 注入异常模拟失败场景
- 检查数据库日志是否出现 rollback
- 使用事务监控工具(如Actuator)