@GlobalTransactional和@Transactional作用和使用场景

@Transactional 作用与使用场景(Spring 事务注解)

一、核心作用

@Transactional 是 Spring 提供的声明式事务 注解,底层基于 AOP 实现,不用手动写commit/rollback,自动管理数据库事务:

  1. 事务原子性:注解内所有数据库操作要么全部成功提交,要么全部回滚;
  2. 自动回滚 :默认遇到运行时异常 (RuntimeException)、错误 (Error) 自动回滚;普通 Checked 受检异常默认不回滚;
  3. 事务传播控制 :通过propagation控制嵌套事务如何运行;
  4. 隔离级别控制 :通过isolation解决脏读、不可重复读、幻读;
  5. 超时、只读、锁控制:配置超时时间、只读事务、悲观锁。

底层原理

Spring AOP 动态代理:调用带注解方法前开启事务,方法正常结束提交;抛出指定异常则回滚。

坑:同类内部方法自调用不生效(不会走代理),需要自己注入自己或拆到不同类。

二、常用属性详解

java 复制代码
@Transactional(
    propagation = Propagation.REQUIRED, // 传播机制(默认)
    isolation = Isolation.DEFAULT,     // 隔离级别,使用数据库默认
    rollbackFor = RuntimeException.class, // 指定什么异常回滚
    noRollbackFor = {},                // 指定异常不回滚
    readOnly = false,                  // 只读事务,优化查询
    timeout = 30                       // 事务超时秒数
)
  1. propagation 传播机制(高频)

    • REQUIRED(默认):有事务就加入,无则新建(90% 业务使用)
    • REQUIRES_NEW:新建独立事务,外层回滚不影响内层
    • SUPPORTS:有事务就用,没有就无事务执行
    • NOT_SUPPORTED:挂起当前事务,无事务执行
    • MANDATORY:必须存在外部事务,否则抛异常
    • NEVER:禁止有事务,有则抛异常
    • NESTED:嵌套事务,可单独回滚子事务(依赖数据库 savepoint)
  2. rollbackFor 默认只回滚RuntimeException/Error,如果需要受检异常 (IOException 等) 回滚,必须手动指定:

    java 复制代码
    @Transactional(rollbackFor = Exception.class)
  3. readOnly=true 标记只读事务,数据库优化查询,禁止增删改,适合纯查询接口。

三、适用使用场景

1. 多表 / 多操作必须同时成功的业务(最常用)

  • 下单:扣库存、生成订单、扣余额、生成物流记录,任一失败全部回滚
  • 转账:A 扣钱、B 加钱,不能单边成功
  • 数据同步:主表新增 + 多张关联子表批量插入 / 更新
java 复制代码
@Transactional
public void createOrder(OrderDTO dto){
    orderMapper.insert();
    stockMapper.deduct();
    accountMapper.subMoney();
}

2. 批量操作、批量导入 / 批量更新

批量插入几百上千条数据,中途报错需要全部撤销,防止半批数据入库。

3. 嵌套事务场景(REQUIRES_NEW / NESTED)

  • 主流程失败,但日志、记录操作必须留存:日志方法单独开新事务
java 复制代码
// 主方法,失败回滚
@Transactional
public void pay(){
    payMapper.update();
    try{
        saveLog();
    }catch (Exception e){}
}
// 独立事务,不受外层回滚影响
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(){
    logMapper.insert();
}

4. 需要自定义异常回滚规则

业务自定义 Exception,需要触发回滚,配置rollbackFor = Exception.class

5. 长流程业务,设置事务超时

复杂统计、大量数据计算,防止长事务占用锁:timeout = 60

6. 纯查询接口(只读事务优化)

java 复制代码
@Transactional(readOnly = true)
public Page<DataVO> queryData(){}

四、不适合使用 @Transactional 的场景

  1. 只读查询简单接口:不加注解性能更好,不需要事务;
  2. 耗时极长的大事务(文件 IO、网络请求、循环大量计算):长事务锁表,引发阻塞死锁;拆分事务;
  3. 同类内部调用A类.methodA()调用本类@Transactional methodB(),事务失效;
  4. 静态方法、private 方法:AOP 无法代理,注解无效;
  5. 只做单条简单 insert/update,无联动数据,没必要加。

五、常见踩坑点

  1. 方法非 public → 事务失效;
  2. 同类内部调用带 @Transactional 方法 → 失效;
  3. 抛出 try-catch 捕获异常,没有重新抛出 → 不会自动回滚;
  4. 只抛出受检 Exception,未配置 rollbackFor → 不回滚;
  5. 多数据源未指定transactionManager,事务管理器不匹配。

