Java-141 深入浅出 MySQL Spring事务失效的常见场景与解决方案详解(3)

点一下关注吧!!!非常感谢!!持续更新!!!

🚀 AI篇持续更新中!(长期更新)

AI炼丹日志-31- 千呼万唤始出来 GPT-5 发布!"快的模型 + 深度思考模型 + 实时路由",持续打造实用AI工具指南!📐🤖

💻 Java篇正式开启!(300篇)

目前2025年09月29日更新到:
Java-136 深入浅出 MySQL Spring Boot @Transactional 使用指南:事务传播、隔离级别与异常回滚策略

MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务正在更新!深入浅出助你打牢基础!

📊 大数据板块已完成多项干货更新(300篇):

包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈!
大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解

事务失效场景(3)

参考来源

https://heapdump.cn/article/5542790

续接上节

rollbackFor 配置错误

在 Spring 事务管理中,@Transactional 注解的 rollbackFor 属性用于指定哪些异常类型会触发事务回滚。以下是更详细的说明:

  1. 默认回滚规则

    • 默认情况下,Spring 事务会在遇到 RuntimeException 或其子类以及 Error 及其子类时自动回滚。
    • 例如:NullPointerExceptionRuntimeException 子类)或 OutOfMemoryErrorError 子类)都会触发自动回滚。
  2. rollbackFor 的明确指定

    • 当显式指定 rollbackFor=Error.class 时,只有 Error 及其子类异常会触发回滚。
    • 其他异常类型(包括 Exception)不会触发回滚,即使它们是未检查异常。
  3. 异常继承关系的影响

    • ErrorException 都是 Throwable 的子类,但彼此没有继承关系。
    • 如果抛出的异常是 Exception 或其子类(如 IOException),但 rollbackFor 仅指定了 Error.class,则事务不会回滚。
  4. 典型问题示例

java 复制代码
   @Transactional(rollbackFor = Error.class)
   public void updateData() throws Exception {
       // 业务逻辑
       throw new Exception("自定义异常"); // 不会触发回滚
   }
  • 这里抛出的是 Exception,而 rollbackFor 仅覆盖 Error,因此事务不会回滚。
  1. 解决方案
    • 如果需要回滚 Exception,应明确指定:
java 复制代码
     @Transactional(rollbackFor = {Error.class, Exception.class})
     ```
   - 或者使用更通用的方式:
     ```java
     @Transactional(rollbackFor = Throwable.class) // 覆盖所有异常
  1. 实际应用场景
    • 在需要严格区分异常处理的场景中(如仅对系统级错误回滚),可以限定 rollbackFor=Error.class
    • 在大多数业务场景中,建议至少包含 RuntimeException 和自定义业务异常。

这里我们自定了几个异常类,BaseException 继承了 Exception,同时有两个子类:

● ParamException 参数异常

● ResultException 结果异常

此时,rollbackFor 只有 ResultException,这种情况下,会导致事务失效不会回滚:

java 复制代码
@Service
@RequiredArgsConstructor
public class TestServiceImpl implements TestService {

    private final UnitInfoMapper unitInfoMapper;

    @Transactional(rollbackFor = ResultException.class)
    @Override
    public String test01() throws Exception {
        UnitInfo unitInfo = UnitInfo
                .builder()
                .unitName("wzk")
                .unitCode("icu")
                .build();
        unitInfoMapper.insertUnitInfo(unitInfo);
        if (1 == 1) {
            throw new ParamException("ERROR");
        }
        return "ok";
    }
}

执行完后,数据库的情况:

事务注解被覆盖导致事务失效

事务失效问题分析:子类覆盖父类事务的情况

问题描述

在Spring框架的事务管理中,当子类重写父类带有事务注解(@Transactional)的方法时,可能会出现事务失效的情况。这是因为Spring的事务管理是基于AOP代理实现的,而子类方法的覆盖行为可能导致事务注解未被正确识别和应用。

典型场景示例

java 复制代码
@Service
public class ParentService {
    @Transactional
    public void parentMethod() {
        // 业务逻辑
    }
}

@Service
public class ChildService extends ParentService {
    @Override
    public void parentMethod() {
        // 重写父类方法,但未添加@Transactional注解
        super.parentMethod();
    }
}

失效原因分析

  1. 代理机制问题:Spring默认使用JDK动态代理或CGLIB代理来实现事务管理
  2. 注解继承:@Transactional注解默认不会被子类继承
  3. 调用方式:如果子类通过this调用方法,会绕过代理对象

解决方案

  1. 显式添加事务注解
java 复制代码
@Override
@Transactional  // 显式添加事务注解
public void parentMethod() {
    super.parentMethod();
}
  1. 使用接口定义事务方法
java 复制代码
public interface TransactionalService {
    @Transactional
    void transactionalMethod();
}

@Service
public class ActualService implements TransactionalService {
    @Override
    public void transactionalMethod() {
        // 实现逻辑
    }
}
  1. 配置代理模式
java 复制代码
@EnableTransactionManagement(proxyTargetClass = true)  // 强制使用CGLIB代理
  1. 避免内部调用
java 复制代码
@Service
public class SomeService {
    public void outerMethod() {
        innerMethod();  // 错误:内部调用会绕过代理
        this.innerMethod();  // 同样错误
    }
    
    @Transactional
    public void innerMethod() {
        // 业务逻辑
    }
}

最佳实践

  1. 将事务方法定义在接口中
  2. 避免在类内部调用事务方法
  3. 使用final修饰不打算被重写的事务方法
  4. 考虑使用AspectJ模式替代代理模式
  5. 在重写方法时显式声明事务属性

