跳到主要内容

Spring 事务管理详解

Spring事务管理是Spring框架的重要特性,它提供了声明式和编程式两种事务管理方式,简化了数据库事务的处理,确保数据的一致性和完整性。

核心价值

Spring事务 = 声明式事务 + 编程式事务 + 传播行为 + 隔离级别 + 回滚机制

  • 📌 声明式事务:通过注解轻松管理事务,无需编写模板代码
  • 🔄 编程式事务:灵活控制事务边界,满足复杂场景需求
  • 🔀 传播行为:定义事务方法间的协作方式,优化事务边界
  • 🛡️ 隔离级别:解决并发事务问题,保障数据一致性
  • 回滚机制:精确控制异常回滚策略,提升系统健壮性

1. 事务基础概念

1.1 什么是事务?

事务是数据库操作的一个逻辑单元,它要么全部成功执行,要么全部回滚。事务确保数据的一致性和完整性。

ACID特性

特性说明示例
原子性(Atomicity)事务是不可分割的工作单位转账操作要么成功要么失败
一致性(Consistency)事务执行前后数据状态一致转账前后总金额不变
隔离性(Isolation)并发事务之间相互隔离事务A看不到事务B的未提交数据
持久性(Durability)事务提交后数据永久保存提交后数据不会丢失

1.2 Spring事务管理方式

Spring提供了两种事务管理方式:声明式事务和编程式事务。

声明式事务
java
1@Service
2@Transactional
3public class UserService {
4
5 @Autowired
6 private UserRepository userRepository;
7
8 @Autowired
9 private AccountRepository accountRepository;
10
11 @Transactional
12 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}

2. 事务传播行为

2.1 传播行为类型

Spring定义了7种事务传播行为,用于控制事务方法之间的调用关系:

传播行为说明使用场景
REQUIRED如果存在事务则加入,否则创建新事务大多数业务方法
SUPPORTS如果存在事务则加入,否则以非事务方式执行查询方法
MANDATORY必须在已存在事务中执行,否则抛出异常业务方法必须在事务中执行
REQUIRES_NEW创建新事务,挂起当前事务日志记录、独立统计
NOT_SUPPORTED以非事务方式执行,挂起当前事务耗时查询
NEVER以非事务方式执行,如果存在事务则抛出异常确保方法在非事务环境执行
NESTED嵌套事务,可独立回滚批量操作中的单条保存
事务传播行为示例
java
1@Service
2public class UserService {
3
4 @Autowired
5 private OrderService orderService;
6
7 @Transactional
8 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}
18
19@Service
20public class OrderService {
21
22 @Autowired
23 private OrderRepository orderRepository;
24
25 @Autowired
26 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 隔离级别类型

事务隔离级别示例
java
1@Service
2public 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串行化最低

并发问题示例

并发问题示例
java
1// 1. 脏读问题
2@Service
3public 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}
21
22// 2. 不可重复读问题
23@Service
24public 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 自动回滚

自动回滚示例
java
1@Service
2public class UserService {
3
4 @Transactional
5 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 手动回滚

手动回滚示例
java
1@Service
2public class UserService {
3
4 @Autowired
5 private TransactionTemplate transactionTemplate;
6
7 public void createUserWithManualRollback(User user) {
8 transactionTemplate.execute(new TransactionCallbackWithoutResult() {
9 @Override
10 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}
异常回滚规则总结
  1. 默认情况下,Spring只对运行时异常(RuntimeException及其子类)和Error进行回滚
  2. 受检异常(checked exception)不会触发回滚
  3. 可以通过rollbackFor指定需要回滚的异常类型
  4. 可以通过noRollbackFor指定不需要回滚的异常类型
  5. 编程式事务中可以通过status.setRollbackOnly()手动标记事务回滚

5. 事务失效场景

5.1 常见失效场景

java
1@Service
2public class TransactionFailureExample {
3 // 1. 非public方法
4 @Transactional
5 private void privateMethod() {
6 // 事务不会生效
7 userRepository.save(new User());
8 }
9}

原因: Spring AOP代理只拦截public方法调用,非public方法不会创建代理

解决方法: 确保事务方法是public的

5.2 解决方案

事务失效解决方案
java
1@Service
2public class TransactionSolutionExample {
3
4 @Autowired
5 private ApplicationContext applicationContext;
6
7 // 1. 解决自调用问题 - 使用AopContext
8 @Transactional
9 public void methodA() {
10 userRepository.save(new User());
11
12 // 获取代理对象
13 UserService proxy = (UserService) AopContext.currentProxy();
14 proxy.methodB(); // 事务生效
15 }
16
17 // 2. 解决自调用问题 - 注入自身
18 @Autowired
19 private UserService self;
20
21 @Transactional
22 public void methodAWithSelf() {
23 userRepository.save(new User());
24 self.methodB(); // 事务生效
25 }
26
27 // 3. 解决异常捕获问题
28 @Transactional
29 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 事务设计原则

事务设计的五项原则

  1. 原子性原则:事务方法应该尽可能小,只包含必要的数据库操作
  2. 性能原则:避免在事务中进行耗时操作,如远程调用、文件IO等
  3. 隔离原则:选择合适的隔离级别,避免并发问题
  4. 传播原则:合理使用事务传播行为,避免事务嵌套过深
  5. 异常处理原则:明确定义回滚规则,不捕获异常或重新抛出异常
java
1@Service
2public class TransactionBestPractice {
3 // 1. 事务方法要尽可能小
4 @Transactional
5 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}

6.2 读写分离

读写分离示例
java
1@Service
2public class UserReadWriteService {
3
4 @Autowired
5 private UserRepository userRepository;
6
7 // 写操作
8 @Transactional
9 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模式:长事务的补偿模式
  • 消息事务:基于消息的最终一致性
  • 本地消息表:结合本地事务和消息队列
事务管理学习要点
  1. 理解ACID特性:掌握事务的原子性、一致性、隔离性、持久性
  2. 熟悉传播行为:了解7种事务传播行为的使用场景
  3. 掌握隔离级别:理解不同隔离级别的特点和适用场景
  4. 学会异常处理:掌握事务回滚和异常处理机制
  5. 避免失效场景:了解事务失效的原因和解决方案

通过本章的学习,你应该已经掌握了Spring事务管理的核心概念、传播行为、隔离级别和最佳实践。事务管理是保证数据一致性的重要技术,在实际项目中合理使用Spring事务可以确保业务数据的完整性和可靠性。

评论