Java-142 深入浅出 MySQL Spring事务失效的常见场景与解决方案详解(4)
点一下关注吧!!!非常感谢!!持续更新!!!
🚀 AI篇持续更新中!(长期更新)
AI炼丹日志-31- 千呼万唤始出来 GPT-5 发布!“快的模型 + 深度思考模型 + 实时路由”,持续打造实用AI工具指南!📐🤖
💻 Java篇正式开启!(300篇)
目前2025年10月07日更新到:
Java-141 深入浅出 MySQL Spring事务失效的常见场景与解决方案详解(3)
MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务正在更新!深入浅出助你打牢基础!
📊 大数据板块已完成多项干货更新(300篇):
包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈!
大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解
事务失效场景(3)
参考来源
● https://heapdump.cn/article/5542790
续接上节
多线程调用与Spring事务机制详解
Spring事务的基本原理
Spring的事务管理是基于ThreadLocal实现的,每个线程都会维护自己独立的事务上下文。这种设计在单线程环境下能够很好地工作,但在多线程场景下会面临挑战。
多线程调用导致事务失效的原因
-
线程隔离的事务上下文
- Spring将事务状态存储在ThreadLocal变量中
- 新创建的线程无法访问父线程的ThreadLocal变量
- 导致子线程无法继承父线程的事务上下文
-
典型失效场景示例
@Transactionalpublic void parentMethod() {// 主线程中的数据库操作1repository.save(entity1);new Thread(() -> {// 子线程中的数据库操作2repository.save(entity2); // 这个操作不会在事务中执行}).start();}
解决方案与实践建议
-
避免在事务方法中创建新线程
- 这是最直接有效的解决方案
- 将多线程逻辑移到事务边界之外
-
使用编程式事务管理
public void multiThreadOperation() {// 在外部开启事务TransactionTemplate transactionTemplate = ...;transactionTemplate.execute(status -> {// 线程1操作CompletableFuture.runAsync(() -> {// 需要显式获取事务管理器DataSourceTransactionManager txManager = ...;TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition());try {// 业务操作txManager.commit(txStatus);} catch (Exception e) {txManager.rollback(txStatus);throw e;}});// 线程2操作...return null;});}
- 使用分布式事务框架
- 对于复杂的多线程事务场景
- 可考虑Seata等分布式事务解决方案
性能考量
- 多线程事务会带来额外的性能开销
- 需要权衡并发性能提升与事务管理复杂度
- 建议对关键业务进行压力测试
最佳实践
- 尽量保持事务方法简单,避免包含复杂线程逻辑
- 明确划分事务边界,一个事务最好只包含一个线程的操作
- 对必须跨线程的事务操作进行充分测试
异常捕获与Spring事务回滚机制
问题描述
在Spring框架中,当异常被捕获处理时,事务回滚机制可能会失效。这是因为Spring的事务管理是基于AOP实现的,默认情况下只对未捕获的运行时异常(RuntimeException)和错误(Error)进行回滚。
具体原因分析
-
异常捕获导致事务不回滚:
- 当异常在方法内部被try-catch块捕获并处理时,Spring的事务拦截器无法感知到这个异常
- 事务拦截器只能捕获从方法中抛出的未被处理的异常
-
捕获后未重新抛出异常:
- 即使捕获了异常,如果在catch块中没有重新抛出异常或没有手动回滚事务,事务也会正常提交
解决方案
- 在catch块中手动回滚事务:
@Transactionalpublic void someMethod() {try {// 业务代码} catch (Exception e) {// 手动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();// 可以选择重新抛出或处理异常}}
- 重新抛出异常:
@Transactionalpublic void someMethod() throws SomeException {try {// 业务代码} catch (Exception e) {// 处理异常后重新抛出throw new SomeException("处理失败", e);}}
- 配置@Transactional注解:
- 通过rollbackFor属性指定需要回滚的异常类型
@Transactional(rollbackFor = {Exception.class})public void someMethod() {// 方法实现}
最佳实践
- 对于需要事务管理的方法,尽量减少不必要的异常捕获
- 如果必须捕获异常,确保在catch块中正确处理事务状态
- 在服务层方法中抛出业务异常,在控制器层处理异常更为合理
- 明确配置@Transactional注解的rollbackFor属性,避免默认行为带来的意外
示例场景
@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate InventoryService inventoryService;@Transactional(rollbackFor = {BusinessException.class, Exception.class})public void placeOrder(Order order) throws BusinessException {try {// 保存订单orderRepository.save(order);// 扣减库存inventoryService.deductInventory(order);} catch (InventoryException e) {// 库存不足异常,手动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();throw new BusinessException("库存不足,下单失败");} catch (Exception e) {// 其他异常,同样回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();throw new BusinessException("系统异常,下单失败", e);}}
}
手动抛出异常错误详解
异常类型与事务回滚
当我们在代码中手动抛出异常时,需要特别注意异常类型与事务回滚机制之间的关系。Spring框架默认只对RuntimeException
及其子类(即未检查异常)和Error
类异常进行事务回滚。
常见的运行时异常示例:
NullPointerException
:空指针异常IllegalArgumentException
:非法参数异常IndexOutOfBoundsException
:数组越界异常UncheckedException
:自定义的未检查异常
检查异常与事务处理
对于检查异常(Checked Exception),如以下类型,默认情况下事务不会自动回滚:
IOException
:文件读写异常SQLException
:数据库操作异常FileNotFoundException
:文件未找到异常ClassNotFoundException
:类未找到异常
处理检查异常的事务回滚方案
方案一:配置回滚规则
可以通过@Transactional
注解的rollbackFor
属性显式指定需要回滚的异常类型:
@Transactional(rollbackFor = {IOException.class, SQLException.class})
public void businessMethod() throws IOException {// 业务逻辑
}
方案二:异常转换
将检查异常包装为运行时异常抛出:
public void processFile() {try {// 文件操作} catch (IOException e) {throw new RuntimeException("文件处理失败", e);}
}
最佳实践建议
- 明确区分业务异常和技术异常
- 对于需要事务回滚的检查异常,建议使用异常包装方式
- 在事务注解中显式声明需要回滚的异常类型
- 保持异常处理的统一风格,避免混合使用检查异常和运行时异常
通过正确理解异常类型与事务回滚的关系,可以确保在异常发生时业务数据的一致性得到保障。