(附@Transactional注解深度剖析与常见踩坑解决方案)
假设银行转账场景:A向B转账100元,需执行两个操作:
1️⃣ A账户扣除100 → 2️⃣ B账户增加100
如果步骤1成功后系统崩溃,将导致 数据不一致。事务管理的核心使命:通过ACID特性(原子性、一致性、隔离性、持久性)确保多个操作要么全部成功,要么全部回滚。
核心API:TransactionTemplate
@Service
public class BankService {
@Autowired
private TransactionTemplate transactionTemplate;
public void transfer(Long fromId, Long toId, BigDecimal amount) {
transactionTemplate.execute(status -> {
try {
accountDao.deduct(fromId, amount); // 扣款
accountDao.add(toId, amount); // 加款
return true;
} catch (Exception e) {
status.setRollbackOnly(); // 标记回滚
return false;
}
});
}
}
适用场景:需要精细控制事务边界
通过 @Transactional
注解 自动管理事务
@Service
public class BankService {
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
accountDao.deduct(fromId, amount);
accountDao.add(toId, amount);
}
}
优势:非侵入式、代码简洁、易于维护
参数 | 作用 | 默认值 |
---|---|---|
propagation | 事务传播行为 | REQUIRED |
isolation | 事务隔离级别 | DEFAULT(数据库默认) |
timeout | 事务超时时间(秒) | -1(不超时) |
readOnly | 是否只读事务 | false |
rollbackFor | 触发回滚的异常类 | RuntimeException和Error |
noRollbackFor | 不触发回滚的异常类 | 无 |
重要规则:
@Transactional
只能应用于 public方法// 错误示例:事务失效!
public void transfer(Long fromId, Long toId, BigDecimal amount) {
deduct(fromId, amount);
add(toId, amount);
}
@Transactional
public void deduct(Long id, BigDecimal amount) { ... }
@Transactional
public void add(Long id, BigDecimal amount) { ... }
传播行为类型 | 说明 |
---|---|
REQUIRED(默认) | 当前存在事务则加入,否则新建事务 |
REQUIRES_NEW | 新建事务,挂起当前事务 |
SUPPORTS | 当前存在事务则加入,否则非事务运行 |
NOT_SUPPORTED | 非事务运行,挂起当前事务 |
MANDATORY | 必须存在事务,否则抛异常 |
NEVER | 必须非事务运行,否则抛异常 |
NESTED | 嵌套事务(保存点机制),外层异常回滚内层 |
场景:订单提交(主事务)需要记录日志(子事务),日志失败不影响主事务
@Transactional(propagation = Propagation.REQUIRED)
public void submitOrder(Order order) {
orderDao.save(order);
try {
logService.addLog("订单提交"); // 使用REQUIRES_NEW
} catch (Exception e) {
// 日志异常不影响主事务
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addLog(String content) { ... }
}
隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
---|---|---|---|---|
READ_UNCOMMITTED | ✅ | ✅ | ✅ | 最低隔离,性能最高 |
READ_COMMITTED(默认) | ❌ | ✅ | ✅ | 防止脏读 |
REPEATABLE_READ | ❌ | ❌ | ✅ | 防止不可重复读 |
SERIALIZABLE | ❌ | ❌ | ❌ | 完全串行化,性能最低 |
悲观锁示例:
@Transactional
public Product getProductForUpdate(Long id) {
// SELECT ... FOR UPDATE
return productDao.findByIdWithLock(id);
}
// 使用JPA实现
@Query("SELECT p FROM Product p WHERE p.id = :id FOR UPDATE")
Product findByIdWithLock(@Param("id") Long id);
乐观锁示例(使用版本号):
@Entity
public class Product {
@Version
private Integer version;
// ...
}
问题现象 | 原因分析 | 解决方案 |
---|---|---|
方法内部调用导致事务失效 | 自调用不走代理 | 将方法拆分到不同类 |
异常被捕获未抛出 | 未触发回滚机制 | 抛出RuntimeException |
非public方法使用事务注解 | 代理不生效 | 改为public方法 |
多数据源未指定事务管理器 | 默认管理器不匹配 | @Transactional("tm1") |
默认回滚:
@Transactional(rollbackFor = BusinessException.class)
public void businessOp() throws BusinessException {
// 抛出BusinessException时回滚
}
Joshua Bloch 建议:
“好的API设计应该让正确的事情容易做,错误的事情难以发生。Spring事务管理正是这一理念的典范。”
讨论:你在使用Spring事务时踩过哪些坑?欢迎分享解决方案!