Spring Boot事务使用方式汇总:从注解到编程式,附实战踩坑与避坑指南

5 阅读11分钟

做了五年Java后端开发,我敢说事务是Spring Boot项目里最容易出问题的模块之一——要么是事务不生效,要么是回滚失败,最头疼的是线上出了问题才发现,数据都不一致了。我之前在做电商订单模块的时候,就踩过“同一个类里方法调用导致事务不生效”的坑,查了半天才搞懂Spring AOP的代理机制,差点影响了订单数据的一致性。

今天就结合我这些年的实战经验,把Spring Boot中使用事务的方式做个全面汇总,从最常用的@Transactional注解,到灵活的编程式事务,再到事务不生效的常见原因和优化方案,每一点都有实际代码和踩坑经历,没有空洞的理论,只有可直接复用的经验。

一、最常用:@Transactional注解式事务 @Transactional是Spring Boot中使用事务的主流方式,简单方便,只需在方法或类上加个注解,Spring就会自动帮我们管理事务的开启、提交和回滚。但这个注解看似简单,里面的坑却最多,先从它的核心属性讲起。

  1. 基本使用 在需要事务的方法或类上添加@Transactional注解即可:

     @Service
     public class OrderService {
    
         @Autowired
         private OrderMapper orderMapper;
         @Autowired
         private ProductMapper productMapper;
    
         /**
          * 创建订单:扣减库存 + 插入订单
          */
         @Transactional(rollbackFor = Exception.class)
         public void createOrder(Order order) {
             // 1. 扣减商品库存
             productMapper.decreaseStock(order.getProductId(), order.getQuantity());
             // 2. 插入订单
             orderMapper.insert(order);
             // 模拟异常,测试回滚
             if (order.getQuantity() > 100) {
                 throw new RuntimeException("库存不足");
             }
         }
     }
    

如果在类上加@Transactional,类里所有public方法都会开启事务; 如果在方法上加,只对当前方法生效,方法级的优先级高于类级。 2. 核心属性详解(实战必看) @Transactional的属性很多,我只讲几个实战中最常用、最容易出问题的:

(1)rollbackFor:指定回滚异常 这是最容易踩坑的属性!默认情况下,Spring只对RuntimeException和Error回滚,对Exception(比如IOException、SQLException)不回滚。我之前就遇到过:

    // 错误写法:抛出IOException,事务不会回滚
    @Transactional
    public void createOrder(Order order) throws IOException {
        productMapper.decreaseStock(order.getProductId(), order.getQuantity());
        orderMapper.insert(order);
        throw new IOException("文件操作异常"); // 事务不回滚!
    }

解决方法:显式指定rollbackFor = Exception.class,让所有异常都回滚:

    // 正确写法
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Order order) throws IOException {
        // ...
    }

(2)propagation:事务传播行为 传播行为决定了“当一个事务方法被另一个事务方法调用时,事务该如何处理”,常用的有以下几种:

传播行为 含义 适用场景 Propagation.REQUIRED(默认) 如果当前有事务,就加入;如果没有,就新建一个事务 大多数场景,比如订单创建、库存扣减 Propagation.REQUIRES_NEW 不管当前有没有事务,都新建一个事务,挂起当前事务 需要独立事务的场景,比如记录操作日志(不管主事务是否成功,日志都要记录) Propagation.NESTED 如果当前有事务,就嵌套一个子事务;如果没有,就新建一个 嵌套事务场景,比如订单创建失败,只回滚订单,不回滚之前的库存预扣(需要数据库支持保存点) Propagation.SUPPORTS 如果当前有事务,就加入;如果没有,就以非事务方式运行 查询方法,可加可不加 Propagation.NOT_SUPPORTED 以非事务方式运行,挂起当前事务 不需要事务的耗时操作,比如文件上传 举个REQUIRES_NEW的例子:

    @Service
    public class OrderService {

        @Autowired
        private OrderMapper orderMapper;
        @Autowired
        private LogService logService;

        @Transactional(rollbackFor = Exception.class)
        public void createOrder(Order order) {
            // 1. 扣减库存、插入订单(主事务)
            productMapper.decreaseStock(order.getProductId(), order.getQuantity());
            orderMapper.insert(order);

            // 2. 记录操作日志(独立事务,不管主事务是否成功,日志都要记录)
            logService.insertLog("创建订单:" + order.getId());

            // 3. 模拟主事务失败
            throw new RuntimeException("主事务失败");
        }
    }

    @Service
    public class LogService {

        @Autowired
        private LogMapper logMapper;

        // REQUIRES_NEW:新建独立事务
        @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
        public void insertLog(String content) {
            logMapper.insert(content);
        }
    }

