事务管理
一、事务概述
ACID 特性
┌─────────────────────────────────────────────────────────────────┐
│ ACID 特性 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Atomic(原子性) │
│ └─ 事务是最小执行单元,不可分割 │
│ 要么全部成功,要么全部失败 │
│ │
│ Consistency(一致性) │
│ └─ 事务执行前后,数据库状态保持一致 │
│ 约束不被破坏 │
│ │
│ Isolation(隔离性) │
│ └─ 并发执行的事务互不干扰 │
│ 通过隔离级别控制 │
│ │
│ Durability(持久性) │
│ └─ 事务提交后,结果永久保存 │
│ │
└─────────────────────────────────────────────────────────────────┘Spring 事务抽象
┌─────────────────────────────────────────────────────────────────┐
│ Spring 事务抽象 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ PlatformTransactionManager(事务管理器接口) │
│ ├─ DataSourceTransactionManager ← JDBC │
│ ├─ JpaTransactionManager ← JPA │
│ ├─ HibernateTransactionManager ← Hibernate(老) │
│ ├─ JtaTransactionManager ← JTA(分布式事务) │
│ └─ RabbitTransactionManager ← RabbitMQ │
│ │
└─────────────────────────────────────────────────────────────────┘二、@Transactional
基本用法
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private AccountRepository accountRepository;
@Transactional // 默认配置
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// 扣款
Account from = accountRepository.findById(fromId).orElseThrow();
from.setBalance(from.getBalance().subtract(amount));
accountRepository.save(from);
// 存款
Account to = accountRepository.findById(toId).orElseThrow();
to.setBalance(to.getBalance().add(amount));
accountRepository.save(to);
// 如果抛出异常,事务回滚
}
}事务管理器
java
// 默认自动选择(根据 DataSource)
@Transactional
// 显式指定
@Transactional(transactionManager = "jpaTransactionManager")
// Spring Boot 2.x+ 配置
spring:
transaction:
default-timeout: 30 # 全局超时(秒)三、事务传播行为
7 种传播级别
┌─────────────────────────────────────────────────────────────────┐
│ 事务传播行为 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ REQUIRED(默认) │
│ ├─ 有事务 → 加入当前事务 │
│ └─ 无事务 → 创建新事务 │
│ │
│ REQUIRES_NEW │
│ ├─ 总是创建新事务 │
│ └─ 挂起当前事务(如果有) │
│ │
│ SUPPORTS │
│ ├─ 有事务 → 加入当前事务 │
│ └─ 无事务 → 非事务执行 │
│ │
│ NOT_SUPPORTED │
│ ├─ 非事务执行 │
│ └─ 挂起当前事务(如果有) │
│ │
│ MANDATORY │
│ ├─ 有事务 → 加入当前事务 │
│ └─ 无事务 → 抛异常 │
│ │
│ NEVER │
│ ├─ 非事务执行 │
│ └─ 有事务 → 抛异常 │
│ │
│ NESTED(嵌套事务) │
│ ├─ 有事务 → 创建嵌套事务(savepoint) │
│ └─ 无事务 → 创建新事务(等同 REQUIRED) │
│ │
└─────────────────────────────────────────────────────────────────┘常用场景
java
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// 保存订单
orderRepository.save(order);
// 扣减库存(REQUIRES_NEW:新事务,独立提交/回滚)
inventoryService.reduceStock(order.getItems());
// 发送消息(REQUIRES_NEW:不影响主事务)
messageService.sendOrderCreated(order);
}
}
@Service
public class InventoryService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void reduceStock(List<OrderItem> items) {
// 库存扣减逻辑
// 即使这里失败,订单已创建不会回滚
}
}传播行为对比
| 传播行为 | 有事务 | 无事务 |
|---|---|---|
| REQUIRED | 加入 | 创建 |
| REQUIRES_NEW | 挂起,创建新事务 | 创建 |
| SUPPORTS | 加入 | 非事务 |
| NOT_SUPPORTED | 挂起 | 非事务 |
| MANDATORY | 加入 | 抛异常 |
| NEVER | 抛异常 | 非事务 |
| NESTED | 嵌套(savepoint) | 创建 |
四、隔离级别
4 种隔离级别
┌─────────────────────────────────────────────────────────────────┐
│ 隔离级别 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ READ_UNCOMMITTED(读未提交) │
│ ├─ 最低隔离级别 │
│ ├─ 可能脏读、不可重复读、幻读 │
│ └─ 性能最好 │
│ │
│ READ_COMMITTED(读已提交) │
│ ├─ Oracle 默认级别 │
│ ├─ 防止脏读 │
│ ├─ 不可重复读、幻读可能 │
│ └─ 性能较好 │
│ │
│ REPEATABLE_READ(可重复读) │
│ ├─ MySQL InnoDB 默认级别 │
│ ├─ 防止脏读、不可重复读 │
│ ├─ 幻读可能(InnoDB 通过 MVCC 优化) │
│ └─ 性能一般 │
│ │
│ SERIALIZABLE(串行化) │
│ ├─ 最高隔离级别 │
│ ├─ 防止所有并发问题 │
│ └─ 性能最差,可能死锁 │
│ │
└─────────────────────────────────────────────────────────────────┘并发问题
| 问题 | 说明 |
|---|---|
| 脏读 | 读取到其他事务未提交的数据 |
| 不可重复读 | 同一事务内两次读取数据不同(其他事务提交了) |
| 幻读 | 同一事务内查询结果条数不同(其他事务插入/删除了) |
配置隔离级别
java
@Service
public class UserService {
@Transactional(isolation = Isolation.READ_COMMITTED)
public User findById(Long id) {
return userRepository.findById(id).orElseThrow();
}
}五、回滚规则
默认回滚
java
@Transactional
public void transfer(...) {
// 默认只对 RuntimeException 和 Error 回滚
// Checked Exception 不回滚
throw new RuntimeException("余额不足"); // 回滚 ✅
throw new SQLException("数据库错误"); // 回滚 ✅
throw new IOException("IO错误"); // 回滚 ✅
throw new Exception("其他异常"); // 不回滚 ❌
}配置回滚
java
@Transactional(rollbackFor = Exception.class)
// 所有 Exception 都回滚(包括 Checked Exception)
@Transactional(noRollbackFor = RuntimeException.class)
// RuntimeException 不回滚(一般不这样用)正确使用
java
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
try {
// 业务逻辑
accountRepository.save(from);
accountRepository.save(to);
} catch (BusinessException e) {
// 业务异常,回滚
throw e;
} catch (Exception e) {
// 系统异常,回滚
throw new RuntimeException("转账失败", e);
}
}六、编程式事务
TransactionTemplate
java
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
public void transfer(Long fromId, Long toId, BigDecimal amount) {
transactionTemplate.executeWithoutResult(status -> {
try {
// 业务逻辑
Account from = accountRepository.findById(fromId).orElseThrow();
from.setBalance(from.getBalance().subtract(amount));
accountRepository.save(from);
Account to = accountRepository.findById(toId).orElseThrow();
to.setBalance(to.getBalance().add(amount));
accountRepository.save(to);
} catch (Exception e) {
status.setRollbackOnly(); // 标记回滚
throw new RuntimeException("转账失败", e);
}
});
}
}返回值
java
public User findById(Long id) {
return transactionTemplate.execute(status -> {
return userRepository.findById(id).orElseThrow();
});
}
public BigDecimal calculate(Long userId) {
return transactionTemplate.execute(status -> {
// 复杂计算
return accountRepository.sumBalance(userId);
});
}七、嵌套事务
savepoint 机制
┌─────────────────────────────────────────────────────────────────┐
│ 嵌套事务 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Parent Transaction ─────────────────────────────────────────▶││
│ │ │
│ ├─ savepoint: sp1 │
│ │ │
│ ├─ Nested Transaction ──────────────────────────────▶│││
│ │ │ │││
│ │ └─ rollback to sp1 ──────────────────────▶│││││
│ │ │
│ └─ commit ───────────────────────────────────────────────▶│
│ │
│ 外层回滚 → 全部回滚 │
│ 内层回滚 → 只回滚到 savepoint,外层继续 │
│ │
└─────────────────────────────────────────────────────────────────┘示例
java
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional
public void createOrder(Order order) {
// 保存主订单
orderRepository.save(order);
try {
// 嵌套事务:处理每个订单项
for (OrderItem item : order.getItems()) {
processItem(order.getId(), item);
}
} catch (Exception e) {
// 只回滚嵌套部分,主订单不受影响
log.warn("部分订单项处理失败", e);
}
}
@Transactional(propagation = Propagation.NESTED)
public void processItem(Long orderId, OrderItem item) {
// 每个订单项的处理
// 失败时只回滚到 savepoint
}
}八、注意事项
代理问题
java
@Service
public class UserService {
@Transactional
public void save(User user) {
userRepository.save(user);
// 内部调用(绕过代理,事务不生效!)
this.sendNotification(user); // ❌ 事务不生效
}
// 解决方案1:注入自身
@Autowired
private UserService self;
public void save(User user) {
userRepository.save(user);
self.sendNotification(user); // ✅ 通过代理调用
}
// 解决方案2:AopContext
((UserService) AopContext.currentProxy()).sendNotification(user); // ✅
}自调用问题
java
// 问题代码
@Service
public class A {
@Transactional
public void methodA() {
methodB(); // 内部调用,事务不生效
}
@Transactional
public void methodB() {
// 事务生效了吗?取决于调用方式
}
}
// 正确做法
@Service
public class A {
@Autowired
private A self; // 注入自身代理
@Transactional
public void methodA() {
self.methodB(); // 通过代理调用
}
@Transactional
public void methodB() {
// 现在事务生效
}
}异常被吞
java
@Transactional
public void wrong() {
try {
// 业务逻辑
doSomething();
} catch (Exception e) {
log.error("异常", e);
// 异常被吞掉,事务不会回滚!
}
}
@Transactional
public void correct() {
try {
doSomething();
} catch (Exception e) {
log.error("异常", e);
throw e; // 重新抛出,事务才会回滚
}
}九、Spring Boot 事务配置
默认配置
yaml
spring:
transaction:
# 默认超时(秒)
default-timeout: 30
# 默认回滚异常
rollback-on-commit-failures: true多数据源事务
java
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager txManager1(EntityManagerFactory emf1) {
return new JpaTransactionManager(emf1);
}
@Bean
public PlatformTransactionManager txManager2(EntityManagerFactory emf2) {
return new JpaTransactionManager(emf2);
}
}@TransactionalOnBean
java
@Bean
@Transactional(transactionManager = "txManager2")
public UserService userService2() {
return new UserService();
}十、面试高频问题
Q1: @Transactional 哪些情况会失效?
- 非 public 方法(Spring AOP 限制)
- 内部调用(绕过代理)
- 异常被 catch 吞掉
- 传播行为设置错误
- Checked Exception 未配置 rollbackFor
Q2: REQUIRED 和 REQUIRES_NEW 区别?
- REQUIRED:加入当前事务,共享同一个事务
- REQUIRES_NEW:创建新事务,挂起当前事务
Q3: NESTED 和 REQUIRED 区别?
- REQUIRED:共享事务
- NESTED:使用 savepoint,嵌套部分失败只回滚到 savepoint
Q4: 事务隔离级别和传播行为区别?
- 隔离级别:并发事务之间如何隔离
- 传播行为:事务和方法之间的关系(新建/加入/挂起)
Q5: 如何选择隔离级别?
- 读频繁:READ_COMMITTED
- 数据一致性要求高:REPEATABLE_READ
- 写入频繁且一致性要求极高:SERIALIZABLE
十一、下一章预告
下一章我们将学习 Spring Security:
- 认证与授权流程
- JWT 实现登录
- 权限控制 RBAC
- OAuth2 第三方登录