Spring 事务属性详解
Spring 的 @Transactional 注解提供了一组属性,用来精确控制事务的行为。这些属性决定了事务如何传播、隔离级别、超时时间、是否只读,以及哪些异常触发回滚。
一、事务属性总览
| 属性 | 类型 | 默认值 | 作用 |
|---|---|---|---|
propagation |
Propagation |
REQUIRED |
事务传播行为 |
isolation |
Isolation |
DEFAULT |
事务隔离级别 |
timeout |
int |
-1(不超时) |
事务超时时间(秒) |
readOnly |
boolean |
false |
是否只读事务 |
rollbackFor |
Class[] |
{} |
触发回滚的异常类型 |
rollbackForClassName |
String[] |
{} |
触发回滚的异常类名 |
noRollbackFor |
Class[] |
{} |
不触发回滚的异常类型 |
noRollbackForClassName |
String[] |
{} |
不触发回滚的异常类名 |
二、传播行为(propagation)
传播行为定义了事务方法被调用时,如何与当前已有事务交互。这是事务属性中最核心、最复杂的一个。
| 传播行为 | 说明 | 适用场景 |
|---|---|---|
REQUIRED |
默认。当前有事务则加入,没有则新建 | 绝大多数业务方法 |
REQUIRES_NEW |
挂起当前事务,新建独立事务 | 日志记录、审计操作 |
NESTED |
当前有事务则嵌套执行(保存点),没有则新建 | 部分回滚场景 |
SUPPORTS |
当前有事务则加入,没有则以非事务执行 | 查询方法 |
NOT_SUPPORTED |
以非事务方式执行,挂起当前事务 | 不需要事务的操作 |
MANDATORY |
必须在已有事务中执行,否则抛异常 | 强制要求事务的方法 |
NEVER |
必须以非事务方式执行,否则抛异常 | 禁用事务的方法 |
java
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
orderDao.save(order);
}
最常用的两个:
java
// 主业务:加入或新建事务
@Transactional(propagation = Propagation.REQUIRED)
public void placeOrder(Order order) { }
// 日志:独立事务,即使主业务回滚,日志也要保留
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String message) { }
三、隔离级别(isolation)
隔离级别定义了事务之间的数据可见性,解决并发事务的脏读、不可重复读、幻读问题。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
DEFAULT |
使用数据库默认 | --- | --- |
READ_UNCOMMITTED |
可能 | 可能 | 可能 |
READ_COMMITTED |
不会 | 可能 | 可能 |
REPEATABLE_READ |
不会 | 不会 | 可能 |
SERIALIZABLE |
不会 | 不会 | 不会 |
java
@Transactional(isolation = Isolation.READ_COMMITTED)
public User getUser(Long id) {
return userDao.findById(id);
}
注意: DEFAULT 表示使用底层数据库的默认隔离级别。MySQL InnoDB 默认是 REPEATABLE_READ,Oracle 默认是 READ_COMMITTED。
四、超时时间(timeout)
事务允许执行的最大时间,单位是秒。超时后事务自动回滚。
java
@Transactional(timeout = 30)
public void batchProcess(List<Order> orders) {
// 超过 30 秒自动回滚
}
默认值 -1 表示不超时,由数据库自己决定。超时时间不是精确的,实际超时可能略有偏差,因为检查时机由数据库或框架决定。
五、只读事务(readOnly)
标记事务为只读,用于优化查询性能。
java
@Transactional(readOnly = true)
public List<User> findAll() {
return userMapper.selectAll();
}
只读事务的作用:
- 数据库可以做一些优化(如不记录回滚日志)。
- 提示框架和数据库,这个事务不会修改数据。
- 某些数据库驱动会对只读事务做特殊处理。
注意: readOnly = true 并不保证一定会阻止写操作。如果只读事务中执行了 INSERT、UPDATE、DELETE,行为取决于数据库实现,可能报错,也可能静默执行。
六、回滚规则
Spring 事务默认只在遇到 RuntimeException 和 Error 时回滚。检查型异常(Exception 的子类)不会触发回滚。
rollbackFor
指定哪些异常触发回滚。
java
@Transactional(rollbackFor = {BusinessException.class, SQLException.class})
public void processOrder(Order order) throws BusinessException {
// 抛出这些异常时回滚
}
rollbackForClassName
用类名字符串指定,效果与 rollbackFor 相同。
java
@Transactional(rollbackForClassName = {"com.example.BusinessException"})
noRollbackFor
指定哪些异常不触发回滚。
java
@Transactional(noRollbackFor = {BusinessException.class})
public void processOrder(Order order) throws BusinessException {
// 抛出 BusinessException 时不回滚
}
noRollbackForClassName
用类名字符串指定不触发回滚的异常。
java
@Transactional(noRollbackForClassName = {"com.example.BusinessException"})
最佳实践: 默认回滚规则只覆盖 RuntimeException。如果业务中会抛出检查型异常,务必显式指定 rollbackFor = Exception.class,避免异常被吞掉但数据已提交。
java
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) throws IOException {
// 抛出 IOException 时也回滚
}
七、完整示例
java
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private InventoryDao inventoryDao;
@Autowired
private LogService logService;
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
timeout = 30,
readOnly = false,
rollbackFor = {BusinessException.class, SQLException.class},
noRollbackFor = {NotifyException.class}
)
public void createOrder(Order order) throws BusinessException, SQLException {
orderDao.save(order);
inventoryDao.decrease(order.getProductId(), order.getQuantity());
// 日志用独立事务,即使主业务回滚,日志也保留
logService.saveLog("创建订单:" + order.getId());
}
}
八、属性配置方式对比
| 配置方式 | 示例 | 适用场景 |
|---|---|---|
| 注解属性 | @Transactional(propagation = ...) |
方法或类级别 |
| XML 配置 | <tx:advice> + <aop:config> |
老项目 |
| 编程式 | TransactionTemplate |
需要动态控制事务边界 |
九、常见问题
1. 默认回滚规则不覆盖检查型异常
java
@Transactional
public void process() throws IOException {
// IOException 是检查型异常,默认不回滚
// 需要 rollbackFor = Exception.class
}
2. 传播行为选择不当导致数据不一致
java
// ❌ 日志用 REQUIRED,主业务回滚时日志也回滚
@Transactional(propagation = Propagation.REQUIRED)
public void saveLog(String msg) { }
// ✅ 日志用 REQUIRES_NEW,主业务回滚日志仍保留
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String msg) { }
3. 只读事务中执行写操作
java
@Transactional(readOnly = true)
public void updateUser(User user) {
userDao.update(user); // 可能报错,也可能静默执行
}
4. 超时时间设置过短
java
@Transactional(timeout = 1)
public void batchProcess() {
// 批量操作可能超过 1 秒,导致意外回滚
}
5. 隔离级别与数据库默认不一致
java
// MySQL 默认 REPEATABLE_READ,显式设置为 READ_COMMITTED 可能改变行为
@Transactional(isolation = Isolation.READ_COMMITTED)
十、总结
| 属性 | 核心作用 | 默认值 | 常用值 |
|---|---|---|---|
propagation |
事务传播行为 | REQUIRED |
REQUIRED、REQUIRES_NEW |
isolation |
事务隔离级别 | DEFAULT |
READ_COMMITTED、REPEATABLE_READ |
timeout |
超时时间(秒) | -1 |
30 |
readOnly |
是否只读 | false |
查询方法设 true |
rollbackFor |
触发回滚的异常 | RuntimeException |
Exception.class |
noRollbackFor |
不触发回滚的异常 | {} |
业务异常 |
事务属性是 @Transactional 精确控制事务行为的核心。传播行为决定事务如何嵌套,隔离级别决定并发时的数据可见性,超时时间防止事务长期占用资源,只读标记优化查询性能,回滚规则决定哪些异常触发回滚。理解这些属性的含义和使用场景,才能写出正确、高效的事务代码。