这个例子中,主事务失败回滚,但日志的独立事务已经提交,日志会正常记录,符合业务需求。

(3)isolation:事务隔离级别 隔离级别决定了“多个事务并发执行时,数据的一致性程度”,Spring默认使用数据库的默认隔离级别(MySQL默认是REPEATABLE_READ),常用的隔离级别:

    隔离级别	含义	解决的问题
    Isolation.READ_UNCOMMITTED	可以读取未提交的数据	无,会出现脏读
    Isolation.READ_COMMITTED	只能读取已提交的数据	解决脏读,会出现不可重复读
    Isolation.REPEATABLE_READ(MySQL默认)	同一个事务内多次读取同一数据,结果一致	解决脏读、不可重复读,会出现幻读
    Isolation.SERIALIZABLE	串行化执行事务,最高隔离级别	解决所有问题,但性能最差
    一般情况下,用数据库的默认隔离级别即可,不需要手动修改。

(4)readOnly:只读事务 如果方法只有查询操作,没有增删改,可以设置readOnly = true,优化数据库性能:

    @Transactional(readOnly = true)
    public Order getOrderById(Long id) {
        return orderMapper.selectById(id);
    }

只读事务会告诉数据库“这个事务不会修改数据”,数据库可以做一些优化,比如不加锁,提升查询速度。

(5)timeout:事务超时时间 设置事务的最大执行时间,超过时间自动回滚,避免事务长时间占用数据库连接:

    // 超时时间30秒,单位秒
    @Transactional(timeout = 30, rollbackFor = Exception.class)
    public void createOrder(Order order) {
        // ...
    }

二、灵活可控:编程式事务 注解式事务虽然方便,但在某些复杂场景下不够灵活,比如需要手动控制事务的开启和提交、根据业务逻辑动态决定是否回滚,这时候就需要用编程式事务。Spring提供了两种编程式事务的方式:TransactionTemplate和PlatformTransactionManager。

  1. TransactionTemplate(推荐) TransactionTemplate是Spring封装好的编程式事务工具类,使用起来比较简洁:

     @Service
     public class OrderService {
    
         @Autowired
         private OrderMapper orderMapper;
         @Autowired
         private ProductMapper productMapper;
         @Autowired
         private TransactionTemplate transactionTemplate;
    
         public void createOrder(Order order) {
             // execute方法里的代码会在事务中执行
             transactionTemplate.execute(status -> {
                 try {
                     // 1. 扣减库存
                     productMapper.decreaseStock(order.getProductId(), order.getQuantity());
                     // 2. 插入订单
                     orderMapper.insert(order);
                     // 3. 模拟异常,手动回滚
                     if (order.getQuantity() > 100) {
                         throw new RuntimeException("库存不足");
                     }
                     return null; // 正常返回
                 } catch (Exception e) {
                     // 手动回滚事务
                     status.setRollbackOnly();
                     throw e; // 重新抛出异常
                 }
             });
         }
     }
    

execute方法的参数是TransactionCallback,里面的代码在事务中执行; 如果需要回滚,调用status.setRollbackOnly()即可。 2. PlatformTransactionManager(底层方式) PlatformTransactionManager是Spring事务的底层接口,需要手动管理事务的开启、提交和回滚,代码比较繁琐,但最灵活:

    @Service
    public class OrderService {

        @Autowired
        private OrderMapper orderMapper;
        @Autowired
        private ProductMapper productMapper;
        @Autowired
        private PlatformTransactionManager transactionManager;

        public void createOrder(Order order) {
            // 1. 定义事务属性
            DefaultTransactionDefinition def = new DefaultTransactionDefinition();
            def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            def.setTimeout(30);

            // 2. 开启事务
            TransactionStatus status = transactionManager.getTransaction(def);

            try {
                // 3. 执行业务逻辑
                productMapper.decreaseStock(order.getProductId(), order.getQuantity());
                orderMapper.insert(order);

                if (order.getQuantity() > 100) {
                    throw new RuntimeException("库存不足");
                }

                // 4. 提交事务
                transactionManager.commit(status);
            } catch (Exception e) {
                // 5. 回滚事务
                transactionManager.rollback(status);
                throw e;
            }
        }
    }

