Spring 事务失效的场景及修复:从原理到实战

在Java​企业级开发中,Spring​事务是保障数据一致性的核心机制。但实际项目中,开发者常遇到@Transactional​注解加了却不生效的问题,这本质是对Spring事务实现原理理解不透彻或忽略关键细节导致的。

引言

在Java企业级开发中,Spring事务是保障数据一致性的核心机制。但实际项目中,开发者常遇到@Transactional注解加了却不生效的问题,这本质是对Spring事务实现原理理解不透彻或忽略关键细节导致的。

Spring 事务基础

在分析失效场景前,必须先明确 Spring 事务的核心原理 ——基于 AOP 动态代理实现,这是理解所有失效场景的关键。

事务的核心特性(ACID)
  • 原子性(Atomicity):事务是不可分割的最小单元,要么全成功,要么全回滚;
  • 一致性(Consistency):事务执行前后,数据从一个合法状态转换到另一个合法状态;
  • 隔离性(Isolation):多个事务并发执行时,相互不干扰(由隔离级别控制,如READ_COMMITTED);
  • 持久性(Durability):事务提交后,数据修改永久保存在数据库中。
Spring 事务的实现原理

Spring事务通过动态代理为目标Bean生成代理对象,当调用被@Transactional标注的方法时,代理对象会先拦截方法执行:

  • 开启事务(创建数据库连接,设置事务隔离级别、传播行为等);
  • 调用目标方法(业务逻辑执行);
  • 若方法正常返回,提交事务;
  • 若方法抛出指定异常,回滚事务;
  • 若抛出未指定异常,不回滚(默认仅回滚RuntimeException和Error)。

关键结论:只有通过 Spring 容器管理的代理对象调用事务方法,事务才会生效;若绕开代理直接调用(如自调用),事务机制无法触发。

Spring 事务失效的场景及说明

场景 1:非 public 修饰的方法加 @Transactional
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    // 错误:private方法,@Transactional无效
    @Transactional
    private void createOrder(Order order) {
        orderMapper.insert(order);
        // 模拟异常
        if (order.getAmount() < 0) {
            throw new RuntimeException("订单金额非法");
        }
    }
}

// 外部调用private方法
public void submitOrder(Order order) {
    createOrder(order); // 直接调用,无代理拦截
}

Spring事务默认通过AOP动态代理实现,而Spring AOP(无论是JDK动态代理还是CGLIB代理)对方法权限有明确限制:仅拦截public修饰的方法。

场景 2:事务方法内部自调用
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private LogMapper logMapper;

    // 事务方法A
    @Transactional
    public void createOrder(Order order) {
        orderMapper.insert(order);
        // 错误:内部直接调用事务方法B,无代理拦截
        this.addOrderLog(order.getId()); 
    }

    // 事务方法B(期望单独事务,但实际失效)
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addOrderLog(Long orderId) {
        Log log = new Log();
        log.setOrderId(orderId);
        log.setContent("订单创建成功");
        logMapper.insert(log);
        // 模拟异常:此时addOrderLog的事务不回滚,log仍会插入
        throw new RuntimeException("日志记录异常");
    }
}

Spring事务的触发依赖代理对象调用,而当一个事务方法(如methodA)内部直接调用另一个事务方法(如methodB)时,调用过程是目标对象→目标对象,而非代理对象→目标对象,绕开了AOP代理的拦截逻辑,导致methodB的事务不生效。

场景 3:异常被捕获(try-catch)且未重新抛出
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Transactional
    public void createOrder(Order order) {
        try {
            orderMapper.insert(order);
            // 模拟异常
            if (order.getAmount() > 10000) {
                throw new RuntimeException("订单金额超过上限");
            }
        } catch (Exception e) {
            // 错误:仅打印日志,未重新抛出异常
            log.error("创建订单失败", e);
        }
    }
}

Spring事务默认仅在方法抛出未被捕获的RuntimeException或Error时才会触发回滚。若开发者在事务方法中用try-catch捕获了异常,且未在catch块中重新抛出异常,Spring会认为方法执行成功,直接提交事务,导致异常发生时无法回滚。

场景 4:错误的事务传播机制
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    // 错误:使用NOT_SUPPORTED,不支持事务
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void createOrder(Order order) {
        orderMapper.insert(order);
        throw new RuntimeException("模拟异常"); // 异常抛出,但事务未开启,无法回滚
    }
}

Spring事务传播机制定义了多个事务方法嵌套调用时,事务如何传递,若选择了不支持事务或强制不使用事务的传播行为,会导致事务失效。常见错误传播行为:

  • Propagation.NOT_SUPPORTED:以非事务方式执行,若当前存在事务则暂停;
  • Propagation.NEVER:以非事务方式执行,若当前存在事务则抛出异常;
  • Propagation.SUPPORTS:若当前存在事务则加入,否则以非事务方式执行(非主动开启事务)。
场景 5:数据源未配置事务管理器
// 错误:仅配置数据源,未配置事务管理器
@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/order_db");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

Spring事务的执行依赖事务管理器(TransactionManager),不同的数据源对应不同的事务管理器(如JDBC对应DataSourceTransactionManager,MyBatis对应SqlSessionTransactionManager)。若未在Spring容器中配置事务管理器,@Transactional注解会被忽略,事务无法生效。

Spring Boot中引入spring-boot-starter-jdbc或spring-boot-starter-data-jpa,会自动配置DataSourceTransactionManager,无需手动配置;但自定义数据源时,需手动绑定事务管理器。

场景 6:多线程调用事务方法
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private LogMapper logMapper;

    @Transactional
    public void createOrder(Order order) {
        // 主线程:插入订单
        orderMapper.insert(order);

        // 错误:子线程调用事务方法,与主线程事务独立
        new Thread(() -> {
            addOrderLog(order.getId()); // 子线程事务不生效(或与主线程独立)
        }).start();

        // 模拟主线程异常:主线程回滚(订单不插入),但子线程日志已插入
        throw new RuntimeException("主线程异常");
    }

    @Transactional
    public void addOrderLog(Long orderId) {
        Log log = new Log();
        log.setOrderId(orderId);
        logMapper.insert(log);
    }
}

AI大模型学习福利

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。


因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

四、AI大模型商业化落地方案

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值