springboot-@Transactional
springboot @Transactional
在 Spring Boot 中,
@Transactional是声明式事务管理的核心注解,用于简化事务控制逻辑。它基于 AOP(面向切面编程)实现,通过注解声明方法或类需要在事务中运行,从而保证数据操作的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)(即 ACID 特性)。一、基本作用
@Transactional的核心作用是:自动为标注的方法或类添加事务管理逻辑(如事务开启、提交、回滚等),无需手动编写try-catch或TransactionStatus相关代码。二、使用位置
- 方法上:仅对当前方法生效。
- 类上:对类中所有public 方法生效(方法上的注解会覆盖类上的配置)。
三、核心属性
@Transactional提供了多个属性用于定制事务行为,常用属性如下:| 属性名 | 作用 | 可选值(常用) | 
|---|---|---|
| propagation | 事务传播行为(定义方法间事务的嵌套规则) | REQUIRED(默认)、REQUIRES_NEW、SUPPORTS、MANDATORY、NEVER等 | 
| isolation | 事务隔离级别(解决并发问题:脏读、不可重复读、幻读) | DEFAULT(默认,使用数据库隔离级别)、READ_COMMITTED、REPEATABLE_READ等 | 
| readOnly | 是否为只读事务(查询操作建议设为 true,优化性能) | true/false(默认false) | 
| rollbackFor | 指定哪些异常触发事务回滚(默认仅对 RuntimeException和Error回滚) | 异常类数组(如 {Exception.class}) | 
| noRollbackFor | 指定哪些异常不触发事务回滚 | 异常类数组(如 {BusinessException.class}) | 
| timeout | 事务超时时间(超过时间未完成则回滚,单位:秒) | 整数(默认 -1,表示不超时) | 
1. 事务传播行为(propagation)
定义多个事务方法嵌套调用时,事务的创建 / 关联规则(最常用的两个):
- REQUIRED(默认):如果当前存在事务,则加入该事务;如果没有,则新建一个事务。例:ServiceA.methodA(带事务)调用 ServiceB.methodB(- REQUIRED),methodB 会加入 methodA 的事务,若 methodB 失败,整个事务回滚。
- REQUIRES_NEW:无论当前是否有事务,都新建一个事务;原事务会被挂起,直到新事务完成。例:methodA(事务)调用 methodB(- REQUIRES_NEW),methodB 的事务独立,即使 methodB 失败,仅回滚自身,不影响 methodA 的事务。
2. 事务隔离级别(isolation)
解决并发场景下的事务冲突问题(依赖数据库支持):
- DEFAULT:使用数据库默认隔离级别(MySQL 默认- REPEATABLE_READ,Oracle 默认- READ_COMMITTED)。
- READ_COMMITTED:只能读取已提交的数据,避免脏读(读取未提交的数据)。
- REPEATABLE_READ:保证多次读取同一数据结果一致,避免不可重复读(两次读取数据不一致)。
- SERIALIZABLE:最高隔离级别,完全串行化执行,避免幻读(读取到新增的数据),但性能最差。
四、使用示例
java
运行
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    // 声明事务:传播行为REQUIRED,对所有Exception回滚,超时30秒
    @Transactional(propagation = Propagation.REQUIRED, 
                   rollbackFor = Exception.class, 
                   timeout = 30)
    public void transferMoney(Long fromId, Long toId, BigDecimal amount) throws Exception {
        // 扣钱
        userMapper.decreaseBalance(fromId, amount);
        // 模拟异常(如网络波动)
        if (amount.compareTo(new BigDecimal("10000")) > 0) {
            throw new Exception("转账金额过大"); // 触发回滚
        }
        // 加钱
        userMapper.increaseBalance(toId, amount);
    }
}
五、注意事项(避坑点)
- 
仅对 public 方法生效:Spring AOP 默认只代理public方法,非public方法(如private、protected)的@Transactional会失效。
- 
自调用事务失效:同一类中,无事务的方法 A 调用有事务的方法 B 时,B 的事务会失效(因未经过 Spring 代理对象)。解决:注入自身代理对象(@Autowired private UserService self;),通过self.methodB()调用。
- 
异常被捕获会导致回滚失效:若方法内部捕获异常且未重新抛出(如try-catch后不throw),Spring 无法感知异常,事务不会回滚。解决:捕获后需重新抛出异常(如throw new RuntimeException(e))。
- 
数据库引擎支持:事务依赖数据库支持,例如 MySQL 的MyISAM引擎不支持事务,需使用InnoDB。
- 
rollbackFor 默认行为:默认仅对RuntimeException(非检查异常)和Error回滚,对Exception(检查异常,如IOException)不回滚。若需对检查异常回滚,需显式指定rollbackFor = Exception.class。
总结
@Transactional是 Spring Boot 中简化事务管理的核心工具,通过合理配置传播行为、隔离级别等属性,并规避使用中的常见问题,可有效保证数据操作的一致性。@Transactional 事务注入案例
下面通过一个订单创建与库存扣减的业务场景,展示
@Transactional事务注入(即事务在多个组件 / 方法间的传播与协同)的具体案例。该案例将包含 Service 间调用、事务传播行为的实际效果,以及常见问题的对比。场景说明
业务流程:用户创建订单时,需要完成两个核心操作 ——创建订单记录和扣减商品库存。这两个操作必须在同一个事务中:要么都成功,要么都失败(例如库存不足时,订单需回滚)。额外需求:无论订单创建成功与否,都需要记录操作日志(日志记录需独立事务,不受主事务影响)。
技术依赖
- Spring Boot 2.7.x
- MyBatis(数据访问,也可替换为 JPA)
- MySQL(InnoDB 引擎,支持事务)
代码实现
1. 实体类
java
运行
// 订单实体
@Data
public class Order {
    private Long id;
    private Long userId;
    private Long productId;
    private Integer quantity; // 购买数量
    private LocalDateTime createTime;
}
// 商品实体(库存)
@Data
public class Product {
    private Long id;
    private String name;
    private Integer stock; // 库存数量
}
// 操作日志实体
@Data
public class OperateLog {
    private Long id;
    private String content; // 日志内容
    private LocalDateTime createTime;
}
2. 数据访问层(Mapper)
java
运行
// 订单Mapper
@Mapper
public interface OrderMapper {
    // 插入订单
    @Insert("INSERT INTO `order`(user_id, product_id, quantity, create_time) " +
            "VALUES(#{userId}, #{productId}, #{quantity}, #{createTime})")
    void insert(Order order);
}
// 商品Mapper
@Mapper
public interface ProductMapper {
    // 扣减库存(where条件确保库存充足时才扣减)
    @Update("UPDATE product SET stock = stock - #{quantity} " +
            "WHERE id = #{productId} AND stock >= #{quantity}")
    int decreaseStock(@Param("productId") Long productId, @Param("quantity") Integer quantity);
    // 查询商品
    @Select("SELECT * FROM product WHERE id = #{id}")
    Product selectById(Long id);
}
// 日志Mapper
@Mapper
public interface OperateLogMapper {
    @Insert("INSERT INTO operate_log(content, create_time) VALUES(#{content}, #{createTime})")
    void insert(OperateLog log);
}
3. Service 层(核心事务逻辑)
3.1 库存服务(ProductService)
负责扣减库存,声明事务(默认传播行为
REQUIRED,加入调用者的事务)。java
运行
@Service
public class ProductService {
    @Autowired
    private ProductMapper productMapper;
    /**
     * 扣减库存(事务传播行为:REQUIRED,默认)
     * 若库存不足,抛出异常触发回滚
     */
    @Transactional
    public void decreaseStock(Long productId, Integer quantity) {
        // 扣减库存(返回影响行数,0表示库存不足)
        int rows = productMapper.decreaseStock(productId, quantity);
        if (rows == 0) {
            // 库存不足,抛出异常(会被上层事务捕获并触发回滚)
            throw new RuntimeException("商品库存不足");
        }
    }
}
3.2 日志服务(LogService)
负责记录日志,使用
REQUIRES_NEW传播行为,确保日志事务独立(不受主事务影响)。java
运行
@Service
public class LogService {
    @Autowired
    private OperateLogMapper logMapper;
    /**
     * 记录日志(事务传播行为:REQUIRES_NEW,独立事务)
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void recordLog(String content) {
        OperateLog log = new OperateLog();
        log.setContent(content);
        log.setCreateTime(LocalDateTime.now());
        logMapper.insert(log);
    }
}
3.3 订单服务(OrderService)
核心业务逻辑:创建订单 + 调用库存服务扣减库存 + 调用日志服务记录日志。声明事务(
REQUIRED),确保订单创建和库存扣减在同一事务中。java
运行
@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private ProductService productService; // 注入库存服务(事务协同)
    @Autowired
    private LogService logService; // 注入日志服务(独立事务)
    /**
     * 创建订单(主事务:REQUIRED)
     * 流程:扣减库存 -> 创建订单 -> 记录日志(成功/失败)
     */
    @Transactional(rollbackFor = Exception.class) // 对所有异常回滚
    public void createOrder(Long userId, Long productId, Integer quantity) {
        String logContent;
        try {
            // 1. 扣减库存(调用ProductService,加入当前事务)
            productService.decreaseStock(productId, quantity);
            // 2. 创建订单(与扣减库存同事务)
            Order order = new Order();
            order.setUserId(userId);
            order.setProductId(productId);
            order.setQuantity(quantity);
            order.setCreateTime(LocalDateTime.now());
            orderMapper.insert(order);
            logContent = "订单创建成功:用户" + userId + "购买商品" + productId + ",数量" + quantity;
        } catch (Exception e) {
            // 3. 若异常(如库存不足),记录失败日志
            logContent = "订单创建失败:" + e.getMessage();
            throw e; // 关键:必须重新抛出异常,否则主事务不会回滚!
        } finally {
            // 4. 记录日志(调用LogService,独立事务,无论成败都会执行)
            logService.recordLog(logContent);
        }
    }
}
4. Controller 层(接口调用)
java
运行
@RestController
@RequestMapping("/orders")
public class OrderController {
    @Autowired
    private OrderService orderService;
    @PostMapping
    public String createOrder(
            @RequestParam Long userId,
            @RequestParam Long productId,
            @RequestParam Integer quantity) {
        try {
            orderService.createOrder(userId, productId, quantity);
            return "订单创建成功";
        } catch (Exception e) {
            return "订单创建失败:" + e.getMessage();
        }
    }
}
事务注入效果测试
测试 1:正常流程(库存充足)
- 操作:调用接口,购买库存充足的商品(如商品 A 库存 100,购买 10)。
- 结果:
- 库存扣减成功(100→90)。
- 订单表新增一条记录。
- 日志表新增一条 “成功” 日志。
- 所有操作在事务中完成,数据一致。
 