3. 注解式 vs 编程式:怎么选? 对比项 注解式@Transactional 编程式TransactionTemplate 代码复杂度 简单,只需加注解 稍复杂,需要写模板代码 灵活性 一般,适合固定场景 高,适合复杂业务逻辑 可读性 好,事务逻辑一目了然 稍差,事务逻辑和业务逻辑混在一起 适用场景 大多数常规业务场景 需要手动控制事务边界、动态回滚的复杂场景 建议:优先用注解式,简单方便;只有在注解式搞不定的复杂场景下,再用编程式。

三、最容易踩坑:事务不生效的常见原因 事务不生效是Spring Boot项目里最常见的问题,我整理了5个最容易踩的坑,每个都有错误代码和正确代码,帮你快速排查。

  1. 方法不是public @Transactional只能作用于public方法,非public方法(private、protected、default)事务不会生效:

     @Service
     public class OrderService {
    
         // 错误写法:private方法,事务不生效
         @Transactional(rollbackFor = Exception.class)
         private void createOrder(Order order) {
             // ...
         }
     }
    

解决方法:把方法改成public。

  1. 同一个类中方法调用 这是我踩过的最坑的一个!Spring的@Transactional是基于AOP代理实现的,只有通过代理对象调用方法,事务才会生效;如果是同一个类里的方法直接调用(比如this.createOrder()),用的是原对象,不是代理对象,事务不会生效:

     @Service
     public class OrderService {
    
         @Autowired
         private OrderMapper orderMapper;
    
         public void processOrder(Order order) {
             // 错误写法:同一个类里直接调用this.createOrder(),事务不生效
             this.createOrder(order);
         }
    
         @Transactional(rollbackFor = Exception.class)
         public void createOrder(Order order) {
             orderMapper.insert(order);
             throw new RuntimeException("异常");
         }
     }
    

解决方法:

方法1:把createOrder()放到另一个类里,通过依赖注入调用; 方法2:在当前类里注入自己,通过注入的对象调用: @Service public class OrderService {

        @Autowired
        private OrderMapper orderMapper;
        // 注入自己
        @Autowired
        private OrderService orderService;

        public void processOrder(Order order) {
            // 正确写法:通过代理对象调用
            orderService.createOrder(order);
        }

        @Transactional(rollbackFor = Exception.class)
        public void createOrder(Order order) {
            // ...
        }
    }

方法3:用AopContext.currentProxy()获取代理对象:

    // 启动类上添加@EnableAspectJAutoProxy(exposeProxy = true)
    @EnableAspectJAutoProxy(exposeProxy = true)
    @SpringBootApplication
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }

// 调用时用代理对象 ((OrderService) AopContext.currentProxy()).createOrder(order);

  1. 异常被catch了,没有抛出 如果在方法里把异常catch了,没有重新抛出,Spring就不知道发生了异常,事务不会回滚:

     @Service
     public class OrderService {
    
         @Transactional(rollbackFor = Exception.class)
         public void createOrder(Order order) {
             try {
                 productMapper.decreaseStock(order.getProductId(), order.getQuantity());
                 orderMapper.insert(order);
                 throw new RuntimeException("异常");
             } catch (Exception e) {
                 // 错误写法:只打印日志,没有抛出异常,事务不回滚
                 e.printStackTrace();
             }
         }
     }
    

