Spring事务管理:@Transactional注解深度解析
Spring事务管理:@Transactional注解深度解析
引言
在企业级应用开发中,事务管理是保障数据一致性和完整性的基石。无论是金融系统的转账操作,还是电商系统的下单扣库存,都离不开事务的支持。传统的事务管理方式(如编程式事务)往往会导致代码冗余,业务代码与事务管理代码高度耦合,难以维护。
Spring框架通过AOP(面向切面编程)技术,提供了优雅的声明式事务管理能力,其中@Transactional注解更是成为了开发者的“瑞士军刀”。然而,看似简单的一个注解,背后隐藏着复杂的传播行为、隔离级别以及AOP代理机制。如果对其原理理解不深,极易陷入“事务失效”、“数据不一致”等陷阱。
本文将从核心概念出发,深入剖析@Transactional的底层原理,并结合实战代码,助你彻底掌握Spring事务管理。
核心概念
在深入原理之前,我们必须先理解事务的两个核心维度:传播行为和隔离级别。
1. 事务传播行为
传播行为定义了多个事务方法相互调用时,事务如何在这些方法间传播。Spring定义了7种传播行为,最常用的有以下三种:
REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常用的选择。REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。这意味着两个事务相互独立,互不影响。NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则表现得像REQUIRED。它依赖于数据库的SavePoint机制,允许部分回滚。
2. 事务隔离级别
隔离级别定义了一个事务必须与其它事务隔离的程度。主要是为了解决并发场景下的“脏读”、“不可重复读”和“幻读”问题。
READ_UNCOMMITTED:读未提交,最低级别,可能导致脏读。READ_COMMITTED:读已提交,防止脏读,但可能出现不可重复读(Oracle默认)。REPEATABLE_READ:可重复读,防止脏读和不可重复读,但可能出现幻读(MySQL默认)。SERIALIZABLE:串行化,最高级别,完全服从ACID原则,性能极低。
技术原理
为什么加上@Transactional注解,方法就拥有了事务能力?其背后的核心机制是 Spring AOP 和 动态代理。
1. 动态代理机制
Spring在启动时,会扫描所有带有@Transactional注解的Bean。对于这些Bean,Spring不会直接实例化原对象返回给容器,而是创建一个代理对象。
- JDK动态代理:如果目标类实现了接口,默认使用JDK动态代理。
- CGLIB代理:如果目标类没有实现接口,Spring会使用CGLIB生成一个子类代理。
2. 事务拦截器
代理对象的核心逻辑在于TransactionInterceptor。当外部调用被@Transactional注解的方法时,调用链路如下:
- 客户端调用代理对象的方法。
- 代理对象将调用委托给
TransactionInterceptor。 TransactionInterceptor根据事务配置(传播行为、隔离级别等),调用PlatformTransactionManager获取数据库连接并开启事务。- 绑定连接至ThreadLocal:为了确保事务内的多个数据库操作使用同一个数据库连接,Spring将连接对象绑定到当前线程的
ThreadLocal中(通过TransactionSynchronizationManager)。 - 调用目标对象的实际业务方法。
- 如果方法执行成功,
TransactionInterceptor提交事务。 - 如果方法抛出异常,且异常符合回滚规则(默认为
RuntimeException和Error),则回滚事务。
实战代码
下面通过一个具体的“转账”场景,演示@Transactional的使用,特别是传播行为的影响。
场景描述
用户A向用户B转账100元。我们需要:
1. 扣减A的余额。
2. 增加B的余额。
3. 记录转账日志(无论转账成功与否,日志都应记录,即日志事务独立)。
代码实现
首先,定义实体类和Repository(此处简化数据库操作):
/**
* 用户实体类
*/
@Data
public class User {
private Long id;
private String username;
private BigDecimal balance;
}
/**
* 转账日志实体
*/
@Data
public class TransferLog {
private Long id;
private String message;
private Date createTime;
}
接下来是核心业务代码:
```java
@Service
public class TransferService {
@Autowired
private UserRepository userRepository;
@Autowired
private LogService logService;
/**
* 转账主方法
* propagation = Propagation.REQUIRED: 默认行为,如果当前有事务则加入,没有则新建
* rollbackFor = Exception.class: 指定遇到所有Exception都回滚(默认只回滚RuntimeException)
*/
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// 1. 扣减转出人余额
User fromUser = userRepository.findById(fromId);
if (fromUser.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("余额不足"); // 触发回滚
}
fromUser.setBalance(fromUser.getBalance().subtract(amount));
userRepository.update(fromUser);
// 2. 增加转入人余额
User toUser = userRepository.findById(toId);
toUser.setBalance(toUser.getBalance().add(amount));
userRepository.update(toUser);
// 3. 记录日志 (独立事务)
// 即使上面的转账失败回滚,日志也需要保留
try {
logService.recordLog("用户 " + fromId + " 向 " + toId + " 转账 " + amount);
} catch (Exception e) {
// 捕获日志记录的异常,不影响主事务
System.err.println("日志记录失败: " + e.getMessage());
}
// 模拟异常,测试事务回滚
// if (true) throw new RuntimeException("模拟转账异常");
}
}
@Service
public class LogService {
@Autowired
private LogRepository logRepository;
/**
* 记

浙公网安备 33010602011771号