六、最简最佳实践

  1. 写在Service 层 public 方法上,不写在 Controller;
  2. 增删改业务统一加:@Transactional(rollbackFor = Exception.class)
  3. 查询使用@Transactional(readOnly = true)
  4. 独立日志、记录用REQUIRES_NEW隔离事务。

@GlobalTransactional 作用、原理、使用场景

一、基础说明

@GlobalTransactionalSeata 分布式事务 注解,用来解决跨多个微服务、多数据库的数据一致性问题。 对比:

  • @Transactional:本地事务,仅限单库、单服务
  • @GlobalTransactional:全局分布式事务,跨服务 / 多数据源统一回滚。

核心作用

  1. 开启Seata AT 模式全局事务 ,当前方法作为全局事务入口(TC 发起方)
  2. 所有被调用的微服务本地事务(@Transactional)都纳入同一全局事务;
  3. 任一节点报错,所有服务的数据库操作全部回滚;
  4. 正常执行完毕,所有分支事务统一提交。

执行流程(AT 模式)

  1. 入口方法加 @GlobalTransactional,Seata TC 创建全局 XID;
  2. 每个微服务 @Transactional 本地执行业务,记录 undo_log 快照;
  3. 全部执行成功 → TC 通知所有分支提交,删除 undo_log;
  4. 任意分支抛异常 → TC 通知所有分支根据 undo_log 回滚数据。

二、常用属性

java 复制代码
@GlobalTransactional(
    timeout = 60000, // 全局事务超时时间(毫秒),超时自动回滚
    name = "createOrderTx", // 全局事务名称,用于日志排查
    rollbackFor = Exception.class // 指定哪些异常触发全局回滚
)
  • 默认只在抛出异常向外抛出时回滚;
  • 如果内部 try-catch 吞掉异常,Seata 感知不到,不会回滚。

三、使用场景(什么时候必须用)

1. 跨微服务调用(最常用)

下单场景:订单服务 ↔ 库存服务 ↔ 账户服务,三个库三张表。 任一服务失败,全部撤销:

java 复制代码
@Service
public class OrderServiceImpl {
    @Autowired StockFeign stockFeign;
    @Autowired AccountFeign accountFeign;

    // 全局事务入口
    @GlobalTransactional(rollbackFor = Exception.class)
    public void createOrder() {
        // 1. 本地生成订单(本地@Transactional)
        orderMapper.insert();
        // 2. 远程扣库存(库存服务本地事务)
        stockFeign.deduct();
        // 3. 远程扣余额(账户服务本地事务)
        accountFeign.subBalance();
    }
}

任意 feign 调用抛异常,订单、库存、账户全部回滚。

2. 单服务多数据源(多库本地分布式事务)

一个服务连接 MySQL-DB1、MySQL-DB2,同时操作两个库: 单库@Transactional只能管一个数据源,多库必须用@GlobalTransactional

3. 复杂长链路业务(多级服务调用)

A 服务调 B,B 调 C,C 调 D,整条链路数据需要原子一致性。 只需要最外层入口方法@GlobalTransactional,内层微服务只需要普通@Transactional即可。

4. 导入 / 批量同步跨库数据

批量同步数据到多个业务库,中途任一库写入失败,全部回滚避免脏数据。

四、使用规范(重要)

  1. 仅加在全局事务入口 只在最外层发起方法加,内层微服务只使用普通@Transactional,不要重复加@GlobalTransactional

  2. 内层必须配合本地事务 每个微服务数据库操作方法必须标注@Transactional,否则分支无 undo_log,无法回滚。

  3. 异常不能被吞 不能 try-catch 后不抛出,Seata 捕获不到异常不会触发全局回滚:

    java 复制代码
    // 错误写法,吞异常不回滚
    @GlobalTransactional
    public void test() {
        try {
            stockFeign.deduct();
        } catch (Exception e) {
            log.error("异常");
            // 没有重新抛出,全局事务不回滚
        }
    }
    
    // 正确写法
    @GlobalTransactional(rollbackFor = Exception.class)
    public void test() {
        try {
            stockFeign.deduct();
        } catch (Exception e) {
            log.error("异常", e);
            throw new RuntimeException(e); // 重新抛出触发回滚
        }
    }
  4. 不能和本地事务混淆 @GlobalTransactional 依赖 Seata 组件,必须配置:

  • seata 注册中心、配置中心
  • 数据源代理 DataSourceProxy
  • 业务库创建 undo_log 表

五、不适合使用场景

  1. 仅单库单服务:只用@Transactional即可,无需分布式事务,性能损耗大;
  2. 纯查询接口:无数据修改,不需要事务;
  3. 高并发短平快简单业务:Seata AT 模式有锁、undo_log 开销,尽量拆分避免长全局事务;
  4. 异步、MQ 消息消费单独事务场景,可选用 TCC/SAGA 模式替代 AT。