解决方法:catch后重新抛出异常,或者手动回滚:

    // 方法1:重新抛出异常
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Order order) {
        try {
            // ...
        } catch (Exception e) {
            e.printStackTrace();
            throw e; // 重新抛出
        }
    }

    // 方法2:手动回滚(编程式事务)
    @Autowired
    private TransactionTemplate transactionTemplate;

    public void createOrder(Order order) {
        transactionTemplate.execute(status -> {
            try {
                // ...
            } catch (Exception e) {
                status.setRollbackOnly(); // 手动回滚
                throw e;
            }
        });
    }

4. 数据库引擎不支持事务 MySQL的MyISAM引擎不支持事务,只有InnoDB引擎支持,如果你的表是MyISAM引擎,事务不会生效:

    -- 查看表的引擎
    SHOW CREATE TABLE `order`;

    -- 如果是MyISAM,改成InnoDB
    ALTER TABLE `order` ENGINE = InnoDB;

5. rollbackFor配置不对 前面讲过,默认只对RuntimeException和Error回滚,如果抛出的是Exception,要显式指定rollbackFor = Exception.class,这个坑我就不重复写代码了,大家一定要注意。

四、生产级优化:让事务更稳、更快

  1. 减少事务范围 事务范围越大,占用数据库连接的时间越长,并发性能越差。要尽量把不需要事务的操作(比如查询、文件上传、网络请求)放到事务外面:

    @Service public class OrderService {

     @Autowired
     private OrderMapper orderMapper;
     @Autowired
     private ProductMapper productMapper;
     @Autowired
     private FileService fileService;
    
     // 错误写法:文件上传在事务里,占用连接时间长
     @Transactional(rollbackFor = Exception.class)
     public void createOrder(Order order, MultipartFile file) {
         // 不需要事务的文件上传
         fileService.upload(file);
         // 需要事务的业务逻辑
         productMapper.decreaseStock(order.getProductId(), order.getQuantity());
         orderMapper.insert(order);
     }
    
     // 正确写法:文件上传放到事务外面
     public void createOrder(Order order, MultipartFile file) {
         // 1. 先做不需要事务的操作
         fileService.upload(file);
         // 2. 再做需要事务的操作
         doCreateOrder(order);
     }
    
     @Transactional(rollbackFor = Exception.class)
     public void doCreateOrder(Order order) {
         productMapper.decreaseStock(order.getProductId(), order.getQuantity());
         orderMapper.insert(order);
     }
    

    }

    1. 避免在事务中做耗时操作 事务中不要做网络请求、文件IO、远程调用等耗时操作,这些操作会占用数据库连接,导致连接池耗尽:

       // 错误写法:事务里有远程调用
       @Transactional(rollbackFor = Exception.class)
       public void createOrder(Order order) {
           productMapper.decreaseStock(order.getProductId(), order.getQuantity());
           orderMapper.insert(order);
           // 耗时的远程调用,占用数据库连接
           remoteService.notifyWarehouse(order);
       }
      
       // 正确写法:远程调用放到事务外面,或者用异步
       @Transactional(rollbackFor = Exception.class)
       public void createOrder(Order order) {
           productMapper.decreaseStock(order.getProductId(), order.getQuantity());
           orderMapper.insert(order);
       }
      
       // 事务提交后,异步调用远程服务
       @Async
       public void notifyWarehouse(Order order) {
           remoteService.notifyWarehouse(order);
       }
      
  2. 合理设置事务超时时间 根据业务逻辑的执行时间,合理设置timeout,避免事务长时间占用数据库连接,一般设置为30秒-60秒即可。

  3. 用只读事务优化查询 查询方法设置readOnly = true,让数据库做优化,提升查询速度。

五、总结 Spring Boot中使用事务,核心就是三点:

优先用注解式:@Transactional简单方便,注意配置rollbackFor = Exception.class和传播行为; 复杂场景用编程式:TransactionTemplate灵活可控,适合需要手动控制事务的场景; 避坑是关键:注意事务不生效的5个常见原因,尤其是同一个类中方法调用、异常被catch这两个坑; 优化不能少:减少事务范围、避免耗时操作、合理设置超时,提升事务性能。 事务是保证数据一致性的核心,一定要重视,不要等线上出了问题才来排查。希望这篇文章能帮你少走弯路,把事务用得更稳、更好。 ———————————————— 版权声明:本文为CSDN博主「Java程序员威哥」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/2601_948715…