测试 2:异常流程(库存不足)
- 操作:调用接口,购买库存不足的商品(如商品 A 库存 5,购买 10)。
- 结果:
- ProductService.decreaseStock扣减库存失败,抛出- RuntimeException。
- OrderService.createOrder捕获异常后重新抛出,触发主事务回滚:订单记录不会插入数据库。
- LogService.recordLog因- REQUIRES_NEW,独立事务提交:日志表新增一条 “失败” 日志。
- 最终状态:库存不变,无订单,有失败日志(符合业务预期)。
 
测试 3:验证事务传播行为
- 若LogService.recordLog使用默认REQUIRED传播行为:当主事务回滚时,日志的插入也会被回滚(不符合 “必须记录日志” 的需求)。这就是REQUIRES_NEW的作用 —— 强制新建事务,确保日志独立。
关键注意点
- 事务注入的核心是传播行为:多个 Service 方法调用时,propagation属性决定事务是否共享、新建或挂起(如案例中REQUIRED和REQUIRES_NEW的区别)。
- 异常必须抛出:OrderService中catch异常后必须throw e,否则 Spring 无法感知异常,主事务不会回滚(这是最常见的坑)。
- 避免自调用失效:若OrderService中一个无事务的方法 A 调用自身有事务的方法 B,B 的事务会失效(需通过@Autowired注入自身代理对象调用)。
- 数据库支持:确保使用 InnoDB 引擎(MyISAM 不支持事务)。
通过这个案例可以清晰看到:
@Transactional通过 “注入”(即不同 Service 间的调用)实现了事务的协同管理,既保证了核心业务的原子性,又通过传播行为满足了特殊场景(如独立日志)的需求。 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号