六、@Transactional vs @GlobalTransactional 对比

注解 事务范围 依赖组件 适用场景
@Transactional 单数据库、单服务 Spring 自带,无需中间件 本地单体业务
@GlobalTransactional 多服务 / 多数据源分布式 Seata 微服务跨库跨服务数据一致性

七、常见坑

  1. 内层微服务不加@Transactional,分支事务无法回滚;
  2. 捕获异常不重新抛出,全局事务提交不回滚;
  3. 数据源未包装为 Seata 代理数据源,undo_log 不生成;
  4. 全局事务超时,自动触发全局回滚;
  5. 同类内部调用@GlobalTransactional方法,AOP 失效,分布式事务不生效。

一、同类内部方法自调用不生效(不会走代理),需要自己注入自己或拆到不同类

Spring 事务(@Transactional)、Seata 分布式事务(@GlobalTransactional)都是AOP 动态代理 实现。 只有外部对象调用 带注解的方法时,才会经过代理增强,开启事务; 同类内部 this.xxx () 自调用,直接走原生对象,不经过代理,事务完全失效。

下面分两种场景举例:普通本地事务 + 分布式事务。

场景 1:@Transactional 内部调用失效(错误示例)

java 复制代码
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    // 外部调用入口,无事务注解
    public void createOrder() {
        // 内部自调用 this.addOrder(),不走代理!事务失效
        this.addOrder();
    }

    // 加了事务注解
    @Transactional(rollbackFor = Exception.class)
    public void addOrder() {
        orderMapper.insert(new Order());
        // 抛出异常,期望回滚,但因为自调用,事务没开启,数据不会回滚
        int i = 1 / 0;
    }
}

调用逻辑:

java 复制代码
// controller 调用
orderService.createOrder();

现象:插入订单成功,报除零异常,数据没有回滚,事务注解完全无效。

场景 2:@GlobalTransactional 内部调用同样失效

java 复制代码
@Service
public class OrderService {

    @Autowired
    StockFeign stockFeign;

    public void submitOrder() {
        // this自调用,分布式事务失效
        this.globalCreateOrder();
    }

    @GlobalTransactional(rollbackFor = Exception.class)
    public void globalCreateOrder() {
        orderMapper.insert(...);
        stockFeign.deductStock();
        throw new RuntimeException("扣库存失败");
    }
}

调用 submitOrder() 抛异常,不会触发全局回滚,订单数据残留。

二、两种标准解决方案

方案 1:自己注入自己(本类代理对象调用,推荐)

java 复制代码
@Service
public class OrderService {

    // 注入自身代理对象
    @Autowired
    private OrderService orderService;

    @Autowired
    private OrderMapper orderMapper;

    public void createOrder() {
        // 使用代理对象调用,会走AOP增强,事务生效
        orderService.addOrder();
    }

    @Transactional(rollbackFor = Exception.class)
    public void addOrder() {
        orderMapper.insert(new Order());
        int i = 1 / 0;
    }
}

执行后抛异常,数据库数据自动回滚,事务正常生效。

方案 2:拆分方法到新类(解耦,大型项目推荐)

把带事务的方法抽成独立 Service,通过注入调用,天然走代理:

java 复制代码
@Service
public class OrderBizService {
    @Autowired
    private OrderMapper orderMapper;

    @Transactional(rollbackFor = Exception.class)
    public void addOrder() {
        orderMapper.insert(new Order());
        int i = 1 / 0;
    }
}
java 复制代码
@Service
public class OrderService {
    // 注入独立业务类
    @Autowired
    private OrderBizService orderBizService;

    public void createOrder() {
        // 外部类调用,代理生效
        orderBizService.addOrder();
    }
}

三、补充:使用 ApplicationContext 获取自身代理(不推荐)

java 复制代码
@Service
public class OrderService implements ApplicationContextAware {

    private ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.context = applicationContext;
    }

    public void createOrder() {
        // 从容器拿代理对象
        OrderService proxy = context.getBean(OrderService.class);
        proxy.addOrder();
    }

    @Transactional(rollbackFor = Exception.class)
    public void addOrder() {
        orderMapper.insert(new Order());
        int i = 1 / 0;
    }
}

缺点:代码侵入性强,日常开发优先用「自注入」或「拆分类」。

四、总结记忆

  1. this.方法() 内部调用 → 不走代理 → 事务失效;
  2. 本类注入自己 @Autowired OrderService selfself.方法() 生效;
  3. 抽离到其他 Service 注入调用,代码结构更清晰,无坑;
  4. @Transactional@GlobalTransactional 都遵循这条规则。