Spring @Transactional 注解从入门到避坑指南
一、为什么需要事务?
想象一个下单场景:
- 创建订单
- 扣减库存
- 扣除用户余额
如果第2步成功了,第3步失败,库存已经扣了但钱没扣,数据就乱了。事务的作用就是保证这一组操作要么全成功,要么全失败。
二、快速使用
@Service
public class OrderService {
@Transactional
public void submitOrder(OrdersSubmitDTO dto) {
orderMapper.insert(order); // 插入订单
orderDetailMapper.insertBatch(list); // 插入明细
shoppingCartMapper.clean(userId); // 清空购物车
}
}加了这个注解,方法内所有数据库操作会被包装在一个事务中。任意一步抛异常,Spring 会自动回滚。
三、核心属性详解
| 属性 | 作用 | 常用值 |
|---|---|---|
propagation | 事务传播行为 | REQUIRED(默认)、REQUIRES_NEW |
isolation | 隔离级别 | READ_COMMITTED、REPEATABLE_READ |
rollbackFor | 指定回滚的异常类型 | Exception.class |
noRollbackFor | 指定不回滚的异常 | IllegalArgumentException.class |
timeout | 事务超时时间(秒) | -1(默认无限制) |
readOnly | 是否为只读事务 | true / false |
3.1 propagation 传播行为
@Transactional(propagation = Propagation.REQUIRED) // 默认
public void methodA() {
// 如果已有事务,加入;没有则新建
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 挂起当前事务,新建一个独立事务
}常见场景:日志记录必须保存,不管主业务是否回滚。此时日志方法用 REQUIRES_NEW,主业务回滚不影响日志。
3.2 isolation 隔离级别
@Transactional(isolation = Isolation.READ_COMMITTED)
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ_UNCOMMITTED | ✗ | ✗ | ✗ |
| READ_COMMITTED | ✓ | ✗ | ✗ |
| REPEATABLE_READ | ✓ | ✓ | ✗ |
| SERIALIZABLE | ✓ | ✓ | ✓ |
MySQL 默认 REPEATABLE_READ,Spring 默认跟随数据库。
3.3 rollbackFor 回滚配置
重点:默认只回滚 RuntimeException 和 Error。
// 错误示范:IOException 不会触发回滚!
@Transactional
public void pay() throws IOException {
// ...
}
// 正确配置:回滚所有异常
@Transactional(rollbackFor = Exception.class)
public void pay() throws IOException {
// ...
}四、底层原理(简述)
Spring 通过 AOP 动态代理 实现事务:
- 运行时生成目标类的代理对象
- 方法执行前开启事务(
begin) - 方法正常结束则提交(
commit) - 方法抛异常则回滚(
rollback)
这也是为什么同类内部调用会导致事务失效——绕过了代理对象。
五、失效场景全解析(避坑指南)
场景 1:同类内部调用(最高频)
@Service
public class OrderService {
public void createOrder() {
saveOrder(); // 直接调用,事务失效!
}
@Transactional
public void saveOrder() {
// ...
}
}原因:this.saveOrder() 没有走代理对象。
解决:
@Autowired
private OrderService orderService;
public void createOrder() {
orderService.saveOrder(); // 通过注入的代理调用
}场景 2:异常被吞掉
@Transactional
public void submit() {
try {
orderMapper.insert(order);
payService.pay(); // 抛异常
} catch (Exception e) {
log.error("支付失败", e);
// 异常没抛出去,Spring 认为成功,提交事务!
}
}解决:
} catch (Exception e) {
throw new RuntimeException(e);
// 或者手动回滚:
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}场景 3:异常类型不匹配
如 3.3 所述,受检异常(IOException、SQLException)默认不回滚。
场景 4:方法非 public
@Transactional 只能作用于 public 方法。
场景 5:数据库引擎不支持
MySQL 的 MyISAM 引擎不支持事务,表必须是 InnoDB。
六、最佳实践
- 尽量放在 Service 层,不要在 Controller 或 Mapper 上加
- 事务方法尽量精简,避免长事务(大事务会锁表、拖垮性能)
- 查询操作加
readOnly = true: - 明确指定
rollbackFor = Exception.class,避免受检异常漏网 - 不要在事务里调外部 HTTP 接口,会拉长事务时间
七、总结
| 要点 | 记忆口诀 |
|---|---|
| 代理调用才生效 | 同类 this. 调用要警惕 |
| 异常必须抛出去 | 别在 catch 里默默吞掉 |
| 回滚范围要明确 | rollbackFor = Exception.class |
| 长事务是性能杀手 | 查询加 readOnly,事务尽量短 |
到此这篇关于Spring @Transactional 注解详解:从入门到避坑的文章就介绍到这了,更多相关Spring @Transactional 注解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
详解mybatis中association和collection的column传入多个参数问题
这篇文章主要介绍了详解mybatis中association和collection的column传入多个参数问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2019-10-10
java多线程开发之通过对战游戏学习CyclicBarrier
这篇文章给大家分享了关于java多线程开发中通过对战游戏学习CyclicBarrier的相关知识点内容,有兴趣的朋友们学习参考下。2018-08-08


最新评论