Spring 事务管理是 Spring 框架最核心功能之一,它通过声明式事务 和编程式事务 两种方式,将开发者从繁琐的 JDBC 事务控制中解放出来,实现 "注解一行,事务全权托管" 的极致简洁
一、核心概念:ACID 与传播机制
1. 事务的 ACID 特性
①.原子性 (Atomicity):事务是最小执行单元,要么全做,要么全不做
②.一致性 (Consistency):事务完成后,数据库状态必须保持一致
③.隔离性 (Isolation):多个事务并发执行时互不影响
④.持久性(Durability):事务提交后,数据永久保存
2. Spring 事务三要素
java
// 1. 事务管理器(PlatformTransactionManager)
// 2. 事务属性(TransactionDefinition)
// 3. 事务状态(TransactionStatus)
二、事务管理方式:声明式 vs 编程式
1. 声明式事务(@Transactional 注解)【推荐】
java
// 在类或方法上添加注解,Spring 自动创建代理
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private AccountService accountService;
// 完整配置示例
@Transactional(
propagation = Propagation.REQUIRED, // 传播行为
isolation = Isolation.READ_COMMITTED, // 隔离级别
readOnly = false, // 是否只读
timeout = 30, // 超时时间(秒)
rollbackFor = {InsufficientBalanceException.class}, // 回滚异常
noRollbackFor = {DataNotFoundException.class} // 不回滚异常
)
public void createOrder(Order order) {
// 1. 扣减库存
inventoryService.reduceStock(order.getProductId(), order.getQuantity());
// 2. 扣减余额
accountService.deductBalance(order.getUserId(), order.getAmount());
// 3. 创建订单
orderRepository.save(order);
// 4. 发送消息(异常会触发回滚)
messageService.sendOrderCreatedEvent(order);
// 如果以上任何一步抛出异常,所有操作自动回滚
}
}
工作原理:
java
// Spring 创建代理对象(JDK 动态代理或 CGLIB)
OrderService proxy = new OrderServiceProxy();
// 代理增强逻辑
public void createOrder(Order order) {
TransactionStatus status = transactionManager.getTransaction(definition);
try {
target.createOrder(order); // 执行业务代码
transactionManager.commit(status); // 成功则提交
} catch (Exception ex) {
transactionManager.rollback(status); // 异常则回滚
throw ex;
}
}
2. 编程式事务(TransactionTemplate)
适用于事务逻辑复杂、需要细粒度控制的场景:
java
@Service
public class TransferService {
@Autowired
private TransactionTemplate transactionTemplate;
public void transfer(Long fromAccount, Long toAccount, BigDecimal amount) {
// 编程式控制事务
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 执行转账逻辑
accountDao.debit(fromAccount, amount);
accountDao.credit(toAccount, amount);
} catch (InsufficientBalanceException ex) {
status.setRollbackOnly(); // 手动标记回滚
}
}
});
}
// 带返回值版本
public String executeInTransaction() {
return transactionTemplate.execute(status -> {
// 业务逻辑
return "success";
});
}
}
更底层的方式(PlatformTransactionManager):
java
@Autowired
private PlatformTransactionManager transactionManager;
public void manualTransaction() {
// 1. 定义事务属性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 2. 开启事务
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 业务逻辑
// ...
// 3. 提交
transactionManager.commit(status);
} catch (Exception ex) {
// 4. 回滚
transactionManager.rollback(status);
throw ex;
}
}
三、事务传播行为(Propagation)
传播行为定义:当一个事务方法调用另一个事务方法时,事务如何传播?
java
@Transactional(propagation = Propagation.REQUIRED) // 默认
public void methodA() {
methodB(); // methodB 的事务如何加入?
}
7 种传播行为详解
| 传播行为 | 含义 | 使用场景 | 示例代码 |
|---|---|---|---|
| REQUIRED | 默认。当前有事务则加入,无则新建 | 通用场景 | @Transactional(propagation = REQUIRED) |
| REQUIRES_NEW | 挂起当前事务,新建独立事务 | 审计日志(记录失败) | saveAuditLog() 需独立提交 |
| SUPPORTS | 当前有事务则加入,无为非事务 | 查询方法 | findUser() 可共用事务 |
| NOT_SUPPORTED | 挂起当前事务,以非事务执行 | 批量查询(加快速度) | batchQuery() |
| MANDATORY | 必须有事务,无则抛异常 | 强制事务环境 | 内部服务方法 |
| NEVER | 必须无事务,有则抛异常 | 检查类方法 | validateData() |
| NESTED | 嵌套事务,可独立回滚 | 部分回滚场景 | saveMain() 中 saveSub() 可单独回 |
传播行为流程图:
bash
调用者事务状态 → 传播行为 → 被调用者事务状态
无事务 → REQUIRED → 新建事务
有事务 → REQUIRED → 加入事务
无事务 → REQUIRES_NEW → 新建事务
有事务 → REQUIRES_NEW → 挂起原事务,新建
无事务 → NESTED → 新建事务
有事务 → NESTED → 创建 SavePoint(嵌套)
传播行为实战案例
案例 1:REQUIRES_NEW 独立审计日志
java
@Service
public class OrderService {
@Autowired
private AuditService auditService;
@Transactional
public void createOrder(Order order) {
try {
// 保存订单
orderDao.save(order);
// 扣减库存(可能失败)
inventoryService.reduceStock(order.getProductId());
} finally {
// 审计日志必须保存,即使下单失败
auditService.logOrderCreation(order); // REQUIRES_NEW
}
}
}
@Service
public class AuditService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOrderCreation(Order order) {
auditDao.save(new AuditLog(order)); // 独立事务,不受回滚影响
}
}
案例 2:NESTED 嵌套事务(部分回滚)
java
@Service
public class TransferService {
@Transactional
public void transferWithFee(Long from, Long to, BigDecimal amount) {
// 主事务:转账
accountDao.debit(from, amount);
accountDao.credit(to, amount);
try {
// 嵌套事务:扣手续费(可独立回滚)
feeService.deductFee(from, amount.multiply(new BigDecimal("0.01"))); // NESTED
} catch (Exception e) {
// 手续费失败不影响主转账
log.warn("手续费扣取失败", e);
}
}
}
@Service
public class FeeService {
@Transactional(propagation = Propagation.NESTED)
public void deductFee(Long account, BigDecimal fee) {
accountDao.debit(account, fee);
// 如果失败,仅回滚 NESTED 部分,主事务不受影响
}
}
四、事务隔离级别(Isolation)
隔离级别定义:多个事务并发读写时,如何隔离数据以防止脏读、不可重复读、幻读?
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 使用场景 |
|---|---|---|---|---|
| DEFAULT | - | - | - | 默认,使用数据库默认级别(MySQL: RR, Oracle: RC) |
| READ_UNCOMMITTED | ✅ 可能 | ✅ 可能 | ✅ 可能 | 极少使用,性能最高,一致性最差 |
| READ_COMMITTED | ❌ 不可能 | ✅ 可能 | ✅ 可能 | 最常用,读已提交数据(Oracle/SQL Server 默认) |
| REPEATABLE_READ | ❌ 不可能 | ❌ 不可能 | ✅ 可能 | MySQL 默认,保证同一事务多次读一致 |
| SERIALIZABLE | ❌ 不可能 | ❌ 不可能 | ❌ 不可能 | 最严格,串行执行,性能最低 |
三种读异常的解释:
1. 脏读(Dirty Read)
java
// 事务 A 读取事务 B 未提交的修改
事务 A: SELECT balance = 1000 (脏读)
事务 B: UPDATE balance = 500 (未提交)
事务 A: SELECT balance = 500 (读取到未提交数据)
事务 B: ROLLBACK (回滚)
事务 A: 基于错误的 500 做业务判断 → 数据不一致
2. 不可重复读(Non-repeatable Read)
java
// 同一事务内两次读取结果不一致
事务 A: SELECT balance = 1000
事务 B: UPDATE balance = 500 AND COMMIT
事务 A: SELECT balance = 500 (两次读结果不同)
3. 幻读(Phantom Read)
java
// 同一事务内两次查询记录数不一致
事务 A: SELECT COUNT(*) FROM users WHERE age > 18 → 10 条
事务 B: INSERT INTO users (age=20) AND COMMIT
事务 A: SELECT COUNT(*) FROM users WHERE age > 18 → 11 条 (幻影行)
隔离级别配置示例
java
@Service
public class ReportService {
// 报表查询:允许不可重复读,提高并发性能
@Transactional(isolation = Isolation.READ_COMMITTED, readOnly = true)
public Report generateReport(Date start, Date end) {
// 多次读取统计数据,允许中间数据变化
List<Order> orders = orderDao.findByDateRange(start, end);
BigDecimal total = orderDao.sumAmount(start, end);
return new Report(orders, total);
}
// 金融转账:必须可重复读
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void transfer(Long from, Long to, BigDecimal amount) {
// 查询余额(锁住行,防止其他事务修改)
Account fromAcc = accountDao.findById(from, LockModeType.PESSIMISTIC_READ);
// 再次确认余额(必须一致)
if (fromAcc.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException();
}
// 执行转账
accountDao.debit(from, amount);
accountDao.credit(to, amount);
}
}
五、事务失效的 8 大陷阱
陷阱 1:自调用问题(最常见)
java
@Service
public class OrderService {
public void createOrder(Order order) {
// 直接调用本类方法,不走代理,事务失效!
this.saveOrder(order); // ❌ 事务不生效
}
@Transactional
public void saveOrder(Order order) {
orderDao.save(order);
inventoryService.reduceStock(order.getProductId());
}
}
// ✅ 解决方法:注入自己(代理对象)
@Service
public class OrderService {
@Autowired
private OrderService self; // 注入代理对象
public void createOrder(Order order) {
self.saveOrder(order); // ✅ 走代理,事务生效
}
}
陷阱 2:访问修饰符非 public
java
@Transactional // ❌ 只对 public 方法有效
private void saveOrder(Order order) { ... } // 改为 public
// 正确
@Transactional
public void saveOrder(Order order) { ... }
陷阱 3:异常被捕获
java
@Transactional
public void createOrder(Order order) {
try {
orderDao.save(order);
inventoryService.reduceStock(order.getProductId()); // 抛出异常
} catch (Exception e) {
log.error(e); // ❌ 吃掉异常,事务不 rollback
}
}
// ✅ 正确:抛出异常或手动回滚
@Transactional
public void createOrder(Order order) throws InsufficientStockException {
orderDao.save(order);
inventoryService.reduceStock(order.getProductId()); // 抛出异常
}
陷阱 4:检查型异常不回滚
java
@Transactional
public void createOrder(Order order) throws IOException { // IOException 是检查型异常
orderDao.save(order);
fileService.writeLog(order); // 抛出 IOException
} // ❌ 默认不回滚
// ✅ 解决方案1:指定 rollbackFor
@Transactional(rollbackFor = Exception.class)
// ✅ 解决方案2:抛出运行时异常
throw new RuntimeException("IO error", e);
默认回滚规则:
①.自动回滚 :RuntimeException 及其子类、Error
②.不回滚:CheckedException(如 IOException、SQLException)
陷阱 5:多数据源未指定事务管理器
java
// 配置两个数据源
@Bean
public PlatformTransactionManager orderTxManager() {
return new DataSourceTransactionManager(orderDataSource());
}
@Bean
public PlatformTransactionManager accountTxManager() {
return new DataSourceTransactionManager(accountDataSource());
}
// 使用
@Transactional(transactionManager = "orderTxManager") // ✅ 必须指定
public void createOrder() { ... }
陷阱 6:异步线程中事务失效
java
@Transactional
public void createOrder(Order order) {
orderDao.save(order);
// ❌ 在新线程中执行,事务上下文丢失
new Thread(() -> {
notificationService.sendSms(order); // 事务失效
}).start();
}
// ✅ 使用 @Async(需配置事务传播)
@Transactional
public void createOrder(Order order) {
orderDao.save(order);
asyncNotificationService.sendSms(order); // 独立事务
}
@Service
public class AsyncNotificationService {
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendSms(Order order) { ... }
}
陷阱 7:事务超时设置不当
java
@Transactional(timeout = 5) // 5 秒超时
public void batchProcess(List<Data> dataList) {
for (Data data : dataList) {
process(data); // 处理 10000 条数据,耗时 10 秒
// ❌ 超时回滚,但可能已经部分提交
}
}
// ✅ 批量分片
@Transactional(timeout = 30) // 给足够时间
public void batchProcess(List<Data> dataList) {
for (int i = 0; i < dataList.size(); i += 100) {
List<Data> batch = dataList.subList(i, Math.min(i + 100, dataList.size()));
processBatch(batch); // 每批 100 条,快速完成
}
}
陷阱 8:只读事务误用
java
@Transactional(readOnly = true) // 标记只读
public void createOrder(Order order) {
orderDao.save(order); // ❌ 只读事务中执行写操作,可能不生效
}
// ✅ 读写分离
@Transactional(readOnly = true)
public Order getOrder(Long id) { // 查询用只读
return orderDao.findById(id);
}
@Transactional // 写操作不用 readOnly
public void createOrder(Order order) { // 创建用读写
orderDao.save(order);
}
只读事务优势:
①.数据库可优化为只读连接
②.MySQL 下不会创建 read view,减少 MVCC 开销
③.性能提升:查询速度提升 20-30%
六、最佳实践总结
1. 默认配置(90% 场景)
java
@Transactional // 全部默认即可
public void businessMethod() { ... }
2. 只读查询优化
java
@Transactional(readOnly = true) // 查询专用
public List<User> searchUsers() { ... }
3. 独立审计日志
java
@Transactional(propagation = Propagation.REQUIRES_NEW) // 独立提交
public void auditLog() { ... }
4. 超时与回滚精准控制
java
@Transactional(
timeout = 10,
rollbackFor = BusinessException.class,
noRollbackFor = DataValidationException.class
)
public void preciseControl() { ... }
5. 事务方法入口原则
java
// 所有事务从 Service 层入口方法开始
@Transactional
public void serviceMethod() {
dao1.update();
dao2.update(); // 同一事务
externalService.call(); // 可能开启新事务
}
总结
Spring 事务管理 = 声明式注解 + 传播行为控制 + 隔离级别防护 + AOP 代理增强。掌握事务失效的 8 大陷阱,才能写出健壮的企业级代码