Spring 事务管理详解
Spring事务管理是Spring框架的重要特性,它提供了声明式和编程式两种事务管理方式,简化了数据库事务的处理,确保数据的一致性和完整性。
Spring事务 = 声明式事务 + 编程式事务 + 传播行为 + 隔离级别 + 回滚机制
- 📌 声明式事务:通过注解轻松管理事务,无需编写模板代码
- 🔄 编程式事务:灵活控制事务边界,满足复杂场景需求
- 🔀 传播行为:定义事务方法间的协作方式,优化事务边界
- 🛡️ 隔离级别:解决并发事务问题,保障数据一致性
- ⏪ 回滚机制:精确控制异常回滚策略,提升系统健壮性
1. 事务基础概念
1.1 什么是事务?
事务是数据库操作的一个逻辑单元,它要么全部成功执行,要么全部回滚。事务确保数据的一致性和完整性。
ACID特性
| 特性 | 说明 | 示例 |
|---|---|---|
| 原子性(Atomicity) | 事务是不可分割的工作单位 | 转账操作要么成功要么失败 |
| 一致性(Consistency) | 事务执行前后数据状态一致 | 转账前后总金额不变 |
| 隔离性(Isolation) | 并发事务之间相互隔离 | 事务A看不到事务B的未提交数据 |
| 持久性(Durability) | 事务提交后数据永久保存 | 提交后数据不会丢失 |
1.2 Spring事务管理方式
Spring提供了两种事务管理方式:声明式事务和编程式事务。
- 声明式事务
- 编程式事务
- 对比分析
1@Service2@Transactional3public class UserService {4 5 @Autowired6 private UserRepository userRepository;7 8 @Autowired9 private AccountRepository accountRepository;10 11 @Transactional12 public void transferMoney(Long fromUserId, Long toUserId, BigDecimal amount) {13 // 扣款14 Account fromAccount = accountRepository.findByUserId(fromUserId);15 fromAccount.setBalance(fromAccount.getBalance().subtract(amount));16 accountRepository.save(fromAccount);17 18 // 加款19 Account toAccount = accountRepository.findByUserId(toUserId);20 toAccount.setBalance(toAccount.getBalance().add(amount));21 accountRepository.save(toAccount);22 }23}1@Service2public class UserService {3 4 @Autowired5 private TransactionTemplate transactionTemplate;6 7 @Autowired8 private UserRepository userRepository;9 10 public void createUser(User user) {11 transactionTemplate.execute(new TransactionCallbackWithoutResult() {12 @Override13 protected void doInTransactionWithoutResult(TransactionStatus status) {14 try {15 userRepository.save(user);16 // 其他业务逻辑17 } catch (Exception e) {18 status.setRollbackOnly();19 throw e;20 }21 }22 });23 }24}声明式事务 vs 编程式事务
| 特性 | 声明式事务 | 编程式事务 |
|---|---|---|
| 使用方式 | 注解配置 | 代码控制 |
| 代码侵入性 | 低 | 高 |
| 灵活性 | 较低 | 很高 |
| 适用场景 | 大多数业务场景 | 复杂事务控制 |
| 学习曲线 | 平缓 | 较陡 |
| 维护成本 | 低 | 高 |
2. 事务传播行为
2.1 传播行为类型
Spring定义了7种事务传播行为,用于控制事务方法之间的调用关系:
| 传播行为 | 说明 | 使用场景 |
|---|---|---|
| REQUIRED | 如果存在事务则加入,否则创建新事务 | 大多数业务方法 |
| SUPPORTS | 如果存在事务则加入,否则以非事务方式执行 | 查询方法 |
| MANDATORY | 必须在已存在事务中执行,否则抛出异常 | 业务方法必须在事务中执行 |
| REQUIRES_NEW | 创建新事务,挂起当前事务 | 日志记录、独立统计 |
| NOT_SUPPORTED | 以非事务方式执行,挂起当前事务 | 耗时查询 |
| NEVER | 以非事务方式执行,如果存在事务则抛出异常 | 确保方法在非事务环境执行 |
| NESTED | 嵌套事务,可独立回滚 | 批量操作中的单条保存 |
1@Service2public class UserService {3 4 @Autowired5 private OrderService orderService;6 7 @Transactional8 public void createUserWithOrders(User user) {9 userRepository.save(user);10 11 // REQUIRED: 和当前事务在同一个事务中12 orderService.createOrder(user);13 14 // REQUIRES_NEW: 独立的新事务15 orderService.createUserLog(user);16 }17}1819@Service20public class OrderService {21 22 @Autowired23 private OrderRepository orderRepository;24 25 @Autowired26 private LogService logService;27 28 @Transactional(propagation = Propagation.REQUIRED)29 public void createOrder(User user) {30 Order order = new Order();31 order.setUserId(user.getId());32 orderRepository.save(order);33 }34 35 @Transactional(propagation = Propagation.REQUIRES_NEW)36 public void createUserLog(User user) {37 UserLog log = new UserLog();38 log.setUserId(user.getId());39 log.setOperation("CREATE_USER");40 logService.save(log);41 }42}3. 事务隔离级别
3.1 隔离级别类型
1@Service2public class UserService {3 4 // 1. READ_UNCOMMITTED - 读未提交5 @Transactional(isolation = Isolation.READ_UNCOMMITTED)6 public User getUserWithDirtyRead(Long id) {7 return userRepository.findById(id).orElse(null);8 }9 10 // 2. READ_COMMITTED - 读已提交(默认)11 @Transactional(isolation = Isolation.READ_COMMITTED)12 public User getUserWithReadCommitted(Long id) {13 return userRepository.findById(id).orElse(null);14 }15 16 // 3. REPEATABLE_READ - 可重复读17 @Transactional(isolation = Isolation.REPEATABLE_READ)18 public User getUserWithRepeatableRead(Long id) {19 return userRepository.findById(id).orElse(null);20 }21 22 // 4. SERIALIZABLE - 串行化23 @Transactional(isolation = Isolation.SERIALIZABLE)24 public User getUserWithSerializable(Long id) {25 return userRepository.findById(id).orElse(null);26 }27}3.2 隔离级别详解
| 隔离级别 | 说明 | 问题 | 性能 |
|---|---|---|---|
| READ_UNCOMMITTED | 读未提交 | 脏读、不可重复读、幻读 | 最高 |
| READ_COMMITTED | 读已提交 | 不可重复读、幻读 | 高 |
| REPEATABLE_READ | 可重复读 | 幻读 | 中等 |
| SERIALIZABLE | 串行化 | 无 | 最低 |
并发问题示例
1// 1. 脏读问题2@Service3public class DirtyReadExample {4 5 @Transactional(isolation = Isolation.READ_UNCOMMITTED)6 public void updateUserBalance(Long userId, BigDecimal amount) {7 // 事务A:更新余额但未提交8 Account account = accountRepository.findByUserId(userId);9 account.setBalance(account.getBalance().add(amount));10 accountRepository.save(account);11 // 此时事务B可以读到未提交的数据12 }13 14 @Transactional(isolation = Isolation.READ_UNCOMMITTED)15 public BigDecimal getUserBalance(Long userId) {16 // 事务B:可能读到事务A未提交的数据17 Account account = accountRepository.findByUserId(userId);18 return account.getBalance();19 }20}2122// 2. 不可重复读问题23@Service24public class NonRepeatableReadExample {25 26 @Transactional(isolation = Isolation.READ_COMMITTED)27 public void updateUser(Long userId, String name) {28 // 事务A:更新用户名29 User user = userRepository.findById(userId).orElse(null);30 user.setName(name);31 userRepository.save(user);32 }33 34 @Transactional(isolation = Isolation.READ_COMMITTED)35 public void readUserTwice(Long userId) {36 // 事务B:两次读取可能得到不同结果37 User user1 = userRepository.findById(userId).orElse(null);38 // 事务A提交39 User user2 = userRepository.findById(userId).orElse(null);40 // user1.getName() != user2.getName()41 }42}4. 事务回滚机制
4.1 自动回滚
1@Service2public class UserService {3 4 @Transactional5 public void createUser(User user) {6 try {7 userRepository.save(user);8 // 如果这里抛出异常,事务会自动回滚9 if (user.getAge() < 0) {10 throw new IllegalArgumentException("年龄不能为负数");11 }12 } catch (Exception e) {13 // 异常会被重新抛出,触发事务回滚14 throw e;15 }16 }17 18 // 指定回滚异常19 @Transactional(rollbackFor = {IllegalArgumentException.class, SQLException.class})20 public void createUserWithCustomRollback(User user) {21 userRepository.save(user);22 if (user.getAge() < 0) {23 throw new IllegalArgumentException("年龄不能为负数");24 }25 }26 27 // 指定不回滚异常28 @Transactional(noRollbackFor = {BusinessException.class})29 public void createUserWithNoRollback(User user) {30 userRepository.save(user);31 if (user.getName().isEmpty()) {32 throw new BusinessException("用户名不能为空");33 }34 }35}4.2 手动回滚
1@Service2public class UserService {3 4 @Autowired5 private TransactionTemplate transactionTemplate;6 7 public void createUserWithManualRollback(User user) {8 transactionTemplate.execute(new TransactionCallbackWithoutResult() {9 @Override10 protected void doInTransactionWithoutResult(TransactionStatus status) {11 try {12 userRepository.save(user);13 14 // 业务逻辑检查15 if (user.getAge() < 0) {16 // 手动设置回滚17 status.setRollbackOnly();18 return;19 }20 21 // 其他业务逻辑22 } catch (Exception e) {23 // 异常时自动回滚24 status.setRollbackOnly();25 throw e;26 }27 }28 });29 }30}异常回滚规则总结
- 默认情况下,Spring只对运行时异常(RuntimeException及其子类)和Error进行回滚
- 受检异常(checked exception)不会触发回滚
- 可以通过
rollbackFor指定需要回滚的异常类型 - 可以通过
noRollbackFor指定不需要回滚的异常类型 - 编程式事务中可以通过
status.setRollbackOnly()手动标记事务回滚
5. 事务失效场景
5.1 常见失效场景
- 非public方法
- 自调用问题
- 异常被捕获
- 异常类型不匹配
1@Service2public class TransactionFailureExample {3 // 1. 非public方法4 @Transactional5 private void privateMethod() {6 // 事务不会生效7 userRepository.save(new User());8 }9}原因: Spring AOP代理只拦截public方法调用,非public方法不会创建代理
解决方法: 确保事务方法是public的
1@Service2public class TransactionFailureExample {3 // 2. 自调用问题4 @Transactional5 public void methodA() {6 // 事务生效7 userRepository.save(new User());8 9 // 自调用,事务不会生效10 this.methodB();11 }12 13 @Transactional14 public void methodB() {15 // 这个方法的事务不会生效16 userRepository.save(new User());17 }18}原因: Spring AOP代理基于代理模式,只有通过代理对象调用方法时才会创建事务
解决方法: 通过代理对象调用方法(使用AopContext或注入自身)
1@Transactional2public void methodWithCaughtException() {3 try {4 userRepository.save(new User());5 throw new RuntimeException("测试异常");6 } catch (Exception e) {7 // 异常被捕获,事务不会回滚8 log.error("异常被捕获", e);9 }10}原因: Spring事务在方法返回时检查是否有未处理的异常,如果异常被捕获,事务不会回滚
解决方法: 捕获异常后重新抛出或设置事务状态为回滚
1@Transactional(rollbackFor = SQLException.class)2public void methodWithWrongException() {3 userRepository.save(new User());4 throw new RuntimeException("运行时异常"); // 此处抛出的是RuntimeException5}原因: 指定了特定类型的异常才会回滚,但抛出了不同类型的异常
解决方法: 确保rollbackFor包含可能抛出的异常类型
5.2 解决方案
1@Service2public class TransactionSolutionExample {3 4 @Autowired5 private ApplicationContext applicationContext;6 7 // 1. 解决自调用问题 - 使用AopContext8 @Transactional9 public void methodA() {10 userRepository.save(new User());11 12 // 获取代理对象13 UserService proxy = (UserService) AopContext.currentProxy();14 proxy.methodB(); // 事务生效15 }16 17 // 2. 解决自调用问题 - 注入自身18 @Autowired19 private UserService self;20 21 @Transactional22 public void methodAWithSelf() {23 userRepository.save(new User());24 self.methodB(); // 事务生效25 }26 27 // 3. 解决异常捕获问题28 @Transactional29 public void methodWithProperException() {30 try {31 userRepository.save(new User());32 throw new RuntimeException("测试异常");33 } catch (Exception e) {34 log.error("异常被捕获", e);35 // 重新抛出异常,触发回滚36 throw new RuntimeException("重新抛出异常", e);37 }38 }39}使用AopContext需要在配置类上添加@EnableAspectJAutoProxy(exposeProxy = true)注解
6. 事务最佳实践
6.1 事务设计原则
事务设计的五项原则
- 原子性原则:事务方法应该尽可能小,只包含必要的数据库操作
- 性能原则:避免在事务中进行耗时操作,如远程调用、文件IO等
- 隔离原则:选择合适的隔离级别,避免并发问题
- 传播原则:合理使用事务传播行为,避免事务嵌套过深
- 异常处理原则:明确定义回滚规则,不捕获异常或重新抛出异常
- 事务方法要小
- 避免耗时操作
- 合理使用传播行为
- 完整配置
1@Service2public class TransactionBestPractice {3 // 1. 事务方法要尽可能小4 @Transactional5 public void createUser(User user) {6 // 只包含必要的数据库操作7 validateUser(user); // 验证逻辑应尽量放在事务外8 userRepository.save(user);9 }10 11 // 非事务方法12 private void validateUser(User user) {13 // 验证逻辑14 }15}1@Service2public class TransactionBestPractice {3 // 2. 避免在事务中进行耗时操作4 @Transactional5 public void createUserWithAsyncOperation(User user) {6 userRepository.save(user);7 8 // 异步处理耗时操作9 CompletableFuture.runAsync(() -> {10 sendWelcomeEmail(user.getEmail());11 updateUserStatistics();12 });13 }14}1@Service2public class TransactionBestPractice {3 // 3. 合理使用事务传播行为4 @Transactional(propagation = Propagation.REQUIRED)5 public void createUserWithOrder(User user, Order order) {6 userRepository.save(user);7 8 // 订单创建使用独立事务9 orderService.createOrderWithNewTransaction(order);10 }11 12 // 订单服务13 @Service14 public class OrderService {15 @Transactional(propagation = Propagation.REQUIRES_NEW)16 public void createOrderWithNewTransaction(Order order) {17 // 独立事务,即使用户创建失败,订单也可以提交18 orderRepository.save(order);19 }20 }21}1// 4. 使用@Transactional注解的完整配置2@Transactional(3 propagation = Propagation.REQUIRED,4 isolation = Isolation.READ_COMMITTED,5 timeout = 30,6 rollbackFor = {Exception.class},7 readOnly = false8)9public void completeTransactionExample(User user) {10 // 完整的事务配置示例11 userRepository.save(user);12}6.2 读写分离
1@Service2public class UserReadWriteService {3 4 @Autowired5 private UserRepository userRepository;6 7 // 写操作8 @Transactional9 public User createUser(User user) {10 return userRepository.save(user);11 }12 13 // 读操作14 @Transactional(readOnly = true)15 public User findUserById(Long id) {16 return userRepository.findById(id).orElse(null);17 }18 19 // 批量读取操作20 @Transactional(readOnly = true)21 public List<User> findAllUsers() {22 return userRepository.findAll();23 }24}7. 面试题精选
7.1 基础概念题
Q: 什么是事务?Spring事务管理的优势是什么?
A: 事务是数据库操作的一个逻辑单元,具有ACID特性。Spring事务管理的优势包括:
- 声明式事务:通过注解简化事务配置
- 编程式事务:提供灵活的事务控制
- 传播行为:支持复杂的事务嵌套
- 隔离级别:提供不同的事务隔离级别
- 自动回滚:异常时自动回滚事务
Q: Spring事务的传播行为有哪些?
A: Spring定义了7种事务传播行为:
- REQUIRED:支持当前事务,不存在则创建新事务
- REQUIRES_NEW:创建新事务,挂起当前事务
- SUPPORTS:支持当前事务,不存在则以非事务执行
- NOT_SUPPORTED:以非事务执行,挂起当前事务
- MANDATORY:必须在事务中执行,否则抛出异常
- NEVER:不能在事务中执行,否则抛出异常
- NESTED:嵌套事务,可以独立回滚
7.2 实践题
Q: 事务的隔离级别有哪些?各有什么特点?
A: 事务隔离级别包括:
- READ_UNCOMMITTED:读未提交,性能最高但存在脏读问题
- READ_COMMITTED:读已提交,避免脏读但存在不可重复读
- REPEATABLE_READ:可重复读,避免不可重复读但存在幻读
- SERIALIZABLE:串行化,避免所有并发问题但性能最低
Q: 什么情况下Spring事务会失效?
A: Spring事务失效的常见情况:
- 非public方法:@Transactional注解在非public方法上不生效
- 自调用问题:同一个类中的方法调用不会经过代理
- 异常被捕获:异常被catch捕获后不会触发回滚
- 异常类型不匹配:抛出的异常类型不在rollbackFor中
- 数据库不支持:使用的数据库不支持事务
7.3 高级题
Q: 如何解决Spring事务的自调用问题?
A: 解决自调用问题的方法:
- 使用AopContext:通过AopContext.currentProxy()获取代理对象
- 注入自身:使用@Autowired注入自身的代理对象
- 提取方法:将需要事务的方法提取到另一个类中
- 使用AspectJ:使用AspectJ编译时织入
Q: 如何设计一个分布式事务?
A: 分布式事务设计考虑:
- 2PC/3PC协议:两阶段/三阶段提交协议
- TCC模式:Try-Confirm-Cancel模式
- Saga模式:长事务的补偿模式
- 消息事务:基于消息的最终一致性
- 本地消息表:结合本地事务和消息队列
- 理解ACID特性:掌握事务的原子性、一致性、隔离性、持久性
- 熟悉传播行为:了解7种事务传播行为的使用场景
- 掌握隔离级别:理解不同隔离级别的特点和适用场景
- 学会异常处理:掌握事务回滚和异常处理机制
- 避免失效场景:了解事务失效的原因和解决方案
通过本章的学习,你应该已经掌握了Spring事务管理的核心概念、传播行为、隔离级别和最佳实践。事务管理是保证数据一致性的重要技术,在实际项目中合理使用Spring事务可以确保业务数据的完整性和可靠性。
评论