Seata的AT与TCC模式使用案例
约 1732 字大约 6 分钟
2026-04-07
在 Spring Boot 项目中集成 Seata 的 AT 和 TCC 模式,是实现分布式事务的两种主流方案。它们的核心区别在于:AT 模式对业务代码无侵入,通过代理数据源自动完成回滚;而 TCC 模式则需要开发者手动实现 Try、Confirm、Cancel 三个阶段,灵活性更高。
以下是两种模式的详细集成步骤和代码案例。
🧩 前置准备
在开始之前,请确保已完成以下准备工作:
启动 Seata Server (TC):下载并运行 Seata Server,它作为事务协调器。
配置注册/配置中心:通常使用 Nacos,Seata Server 和业务服务都需注册到 Nacos。
准备数据库:
- Seata Server 表:为 Seata Server 创建独立的数据库(如
seata),并执行官方提供的mysql.sql脚本,创建global_table、branch_table和lock_table。 - 业务服务 undo_log 表:在每个参与分布式事务的业务数据库中,创建
undo_log表(仅 AT 模式需要)。
CREATE TABLE `undo_log` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT, `branch_id` BIGINT(20) NOT NULL, `xid` VARCHAR(100) NOT NULL, `context` VARCHAR(128) NOT NULL, `rollback_info` LONGBLOB NOT NULL, `log_status` INT(11) NOT NULL, `log_created` DATETIME NOT NULL, `log_modified` DATETIME NOT NULL, `ext` VARCHAR(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;- Seata Server 表:为 Seata Server 创建独立的数据库(如
📦 通用依赖配置
无论使用 AT 还是 TCC 模式,都需要在 pom.xml 中添加 Seata 的 starter 依赖。
<dependencies>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Seata Starter -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.0.0</version> <!-- 请使用最新稳定版 -->
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- MyBatis Plus (可选) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
</dependencies>⚙️ 通用应用配置
在 application.yml 中进行 Seata 的基础配置。
server:
port: 8081
spring:
application:
name: order-service # 你的服务名
datasource:
url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=UTF-8
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
# Seata 配置
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_test_tx_group # 事务组名,需与 Seata Server 配置一致
service:
vgroup-mapping:
my_test_tx_group: default # 事务组到集群的映射
grouplist:
default: 127.0.0.1:8091 # Seata Server 地址 (仅测试环境需要,生产环境通过注册中心发现)
registry:
type: nacos # 注册中心类型
nacos:
server-addr: 127.0.0.1:8848
config:
type: nacos # 配置中心类型
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
data-id: seataServer.properties🔄 AT 模式案例
AT 模式是 Seata 的默认模式,对业务代码无侵入。你只需在开启全局事务的方法上添加 @GlobalTransactional 注解即可。
场景:创建一个订单,需要同时扣减库存和账户余额。
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class OrderService {
@Resource
private OrderMapper orderMapper;
@Resource
private StockService stockService; // 远程调用库存服务
@Resource
private AccountService accountService; // 远程调用账户服务
/**
* AT模式下的分布式事务
* 所有参与的服务(本服务、stockService、accountService)都使用AT模式
*/
@GlobalTransactional(name = "create-order-at", rollbackFor = Exception.class)
public void createOrder(Order order) {
// 1. 创建订单 (本地数据库操作,AT模式会自动记录undo_log)
orderMapper.insert(order);
// 2. 调用库存服务扣减库存 (远程服务,其内部方法也需@GlobalTransactional或作为RM参与)
stockService.decreaseStock(order.getProductId(), order.getCount());
// 3. 调用账户服务扣减余额
accountService.deduct(order.getUserId(), order.getAmount());
// 如果任何一步抛出异常,整个全局事务将回滚
// 例如:int i = 1 / 0;
}
}特点:
- 优点:业务代码几乎零侵入,开发效率高。
- 缺点:存在全局行锁,高并发下可能影响性能。
🛠️ TCC 模式案例
TCC 模式需要开发者手动实现 Try、Confirm、Cancel 三个接口,提供了更高的灵活性,常用于非数据库资源或需要自定义补偿逻辑的场景。
场景:同样是扣减库存,但这次我们用 TCC 模式实现。
1. 定义 TCC 接口
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
import io.seata.spring.annotation.LocalTCC;
@LocalTCC // 标记为本地TCC服务
public interface InventoryTccAction {
/**
* Try 阶段:尝试执行业务,预留资源
*/
@TwoPhaseBusinessAction(name = "inventoryTccAction", commitMethod = "commit", rollbackMethod = "rollback")
boolean tryDeductStock(BusinessActionContext actionContext,
@BusinessActionContextParameter(paramName = "goodsId") Long goodsId,
@BusinessActionContextParameter(paramName = "num") Integer num);
/**
* Confirm 阶段:确认执行,提交预留的资源
*/
boolean commit(BusinessActionContext actionContext);
/**
* Cancel 阶段:取消执行,释放预留的资源
*/
boolean rollback(BusinessActionContext actionContext);
}2. 实现 TCC 接口
实现类需要处理三个阶段的逻辑,并务必处理好幂等、空回滚和悬挂问题。
import io.seata.rm.tcc.api.BusinessActionContext;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class InventoryTccActionImpl implements InventoryTccAction {
@Resource
private StockMapper stockMapper;
// 假设有一个TccTransactionMapper用于记录TCC事务状态,以实现幂等性
@Resource
private TccTransactionMapper tccTransactionMapper;
@Override
public boolean tryDeductStock(BusinessActionContext actionContext, Long goodsId, Integer num) {
String xid = actionContext.getXid();
String branchId = String.valueOf(actionContext.getBranchId());
// 1. 悬挂处理:检查事务是否已回滚
// if (tccTransactionMapper.isRollbacked(xid, branchId)) { return false; }
// 2. 幂等处理:检查Try是否已执行
if (tccTransactionMapper.isTryExecuted(xid, branchId)) { return true; }
// 3. 核心业务:冻结库存,而不是直接扣减
stockMapper.freeze(goodsId, num);
// 4. 记录事务状态
tccTransactionMapper.add(xid, branchId, "TRY");
return true;
}
@Override
public boolean commit(BusinessActionContext actionContext) {
String xid = actionContext.getXid();
String branchId = String.valueOf(actionContext.getBranchId());
// 1. 幂等处理:检查Confirm是否已执行
if (tccTransactionMapper.isCommitExecuted(xid, branchId)) { return true; }
// 2. 核心业务:真正扣减库存
Long goodsId = (Long) actionContext.getActionContext("goodsId");
Integer num = (Integer) actionContext.getActionContext("num");
stockMapper.deduct(goodsId, num);
// 3. 更新事务状态
tccTransactionMapper.update(xid, branchId, "COMMIT");
return true;
}
@Override
public boolean rollback(BusinessActionContext actionContext) {
String xid = actionContext.getXid();
String branchId = String.valueOf(actionContext.getBranchId());
// 1. 空回滚处理:如果Try未执行,直接记录并返回
if (!tccTransactionMapper.isTryExecuted(xid, branchId)) {
tccTransactionMapper.add(xid, branchId, "ROLLBACK");
return true;
}
// 2. 幂等处理:检查Rollback是否已执行
if (tccTransactionMapper.isRollbackExecuted(xid, branchId)) { return true; }
// 3. 核心业务:解冻库存
Long goodsId = (Long) actionContext.getActionContext("goodsId");
Integer num = (Integer) actionContext.getActionContext("num");
stockMapper.unfreeze(goodsId, num);
// 4. 更新事务状态
tccTransactionMapper.update(xid, branchId, "ROLLBACK");
return true;
}
}3. 调用 TCC 服务
在另一个服务中,通过 @GlobalTransactional 来调用 TCC 服务。
@Service
public class OrderService {
@Resource
private OrderMapper orderMapper;
@Resource
private InventoryTccAction inventoryTccAction; // 注入TCC接口
@GlobalTransactional(name = "create-order-tcc", rollbackFor = Exception.class)
public void createOrder(Order order) {
// 1. 创建订单 (AT模式)
orderMapper.insert(order);
// 2. 调用TCC服务扣减库存
// 这里会先执行 tryDeductStock,如果全局事务提交则执行 commit,回滚则执行 rollback
inventoryTccAction.tryDeductStock(null, order.getGoodsId(), order.getNum());
// 模拟异常,触发回滚
// int i = 1 / 0;
}
}📌 模式对比与总结
| 特性 | AT 模式 | TCC 模式 |
|---|---|---|
| 侵入性 | 低,对业务代码无侵入 | 高,需实现 Try/Confirm/Cancel |
| 一致性保证 | 依赖数据库的 undo_log | 依赖业务逻辑的补偿机制 |
| 隔离性 | 通过全局锁实现 | 通过 Try 阶段预留资源实现 |
| 性能 | 存在全局锁,高并发下有瓶颈 | 无全局锁,性能更高 |
| 适用场景 | 大多数基于数据库的分布式事务 | 复杂业务、非数据库资源、对性能要求高 |
如何选择?
- 首选 AT 模式:对于大多数基于关系型数据库的业务场景,AT 模式因其低侵入性和易用性是首选。
- 选用 TCC 模式:当遇到高性能要求、需要操作非数据库资源(如 Redis、MQ)或 AT 模式无法满足复杂业务补偿逻辑时,再考虑使用 TCC 模式。
