一句话总结
Spring 事务基于 AOP + ThreadLocal 实现。核心接口:PlatformTransactionManager(事务管理器,不同数据源有不同实现)。声明式事务(@Transactional,最常用)vs 编程式事务(TransactionTemplate,灵活但侵入)。传播行为(7种):REQUIRED(默认,有则加入无则新建)、REQUIRES_NEW(总是新建,挂起当前)、NESTED(嵌套事务,savepoint)。隔离级别:DEFAULT(跟随数据库,MySQL 默认 REPEATABLE_READ)。
初级理解
声明式事务 vs 编程式事务
# 声明式事务(推荐)
@Service
public class UserService {
@Transactional
public void createUser(User user) {
userDao.insert(user);
logDao.insert(new Log("创建用户"));
}
}
# 编程式事务
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
public void createUser(User user) {
transactionTemplate.execute(status -> {
userDao.insert(user);
logDao.insert(new Log("创建用户"));
return null;
});
}
}
# 编程式事务适用场景:
# 1. 只有少量操作需要事务
# 2. 需要精细控制事务边界
# 3. 动态决定是否回滚
中级深入
7 种传播行为
| 传播行为 | 说明 | 场景 |
|---|---|---|
| REQUIRED(默认) | 有事务则加入,无则新建 | 最常用,增删改操作 |
| REQUIRES_NEW | 总是新建事务,挂起当前 | 日志记录(不受主事务影响) |
| NESTED | 嵌套事务,通过 savepoint 回滚 | 部分回滚场景 |
| SUPPORTS | 有事务则加入,无则非事务执行 | 查询操作 |
| NOT_SUPPORTED | 非事务执行,挂起当前事务 | 不需要事务的操作 |
| MANDATORY | 必须在事务中,否则抛异常 | 强制事务场景 |
| NEVER | 不能在事务中,否则抛异常 | 禁止事务场景 |
# REQUIRED vs REQUIRES_NEW 示例
@Service
public class OrderService {
@Transactional // REQUIRED(默认)
public void createOrder() {
orderDao.insert(order); // 操作1
logService.writeLog(); // 操作2(REQUIRES_NEW)
// 如果操作3抛异常
throw new RuntimeException(); // 操作3
}
# 结果:操作1 回滚,操作2 提交(独立事务)
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void writeLog() {
logDao.insert(log);
}
}
高级拓展
事务原理 — AOP + ThreadLocal
# Spring 事务执行流程
# 1. @Transactional 方法被 AOP 代理拦截
# 2. TransactionInterceptor 调用 invoke()
# 3. 获取 TransactionManager
# 4. 判断传播行为
# 5. 开启事务(获取连接、设置 autoCommit=false)
# 6. 绑定连接到 ThreadLocal(TransactionSynchronizationManager)
# 7. 执行目标方法
# 8. 成功 → commit;异常 → rollback
# 9. 清理 ThreadLocal、归还连接
# 为什么同一个事务中多次 DAO 操作用的是同一个连接?
# 因为连接绑定在 ThreadLocal 上
# 同一个线程获取的都是同一个连接
隔离级别
# Spring 事务隔离级别
@Transactional(isolation = Isolation.READ_COMMITTED)
# 5 种隔离级别
# DEFAULT:跟随数据库默认(MySQL = REPEATABLE_READ)
# READ_UNCOMMITTED:读未提交(脏读、不可重复读、幻读)
# READ_COMMITTED:读已提交(不可重复读、幻读)
# REPEATABLE_READ:可重复读(幻读,MySQL InnoDB 通过 MVCC 解决)
# SERIALIZABLE:串行化(无并发问题,性能最差)
实战场景
场景:多数据源事务
# 多数据源需要 JTA 分布式事务
# 或使用 Seata 等分布式事务框架
# 单数据源多 DAO 没问题
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
accountDao.decrease(fromId, amount); // 扣钱
accountDao.increase(toId, amount); // 加钱
// 同一个 DataSource,同一个连接,同一个事务
}
# 多数据源需要指定 TransactionManager
@Transactional(transactionManager = "orderTransactionManager")
public void createOrder() { }
@Transactional(transactionManager = "userTransactionManager")
public void createUser() { }
面试模拟
面试官:Spring 事务的传播行为有哪些?
你:7 种。最常用:REQUIRED(默认,有则加入无则新建)、REQUIRES_NEW(总是新建独立事务)、NESTED(嵌套事务,savepoint 回滚)。其他:SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER。
面试官:REQUIRED 和 REQUIRES_NEW 有什么区别?
你:REQUIRED 如果当前有事务就加入,回滚时一起回滚;REQUIRES_NEW 总是新建事务,挂起当前事务,内外事务互不影响。典型场景:日志记录用 REQUIRES_NEW,即使主业务回滚,日志也要保留。