以下是在 Spring Boot 中整合 Seata TCC 模式、PostgreSQL 和 MyBatis 实现分布式事务的完整配置和示例:
1. TCC 模式核心概念
- Try:预留资源(如冻结库存)。
- Confirm:确认操作(如实际扣减库存)。
- Cancel:补偿操作(如释放冻结的库存)。
2. 项目结构
src/
├── main/
│ ├── java/
│ │ └── com/example/
│ │ ├── tcc/ # TCC 接口定义
│ │ │ ├── InventoryTccService.java
│ │ │ └── OrderTccService.java
│ │ ├── mapper/ # MyBatis Mapper接口
│ │ │ ├── InventoryMapper.java
│ │ │ └── OrderMapper.java
│ │ ├── model/ # 实体类
│ │ │ ├── Inventory.java
│ │ │ └── Order.java
│ │ ├── service/ # 服务层
│ │ │ └── OrderService.java
│ │ └── Application.java # 启动类
│ └── resources/
│ ├── mapper/ # MyBatis XML
│ │ ├── InventoryMapper.xml
│ │ └── OrderMapper.xml
│ ├── application.yml # 配置文件
│ └── seata.conf # Seata 配置(可选)
3. 依赖配置(pom.xml)
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- PostgreSQL 驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!-- Seata -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<!-- OpenFeign(用于跨服务调用) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
4. Seata Server 配置
- 下载并启动 Seata Server(参考前文)。
- 配置注册中心和配置中心(如 Nacos)。
5. Spring Boot 应用配置
(1) application.yml
spring:
application:
name: order-service
datasource:
url: jdbc:postgresql://localhost:5432/order_db
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
# Seata 配置
seata:
enabled: true
application-id: order-service
tx-service-group: my-tx-group
service:
vgroup-mapping:
my-tx-group: default
grouplist:
default: 127.0.0.1:8091
registry:
type: nacos
nacos:
server-addr: localhost:8848
config:
type: nacos
# OpenFeign 配置
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
6. TCC 接口定义与实现
(1) 库存服务 TCC 接口
@LocalTCC
public interface InventoryTccService {
@TwoPhaseBusinessAction(name = "deductInventory", commitMethod = "confirm", rollbackMethod = "cancel")
boolean tryDeduct(@BusinessActionContextParameter(paramName = "productId") Long productId,
@BusinessActionContextParameter(paramName = "count") Integer count);
boolean confirm(BusinessActionContext context);
boolean cancel(BusinessActionContext context);
}
(2) 库存服务 TCC 实现
@Service
public class InventoryTccServiceImpl implements InventoryTccService {
@Autowired
private InventoryMapper inventoryMapper;
@Override
public boolean tryDeduct(Long productId, Integer count) {
// Try 阶段:冻结库存
inventoryMapper.freezeInventory(productId, count);
return true;
}
@Override
public boolean confirm(BusinessActionContext context) {
// Confirm 阶段:实际扣减库存
Long productId = (Long) context.getActionContext("productId");
Integer count = (Integer) context.getActionContext("count");
inventoryMapper.reduceInventory(productId, count);
return true;
}
@Override
public boolean cancel(BusinessActionContext context) {
// Cancel 阶段:释放冻结的库存
Long productId = (Long) context.getActionContext("productId");
Integer count = (Integer) context.getActionContext("count");
inventoryMapper.unfreezeInventory(productId, count);
return true;
}
}
7. MyBatis Mapper 与实体类
(1) 实体类 Inventory.java
public class Inventory {
private Long id;
private Long productId;
private Integer total;
private Integer frozen;
// Getters/Setters
}
(2) Mapper 接口 InventoryMapper.java
@Mapper
public interface InventoryMapper {
@Update("UPDATE inventory SET frozen = frozen + #{count} WHERE product_id = #{productId}")
void freezeInventory(@Param("productId") Long productId, @Param("count") Integer count);
@Update("UPDATE inventory SET total = total - #{count}, frozen = frozen - #{count} WHERE product_id = #{productId}")
void reduceInventory(@Param("productId") Long productId, @Param("count") Integer count);
@Update("UPDATE inventory SET frozen = frozen - #{count} WHERE product_id = #{productId}")
void unfreezeInventory(@Param("productId") Long productId, @Param("count") Integer count);
}
8. 服务层与全局事务
(1) 订单服务(主事务发起方)
@Service
public class OrderService {
@Autowired
private InventoryTccService inventoryTccService;
@GlobalTransactional(name = "createOrder", rollbackFor = Exception.class)
public void createOrder(Order order) {
// TCC Try 阶段:冻结库存
inventoryTccService.tryDeduct(order.getProductId(), order.getCount());
// 本地事务:创建订单
orderMapper.createOrder(order);
}
}
(2) MyBatis Mapper OrderMapper.java
@Mapper
public interface OrderMapper {
@Insert("INSERT INTO orders(product_id, count, status) VALUES(#{productId}, #{count}, 'CREATED')")
void createOrder(Order order);
}
9. PostgreSQL 表结构
(1) 库存表 inventory
CREATE TABLE inventory (
id SERIAL PRIMARY KEY,
product_id BIGINT UNIQUE NOT NULL,
total INT NOT NULL,
frozen INT NOT NULL DEFAULT 0
);
(2) 订单表 orders
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
product_id BIGINT NOT NULL,
count INT NOT NULL,
status VARCHAR(20)
);
10. 测试与验证
-
初始化库存数据:
INSERT INTO inventory (product_id, total) VALUES (1001, 100); -
执行下单操作:
@SpringBootTest public class OrderServiceTest { @Autowired private OrderService orderService; @Test public void testTccSuccess() { Order order = new Order(); order.setProductId(1001L); order.setCount(10); orderService.createOrder(order); // 验证库存 total=90,frozen=0 } @Test public void testTccRollback() { Order order = new Order(); order.setProductId(1001L); order.setCount(10); try { orderService.createOrder(order); throw new RuntimeException("模拟异常"); } catch (Exception e) { // 验证库存 total=100,frozen=0 } } }
11. 关键点总结
-
TCC 接口定义:
- 使用
@LocalTCC和@TwoPhaseBusinessAction注解定义 TCC 接口。 - 通过
@BusinessActionContextParameter传递参数到全局上下文。
- 使用
-
数据源配置:
- TCC 模式不依赖
DataSourceProxy,但需确保本地事务与 TCC 协调一致。
- TCC 模式不依赖
-
幂等性与空回滚:
- 实现 TCC 时需处理幂等性(多次调用 Confirm/Cancel 不重复扣减)。
- 处理空回滚(Try 未执行但 Cancel 被调用)。
-
跨服务调用:
- 若 TCC 服务跨微服务,需通过 Feign 调用并传递
XID。
- 若 TCC 服务跨微服务,需通过 Feign 调用并传递
总结
通过以上配置,可以实现:
- TCC 事务管理:通过自定义 Try/Confirm/Cancel 方法实现柔性事务。
- PostgreSQL 支持:结合 MyBatis 操作数据库。
- 分布式事务协调:Seata 保证跨服务操作的最终一致性。