Spring事务三剑客:@Transactional、TransactionTemplate、TransactionManager 该怎么选?

Spring 提供的三种事务实现方式:@Transactional注解、TransactionTemplate 编程式事务、直接操作 TransactionManager,更是让很多开发者犯迷糊:
- 为什么我加了
@Transactional注解,事务却没生效? - 编程式事务写起来麻烦,到底什么时候该用?
- 直接操作 TransactionManager 是不是多此一举?
今天就结合我踩过的坑、落地过的生产场景,把这三种方式的底层逻辑、使用场景、坑点讲透,看完你就能精准判断:什么场景该用什么方式,再也不会在事务上出问题。
先搞懂核心:Spring 事务的本质
不管是哪种方式,Spring 事务的底层都是靠 PlatformTransactionManager(事务管理器)实现的——JDBC 事务用 DataSourceTransactionManager,MyBatis/MyBatis-Plus 本质也是用这个;JPA 事务用 JpaTransactionManager。
所有事务操作,最终都会落到这几个核心步骤:
- 获取事务(开启事务):通过 TransactionManager 获取一个新的事务,或加入已有事务;
- 执行业务逻辑:执行业务代码(增删改查);
- 提交/回滚:业务执行成功则提交事务,抛出异常则回滚事务;
- 释放资源:关闭事务连接,归还到连接池。
三种方式的区别,只在于“谁来控制这些步骤”:
@Transactional:Spring 自动帮你完成所有步骤(声明式);TransactionTemplate:你手动控制执行逻辑,Spring 帮你处理事务生命周期(半编程式);TransactionManager:所有步骤都由你手动控制(纯编程式)。
方式 1:@Transactional 注解——最常用,也最容易踩坑
@Transactional 是声明式事务,也是大多数开发者的首选——只需要在方法上加一个注解,Spring 就会通过 AOP 自动帮你管理事务,不用写任何事务相关的代码。
怎么用?极简示例
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private PayMapper payMapper;
// 加注解,默认 RuntimeException 触发回滚
@Transactional(rollbackFor = Exception.class)
public void createOrderAndPay(OrderDTO orderDTO) throws Exception {
// 1. 创建订单
orderMapper.insert(orderDTO);
// 2. 扣减支付金额
payMapper.deductAmount(orderDTO.getUserId(), orderDTO.getAmount());
// 3. 模拟异常,事务会回滚
if (orderDTO.getAmount() < 0) {
throw new Exception("金额不能为负");
}
}
}
核心优点:简单、无侵入
- 代码干净:不用写任何事务管理代码,只加注解,业务逻辑和事务逻辑分离;
- 成本低:新手也能快速上手,不用理解事务管理器的底层;
- 易维护:注解参数配置灵活,比如指定事务传播行为、隔离级别、超时时间。
最容易踩的 8 个坑(我全踩过)
坑 1:非 public 方法,注解失效
Spring AOP 只对 public 方法生效,如果你把注解加在private/protected/default方法上,事务完全不生效:
// 错误:private 方法,@Transactional 无效
@Transactional
private void createOrder() {
// 业务逻辑
}
坑 2:内部调用,注解失效
同一个类里,无注解的方法调用有注解的方法,AOP 无法拦截,事务失效:
@Service
public class OrderService {
public void outerMethod() {
// 内部调用,事务失效
innerMethod();
}
@Transactional
public void innerMethod() {
// 业务逻辑
}
}
坑 3:异常被捕获,事务不回滚
如果方法内捕获了异常,没有抛出,Spring 感知不到异常,不会回滚:
@Transactional
public void createOrder() {
try {
orderMapper.insert(orderDTO);
payMapper.deductAmount();
// 模拟异常
int i = 1 / 0;
} catch (Exception e) {
// 捕获异常但不抛出,事务不会回滚
log.error("创建订单失败", e);
}
}
正确做法:捕获后重新抛出异常,或手动回滚:
catch (Exception e) {
log.error("创建订单失败", e);
// 手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw new RuntimeException(e);
}
坑 4:默认只回滚 RuntimeException
@Transactional默认只对 RuntimeException 和 Error 回滚,对 checked 异常(比如 IOException、SQLException)不回滚:
✅ 正确做法:指定 rollbackFor 参数:
@Transactional(rollbackFor = Exception.class)
坑 5:传播行为用错
比如用了 PROPAGATION_SUPPORTS(没有事务就不创建),但业务需要必须有事务,导致事务失效:
// 错误:如果外层没有事务,这个方法就没有事务 @Transactional(propagation = Propagation.SUPPORTS)
✅ 常用安全传播行为:PROPAGATION_REQUIRED(默认)、PROPAGATION_REQUIRES_NEW(新建事务)。
坑 6:隔离级别配置错误
比如用了 READ_UNCOMMITTED(读未提交),导致脏读;用了 SERIALIZABLE(串行化),性能极差:
✅ 生产常用:ISOLATION_READ_COMMITTED(读已提交),兼顾性能和数据一致性。
坑 7:超时时间设置不合理
timeout 参数单位是秒,设置太小会导致正常业务超时回滚,设置太大又会占用连接:
// 合理设置:比如支付场景,超时时间设为 30 秒 @Transactional(timeout = 30, rollbackFor = Exception.class)
坑 8:多数据源场景,事务管理器指定错误
如果项目有多个数据源(比如主库、从库),没指定对应的事务管理器,注解会失效:
// 指定事务管理器 @Transactional(transactionManager = "masterTransactionManager")
适用场景
- 简单业务场景:单表/多表操作,无复杂的事务控制逻辑;
- 非嵌套事务:不需要在方法内手动控制事务提交/回滚;
- 团队开发:统一规范,降低新手使用成本。
方式 2:TransactionTemplate——编程式事务,可控性更强
TransactionTemplate 是 Spring 提供的编程式事务模板,相比注解,它能手动控制事务的执行逻辑,避免注解的各种“坑”。
怎么用?核心示例
@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private OrderMapper orderMapper;
@Autowired
private PayMapper payMapper;
public void createOrderAndPay(OrderDTO orderDTO) {
// 执行编程式事务
Boolean result = transactionTemplate.execute(status -> {
try {
// 1. 创建订单
orderMapper.insert(orderDTO);
// 2. 扣减支付金额
payMapper.deductAmount(orderDTO.getUserId(), orderDTO.getAmount());
// 3. 模拟异常,触发回滚
if (orderDTO.getAmount() < 0) {
throw new RuntimeException("金额不能为负");
}
return true;
} catch (Exception e) {
// 手动标记回滚
status.setRollbackOnly();
log.error("创建订单失败", e);
return false;
}
});
if (Boolean.FALSE.equals(result)) {
// 事务执行失败,做补偿逻辑
log.warn("订单创建事务回滚,用户 ID:{}", orderDTO.getUserId());
}
}
}
核心优点:可控、无坑
- 避免 AOP 陷阱:不受方法访问修饰符、内部调用的影响,事务 100%生效;
- 手动控制回滚:可以根据业务逻辑,灵活标记事务回滚(
status.setRollbackOnly()); - 有返回值:能获取事务执行结果,方便做后续的补偿逻辑;
- 配置灵活:可以在代码里动态设置事务隔离级别、超时时间。
小缺点:代码略繁琐
相比注解,需要把业务逻辑包在execute方法里,代码嵌套一层,略显繁琐——但换来的是绝对的可控性,这点繁琐在生产环境中完全值得。
适用场景
- 注解事务踩坑场景:比如内部调用、非 public 方法,注解失效的情况;
- 复杂业务逻辑:需要根据业务结果手动控制回滚(比如部分成功后,主动回滚);
- 动态配置事务:需要在代码里动态设置隔离级别、超时时间;
- 补偿逻辑:需要根据事务执行结果,做后续的补偿操作(比如通知、日志)。
方式 3:直接操作 TransactionManager——极致灵活,极少用
直接使用PlatformTransactionManager是最底层的方式,所有事务步骤都由你手动控制,灵活性拉满,但代码量也最大。
怎么用?完整示例
@Service
public class OrderService {
@Autowired
private DataSourceTransactionManager transactionManager;
@Autowired
private OrderMapper orderMapper;
@Autowired
private PayMapper payMapper;
public void createOrderAndPay(OrderDTO orderDTO) {
// 1. 定义事务属性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 设置隔离级别:读已提交
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
// 设置传播行为:必须有事务
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 设置超时时间:30 秒
def.setTimeout(30);
// 2. 获取事务状态
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 3. 执行业务逻辑
orderMapper.insert(orderDTO);
payMapper.deductAmount(orderDTO.getUserId(), orderDTO.getAmount());
if (orderDTO.getAmount() < 0) {
throw new RuntimeException("金额不能为负");
}
// 4. 提交事务
transactionManager.commit(status);
log.info("订单创建成功,用户 ID:{}", orderDTO.getUserId());
} catch (Exception e) {
// 5. 回滚事务
transactionManager.rollback(status);
log.error("创建订单失败,事务回滚", e);
// 抛出异常,让上层感知
throw new RuntimeException(e);
}
}
}
核心优点:极致灵活
- 完全掌控:事务的开启、提交、回滚全由你控制,没有任何黑盒;
- 高级场景:支持多事务管理器切换、嵌套事务、事务挂起/恢复等高级操作;
- 无依赖:不依赖 Spring AOP,避免任何 AOP 相关的坑。
缺点:代码繁琐、易出错
- 代码量大:需要手动写事务属性、获取状态、提交/回滚,容易漏写(比如忘记回滚);
- 维护成本高:团队协作时,新手容易写错,增加代码审查成本;
- 没必要:99%的业务场景,用 TransactionTemplate 就能满足,不用这么底层。
适用场景
- 极特殊的高级场景:比如多事务管理器切换、事务挂起/恢复、嵌套事务的精细控制;
- 框架开发:自己开发中间件/框架,需要封装事务逻辑;
- 注解和模板都满足不了的场景:比如需要在事务开启前做特殊处理,或提交后执行自定义逻辑。
一张表搞定:三种方式怎么选?
| 特性/方式 | @Transactional 注解 | TransactionTemplate | TransactionManager 直接操作 |
|---|---|---|---|
| 易用性 | 极高(只加注解) | 中等(代码嵌套) | 极低(全手动) |
| 可控性 | 低(AOP 黑盒) | 中高(手动控制回滚) | 极高(全掌控) |
| 坑点 | 多(AOP、异常、传播行为) | 极少(几乎无坑) | 多(易漏写步骤) |
| 代码侵入性 | 低(无业务代码侵入) | 中(业务逻辑包在 execute 里) | 高(事务代码和业务混写) |
| 适用复杂度 | 简单/中等业务 | 中等/复杂业务 | 极复杂/框架级业务 |
| 生产使用频率 | 80% | 19% | 1% |
最后:生产环境的选型建议
- 优先用 @Transactional:简单业务(比如单表 CRUD、多表简单操作),团队规范统一,加好 rollbackFor、指定传播行为,避开常见坑即可;
- 注解搞不定就用 TransactionTemplate:内部调用、需要手动控制回滚、复杂业务逻辑,用模板式编程事务,兼顾简洁和可控;
- 尽量不用 TransactionManager:除非你明确知道自己需要底层控制,否则别给自己找麻烦——模板式已经能满足 99%的业务需求。
额外提个小技巧:
- 新项目可以统一用
@Transactional,并在团队规范里明确:必须加rollbackFor = Exception.class,避免异常回滚坑; - 老项目如果注解事务频繁失效,优先重构为
TransactionTemplate,而不是直接上TransactionManager; - 所有事务方法,都要加日志(成功/失败),方便问题排查。
事务是后端的“生命线”,选对实现方式,能让你少踩 80%的坑——不用追求“越底层越好”,适合业务场景的,才是最好的。
以上关于Spring事务三剑客:@Transactional、TransactionTemplate、TransactionManager 该怎么选?的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » Spring事务三剑客:@Transactional、TransactionTemplate、TransactionManager 该怎么选?
微信
支付宝