springboot 事务(长文解析)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观
前言:事务的必要性与应用场景
在开发任何涉及数据持久化的应用时,事务(Transaction)都是一个绕不开的核心概念。想象一个简单的场景:用户向银行发起转账请求,要求将资金从账户A转移到账户B。如果在转账过程中突然断电,如何确保操作要么完全成功(钱同时从A减去并加到B),要么完全失败(钱既不减少也不增加)?这就是事务存在的意义——保证操作的原子性、一致性、隔离性和持久性(ACID)。
在Spring Boot框架中,事务管理被高度抽象,开发者无需直接操作底层数据库的事务控制语句(如BEGIN TRANSACTION
或COMMIT
),而是通过注解和配置实现声明式事务管理。本文将从基础概念、实现方式、常见问题到实战案例,逐步讲解如何在Spring Boot中高效、安全地使用事务。
一、事务的核心概念与特性
1.1 事务的ACID原则
- 原子性(Atomicity):事务中的所有操作要么全部完成,要么全部不执行。例如,转账操作若中途失败,资金不会部分扣除或添加。
- 一致性(Consistency):事务执行前后,数据必须保持合法状态。例如,转账后两个账户的总金额不变。
- 隔离性(Isolation):多个并发事务之间互不干扰。例如,两个用户同时查询同一账户余额时,不会因操作重叠导致数据混乱。
- 持久性(Durability):事务提交后,结果永久保存到数据库。即使系统崩溃,数据也能通过日志恢复。
1.2 事务的传播行为(Propagation)
在Spring中,事务的传播行为决定了多个方法调用时事务的关联方式。例如:
- PROPAGATION_REQUIRED(默认):若当前存在事务,则加入该事务;若不存在,则创建新事务。
- PROPAGATION_REQUIRES_NEW:无论是否存在事务,都创建新事务。
- PROPAGATION_SUPPORTS:若当前存在事务,则加入;否则不使用事务。
比喻:
可以将传播行为想象为快递员的工作流程:
PROPAGATION_REQUIRED
如同快递员接到新订单时,若已有同事在配送,则加入团队;否则自己独立配送。PROPAGATION_REQUIRES_NEW
则像快递员每次接单都独立行动,即使有同事在场。
二、Spring Boot中事务的实现方式
2.1 基于@Transactional注解的声明式事务
Spring Boot通过@Transactional
注解简化了事务管理。以下是一个典型的Service层示例:
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
Account fromAccount = accountRepository.findById(fromAccountId)
.orElseThrow(() -> new RuntimeException("账户不存在"));
Account toAccount = accountRepository.findById(toAccountId)
.orElseThrow(() -> new RuntimeException("账户不存在"));
fromAccount.decreaseBalance(amount);
accountRepository.save(fromAccount); // 操作1
// 假设此处发生异常(如网络中断)
if (Math.random() > 0.5) {
throw new RuntimeException("模拟转账失败");
}
toAccount.increaseBalance(amount);
accountRepository.save(toAccount); // 操作2
}
}
关键点:
@Transactional
注解标注在方法上,表示该方法内的所有数据库操作均在同一个事务中。- 若方法执行过程中抛出运行时异常(RuntimeException及其子类),事务会自动回滚;若抛出受检异常(Checked Exception),需手动配置
rollbackFor
参数。
2.2 事务的回滚控制
通过@Transactional
的rollbackFor
和noRollbackFor
属性,可以自定义回滚策略:
@Transactional(rollbackFor = {SQLException.class, MyCustomException.class})
public void riskyOperation() {
// 可能抛出SQLException或自定义异常的操作
}
三、常见问题与解决方案
3.1 事务边界问题
问题描述:若在同一个类中调用带有@Transactional
的方法,事务可能失效。
原因:Spring的AOP代理机制要求事务方法必须通过代理对象调用,而非直接调用本类方法。
解决方案:
- 将事务方法移至另一个类中,通过依赖注入调用。
- 或在配置类中启用CGLIB代理(默认为JDK动态代理,仅针对接口生效):
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class AppConfig {}
3.2 嵌套事务与隔离性
当方法A调用方法B,且两者均使用@Transactional
时:
- 若A的传播行为为
REQUIRED
,B会加入A的事务。 - 若B的传播行为为
REQUIRES_NEW
,则B会启动新事务,且独立于A的提交或回滚。
示例:
@Service
public class OrderService {
@Transactional
public void createOrder() {
// 创建订单(操作1)
saveOrder();
// 新事务:无论订单是否成功,需记录日志
logService.log("Order created"); // 假设logService方法使用PROPAGATION_REQUIRES_NEW
}
}
3.3 事务超时与隔离级别
- 超时设置:通过
@Transactional(timeout = 5)
限制事务执行时间,超时后自动回滚。 - 隔离级别:通过
isolation
参数指定(如Isolation.READ_COMMITTED
),避免脏读、幻读等问题。
四、实战案例:用户注册流程
4.1 场景描述
用户注册时需完成以下操作:
- 插入用户基本信息到
user
表。 - 插入用户角色信息到
user_role
表。 - 发送注册邮件。
若步骤1或2失败,需回滚数据库操作;但发送邮件属于外部服务,需单独处理。
4.2 代码实现
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserRoleRepository userRoleRepository;
@Autowired
private MailService mailService;
@Transactional
public void registerUser(User user, Role role) {
// 1. 保存用户基本信息
User savedUser = userRepository.save(user);
// 2. 保存角色信息
UserRole userRole = new UserRole(savedUser.getId(), role);
userRoleRepository.save(userRole);
// 3. 发送邮件(外部服务,不参与事务)
mailService.sendWelcomeEmail(user.getEmail());
}
}
关键点:
- 步骤1和2在同一个事务中,若任一操作失败(如主键冲突),数据库会回滚。
- 步骤3的邮件发送若失败,需通过补偿机制(如重试队列)处理,不影响数据库一致性。
五、性能与最佳实践
5.1 减少事务范围
- 问题:长时间持有事务锁可能导致数据库性能下降或死锁。
- 解决方案:将事务粒度控制在最小必要范围内,例如将日志记录或外部调用移出事务。
5.2 异常处理与回滚策略
- 避免在事务方法中捕获异常:否则Spring无法感知错误并触发回滚。
- 显式指定回滚策略:对于自定义异常,务必通过
rollbackFor
声明。
结论:事务管理是系统可靠性的基石
通过本文的讲解,读者应能掌握Spring Boot中事务的基本概念、实现方法及常见问题的解决方案。事务管理并非简单的“加个注解”就能完成,而是需要结合业务场景、数据库特性及系统架构综合设计。
在实际开发中,建议遵循以下原则:
- 明确事务边界:确保事务仅包含必须的操作。
- 合理配置传播行为:避免因嵌套事务导致的性能问题。
- 监控与日志:记录事务执行时间、回滚原因等,便于后续排查。
通过持续实践与优化,开发者能够将事务管理转化为提升系统健壮性的核心能力。