如何在 Spring Boot 中集成 Seata,解決分布式事務(wù)?
在分布式系統(tǒng)中,服務(wù)之間往往涉及多個數(shù)據(jù)庫操作,這就需要一個可靠的分布式事務(wù)解決方案來確保數(shù)據(jù)的一致性。Seata(Simple Extensible Autonomous Transaction Architecture)是一個開源的分布式事務(wù)解決方案,提供了高性能和易用的分布式事務(wù)服務(wù)。
本文將詳細介紹如何在 Spring Boot 項目中集成 Seata,并通過具體的代碼案例實現(xiàn)分布式事務(wù)管理。以下內(nèi)容包括配置、代碼實現(xiàn)以及一些注意事項,幫助你快速上手。
整體步驟
1、準備工作:
- 搭建 Seata Server 環(huán)境
- 創(chuàng)建兩個模擬服務(wù) order-service 和 account-service,分別代表訂單和賬戶的服務(wù)
- 準備一個 Eureka/Nacos 注冊中心(推薦 Nacos)
2、配置 Seata:
- 在項目中引入 Seata 依賴
- 配置 Seata 客戶端
- 配置數(shù)據(jù)源代理
3、代碼實現(xiàn):
- 使用 @GlobalTransactional 注解實現(xiàn)全局事務(wù)管理
- 編寫業(yè)務(wù)邏輯和分布式事務(wù)示例
環(huán)境搭建
1、下載并啟動 Seata Server
從 Seata 官方倉庫下載 Seata Server:https://github.com/seata/seata/releases。解壓后,修改 conf/registry.conf 文件,將注冊中心改為 Nacos:
registry {
type = "nacos"
nacos {
serverAddr = "localhost:8848" # Nacos 服務(wù)地址
namespace = "" # Nacos 命名空間
cluster = "default"
}
}
config {
type = "nacos"
nacos {
serverAddr = "localhost:8848"
namespace = ""
group = "SEATA_GROUP"
}
}
啟動 Seata Server:
sh bin/seata-server.sh
依賴引入
在兩個 Spring Boot 項目中添加以下依賴:
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.0.4.0</version>
</dependency>
配置文件詳解
以下是 application.yml 配置文件示例(order-service 和 account-service 的配置基本相同,區(qū)別僅在服務(wù)名上)。
order-service 配置
server:
port: 8081
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml
seata:
enabled: true
tx-service-group: my_tx_group # 事務(wù)組名,與 Seata Server 配置一致
account-service 配置
server:
port: 8082
spring:
application:
name: account-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/account_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml
seata:
enabled: true
tx-service-group: my_tx_group # 事務(wù)組名
數(shù)據(jù)庫表設(shè)計
訂單表 order_tbl
CREATE TABLE order_tbl (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
product_id VARCHAR(64) NOT NULL,
count INT NOT NULL,
money DECIMAL(10,2) NOT NULL,
status INT DEFAULT 0 COMMENT '0:待支付, 1:已支付'
);
賬戶表 account_tbl
CREATE TABLE account_tbl (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
total DECIMAL(10,2) NOT NULL,
used DECIMAL(10,2) NOT NULL,
residue DECIMAL(10,2) NOT NULL
);
核心代碼實現(xiàn)
1、全局事務(wù)注解
Seata 提供了 @GlobalTransactional 注解,用于在分布式事務(wù)中管理多個服務(wù)調(diào)用。
2、OrderService 示例
OrderService 中模擬訂單創(chuàng)建邏輯,同時調(diào)用 AccountService 扣減賬戶余額。
代碼實現(xiàn)
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private AccountClient accountClient; // Feign 客戶端調(diào)用 AccountService
@GlobalTransactional(name = "create-order-transaction", rollbackFor = Exception.class)
public void createOrder(String userId, String productId, int count, BigDecimal money) {
log.info("-----> 開始新建訂單");
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setCount(count);
order.setMoney(money);
order.setStatus(0);
orderMapper.insert(order);
log.info("-----> 訂單服務(wù)調(diào)用賬戶,開始扣減余額");
accountClient.decreaseAccount(userId, money);
log.info("-----> 訂單服務(wù)調(diào)用賬戶,扣減余額完成");
log.info("-----> 修改訂單狀態(tài)為已完成");
order.setStatus(1);
orderMapper.updateById(order);
log.info("-----> 訂單處理結(jié)束");
}
}
Mapper 層
@Mapper
public interface OrderMapper extends BaseMapper<Order> {}
3、AccountService 示例
AccountService 中實現(xiàn)賬戶余額的扣減邏輯。
代碼實現(xiàn)
@Service
@Slf4j
public class AccountService {
@Autowired
private AccountMapper accountMapper;
public void decreaseAccount(String userId, BigDecimal money) {
log.info("-----> 扣減賬戶余額開始");
accountMapper.decrease(userId, money);
log.info("-----> 扣減賬戶余額結(jié)束");
}
}
Mapper 層
@Mapper
public interface AccountMapper {
@Update("UPDATE account_tbl SET residue = residue - #{money}, used = used + #{money} WHERE user_id = #{userId}")
void decrease(@Param("userId") String userId, @Param("money") BigDecimal money);
}
4、Feign Client 示例
@FeignClient(name = "account-service")
public interface AccountClient {
@PostMapping("/account/decrease")
void decreaseAccount(@RequestParam("userId") String userId, @RequestParam("money") BigDecimal money);
}
測試分布式事務(wù)
啟動服務(wù)后,通過 Postman 或其他工具發(fā)送請求:
curl -X POST http://localhost:8081/order/create \
-H "Content-Type: application/json" \
-d '{
"userId": "1",
"productId": "1",
"count": 10,
"money": 100.00
}'
重要注意事項
1、Seata Undo Log 表: 在每個數(shù)據(jù)庫中創(chuàng)建 undo_log 表,供 Seata 記錄回滾日志。
CREATE TABLE undo_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
branch_id BIGINT NOT NULL,
xid VARCHAR(128) NOT NULL,
context VARCHAR(128) NOT NULL,
rollback_info LONGBLOB NOT NULL,
log_status INT NOT NULL,
log_created DATETIME NOT NULL,
log_modified DATETIME NOT NULL,
UNIQUE KEY ux_undo_log (xid, branch_id)
);
2、Seata 配置中心: 確保 Seata Server 的配置與服務(wù)端一致,事務(wù)組名必須統(tǒng)一。
總結(jié)
通過以上配置和代碼示例,我們實現(xiàn)了基于 Seata 的分布式事務(wù)管理。在實際項目中,可以根據(jù)業(yè)務(wù)需求靈活調(diào)整服務(wù)和表設(shè)計,確保數(shù)據(jù)一致性。
如果需要更深入的優(yōu)化或其他集成方法,可以進一步研究 Seata 的 AT、TCC 模式以及高可用配置。