调试技巧

  1. 开启DEBUG日志查看事务创建和提交情况
  2. 检查实际调用的方法是否经过代理
  3. 使用TransactionSynchronizationManager.isActualTransactionActive()验证事务状态

比如,在 Service 上,我们定义了正常的类:

java 复制代码
public interface TestService {

    @Transactional
    String test01() throws Exception;
    
}

而在实现类上,我们重新定义了事务的情况:

java 复制代码
@Service
@RequiredArgsConstructor
public class TestServiceImpl implements TestService {

    private final UnitInfoMapper unitInfoMapper;

    @Transactional(rollbackFor = ResultException.class)
    @Override
    public String test01() throws Exception {
        UnitInfo unitInfo = UnitInfo
                .builder()
                .unitName("wzk")
                .unitCode("icu")
                .build();
        unitInfoMapper.insertUnitInfo(unitInfo);
        if (1 == 1) {
            throw new ParamException("ERROR");
        }
        return "ok";
    }
}

此时执行后,没有回滚,数据库写入了数据:

嵌套事务

事务失效问题:嵌套事务场景分析

嵌套事务的基本原理

嵌套事务是指在一个事务方法(A)中调用另一个事务方法(B)时发生的事务交互。在Spring等框架中,默认的事务传播行为是REQUIRED,即:

  • 如果当前存在事务,就加入该事务
  • 如果当前没有事务,就新建一个事务

典型失效场景

当方法A调用方法B时:

  1. 方法A开启事务(事务A)
  2. 方法B被调用,由于传播行为是REQUIRED,方法B会加入事务A
  3. 方法B执行过程中发生异常并回滚
  4. 由于方法B是加入方法A的事务,所以整个事务A都会被回滚

具体示例

java 复制代码
@Service
public class OrderService {
    @Transactional
    public void processOrder(Order order) { // 方法A
        // 订单处理逻辑
        inventoryService.reduceStock(order); // 调用方法B
        // 其他订单处理逻辑
    }
}

@Service
public class InventoryService {
    @Transactional
    public void reduceStock(Order order) { // 方法B
        // 库存扣减逻辑
        if (stock < order.getQuantity()) {
            throw new RuntimeException("库存不足"); // 触发回滚
        }
        // ...
    }
}

解决方案

  1. 修改传播行为 :将方法B的事务传播行为改为REQUIRES_NEW,这样方法B会开启新事务
java 复制代码
   @Transactional(propagation = Propagation.REQUIRES_NEW)
   public void reduceStock(Order order) { ... }
  1. 捕获内部异常:在方法A中捕获方法B的异常,避免异常传播到方法A
java 复制代码
   try {
       inventoryService.reduceStock(order);
   } catch (Exception e) {
       // 处理异常但不抛出
   }
  1. 使用编程式事务:在需要更细粒度控制时使用编程式事务管理

实际影响

这种嵌套事务问题会导致:

  • 主业务逻辑被意外回滚
  • 数据不一致性
  • 难以追踪的问题根源
  • 业务逻辑与预期不符
    比如如下情况,会导致两条数据都没有插入,回滚掉了:
java 复制代码
@Service
@RequiredArgsConstructor
public class TestServiceImpl implements TestService {

    private final UnitInfoMapper unitInfoMapper;

    @Transactional
    @Override
    public String test01() throws Exception {
        UnitInfo unitInfo = UnitInfo
                .builder()
                .unitName("wzk")
                .unitCode("icu")
                .build();
        unitInfoMapper.insertUnitInfo(unitInfo);
        test02();
        return "ok";
    }

    @Override
    public String test02() {
        UnitInfo unitInfo = UnitInfo
                .builder()
                .unitName("wzk")
                .unitCode("icu")
                .build();
        unitInfoMapper.insertUnitInfo(unitInfo);
        if (1 == 1) {
            throw new RuntimeException("ERROR");
        }
        return "ok";
    }
}

下面这种写法结果也是一样的,都回滚掉了:

java 复制代码
@Service
@RequiredArgsConstructor
public class TestServiceImpl implements TestService {

    private final UnitInfoMapper unitInfoMapper;

    @Transactional
    @Override
    public String test01() throws Exception {
        UnitInfo unitInfo = UnitInfo
                .builder()
                .unitName("wzk")
                .unitCode("icu")
                .build();
        unitInfoMapper.insertUnitInfo(unitInfo);
        test02();
        if (1 == 1) {
            throw new RuntimeException("ERROR");
        }
        return "ok";
    }


    @Override
    public String test02() {
        UnitInfo unitInfo = UnitInfo
                .builder()
                .unitName("wzk")
                .unitCode("icu")
                .build();
        unitInfoMapper.insertUnitInfo(unitInfo);
        return "ok";
    }
}
相关推荐
间彧2 小时前
脏读、不可重复读、幻读详解与对比
数据库
珹洺3 小时前
Java-Spring入门指南(十五)SpringMVC注解开发
java·spring·microsoft
小满、3 小时前
什么是Maven?关于 Maven 的坐标、依赖管理与 Web 项目构建
java·maven
间彧3 小时前
数据库事务隔离级别详解
数据库
虫师c3 小时前
分布式系统设计模式:从理论到实践
微服务·设计模式·系统架构·高可用·分布式系统
半旧夜夏3 小时前
【设计模式】核心设计模式实战
java·spring boot·设计模式
fwerfv3453453 小时前
使用PyTorch构建你的第一个神经网络
jvm·数据库·python
半旧夜夏4 小时前
【Spring】AOP的核心原理配方
java·spring