前言
在微服务架构中,分布式事务是一个无法回避的难题。seata 作为一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。本文将通过一步步的教程,带你掌握如何在 spring boot 3.2.5 项目中整合 seata 1.8.0,实现分布式事务控制。
环境准备
在开始整合之前,请确保你的开发环境满足以下要求:
- jdk:17 或更高版本(spring boot 3.x 要求 jdk 17+)
- spring boot:3.2.5
- seata:1.8.0
- 注册中心:nacos(本文使用 nacos 作为注册中心和配置中心)
- 数据库:mysql 5.7+
- 项目构建工具:maven 3.6+
seata 服务端部署
seata 服务端(tc,即 transaction coordinator)是分布式事务的协调器,我们需要先部署好 seata server。
1. 下载 seata server
从 seata 官方 github release 页面下载 seata-server-1.8.0 的压缩包:
wget https://github.com/seata/seata/releases/download/v1.8.0/seata-server-1.8.0.tar.gz tar -zxvf seata-server-1.8.0.tar.gz
2. 配置 seata server
seata 1.8.0 版本使用 application.yml 作为配置文件,位于 seata/conf 目录下。
2.1 配置注册中心和配置中心
修改 application.yml,设置使用 nacos 作为注册中心和配置中心:
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: seata_group
username: "nacos"
password: "nacos"
data-id: seataserver.properties
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: seata_group
namespace: ""
cluster: default
username: "nacos"
password: "nacos"2.2 配置存储模式(可选)
如果需要持久化事务日志,可以配置使用数据库存储。在 nacos 配置中心创建 data-id 为 seataserver.properties 的配置,添加以下内容:
# 存储模式 store.mode=db # 数据库配置 store.db.datasource=druid store.db.dbtype=mysql store.db.driverclassname=com.mysql.cj.jdbc.driver store.db.url=jdbc:mysql://127.0.0.1:3306/seata_server?useunicode=true&rewritebatchedstatements=true store.db.user=root store.db.password=123456 store.db.minconn=5 store.db.maxconn=30 store.db.globaltable=global_table store.db.branchtable=branch_table store.db.locktable=lock_table store.db.querylimit=100
3. 初始化数据库表
如果使用数据库模式,需要在 mysql 中创建 seata 服务端所需的表:
-- 创建数据库 create database if not exists seata_server; -- 建表脚本位于 seata/script/server/db/ 目录下 -- 执行 mysql.sql 文件 source /path/to/seata/script/server/db/mysql.sql;
4. 启动 seata server
执行启动脚本:
# linux/mac sh ./bin/seata-server.sh # windows .\bin\seata-server.bat
服务启动后,默认监听端口:7091(控制台)和 8091(服务端口)。访问 nacos 控制台,如果看到 seata-server 服务注册成功,则表示部署完成。
spring boot 客户端整合
接下来,我们将创建两个 spring boot 微服务(订单服务和库存服务)来演示分布式事务。
1. 创建 spring boot 项目
使用 spring initializr 创建两个 spring boot 3.2.5 项目:
- order-service(订单服务,端口 8081)
- storage-service(库存服务,端口 8082)
2. 引入依赖
在每个服务的 pom.xml 中添加 seata 依赖:
<parent>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-parent</artifactid>
<version>3.2.5</version>
</parent>
<dependencies>
<!-- spring boot web -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
<!-- spring boot data jpa -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-data-jpa</artifactid>
</dependency>
<!-- mysql driver -->
<dependency>
<groupid>com.mysql</groupid>
<artifactid>mysql-connector-j</artifactid>
<scope>runtime</scope>
</dependency>
<!-- seata spring boot starter -->
<dependency>
<groupid>io.seata</groupid>
<artifactid>seata-spring-boot-starter</artifactid>
<version>1.8.0</version>
</dependency>
<!-- nacos 注册中心客户端(seata 注册需要) -->
<dependency>
<groupid>com.alibaba.nacos</groupid>
<artifactid>nacos-client</artifactid>
<version>2.2.3</version>
</dependency>
</dependencies>3. 客户端配置
在每个服务的 application.yml 中添加 seata 配置:
server:
port: 8081 # 订单服务端口
spring:
application:
name: order-service
datasource:
url: jdbc:mysql://localhost:3306/order_db?useunicode=true&characterencoding=utf8&usessl=false&servertimezone=asia/shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.driver
# seata 配置
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: default_tx_group
# 服务端分组映射
service:
vgroup-mapping:
default_tx_group: default
# 注册中心配置
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: seata_group
username: "nacos"
password: "nacos"
# 配置中心(可选)
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: seata_group
username: "nacos"
password: "nacos"
data-id: seataclient.properties
# 数据源代理(自动开启)
enable-auto-data-source-proxy: true
# 使用 at 模式
data-source-proxy-mode: at4. 创建 undo_log 表
seata at 模式需要在每个业务数据库中创建 undo_log 表,用于记录事务回滚日志:
-- 注意:在 order_db 和 storage_db 中都执行
create table if not exists `undo_log`
(
`id` bigint(20) not null auto_increment comment 'increment id',
`branch_id` bigint(20) not null comment 'branch transaction id',
`xid` varchar(100) not null comment 'global transaction id',
`context` varchar(128) not null comment 'undo_log context,such as serialization',
`rollback_info` longblob not null comment 'rollback info',
`log_status` int(11) not null comment '0:normal status,1:defense status',
`log_created` datetime not null comment 'create datetime',
`log_modified` datetime not null comment 'modify datetime',
primary key (`id`),
unique key `ux_undo_log` (`xid`, `branch_id`)
) engine = innodb
auto_increment = 1
default charset = utf8mb4 comment ='at transaction mode undo table';5. 业务代码实现
5.1 库存服务 (storage service)
库存服务提供扣减库存的接口:
@restcontroller
@requestmapping("/storage")
public class storagecontroller {
@autowired
private storagerepository storagerepository;
@postmapping("/deduct")
public string deduct(@requestparam long productid, @requestparam integer count) {
storage storage = storagerepository.findbyproductid(productid);
if (storage.getcount() < count) {
throw new runtimeexception("库存不足");
}
storage.setcount(storage.getcount() - count);
storagerepository.save(storage);
return "扣减成功";
}
}
@entity
public class storage {
@id
@generatedvalue(strategy = generationtype.identity)
private long id;
private long productid;
private integer count;
// getters and setters
}5.2 订单服务 (order service)
订单服务创建订单并调用库存服务:
@restcontroller
@requestmapping("/order")
public class ordercontroller {
@autowired
private resttemplate resttemplate;
@autowired
private orderrepository orderrepository;
@postmapping("/create")
@globaltransactional(name = "create-order", rollbackfor = exception.class)
public string createorder(@requestparam long productid,
@requestparam integer count,
@requestparam bigdecimal amount) {
// 1. 创建本地订单
order order = new order();
order.setproductid(productid);
order.setcount(count);
order.setamount(amount);
order.setstatus(0); // 待处理
orderrepository.save(order);
// 2. 远程调用库存服务扣减库存
string url = "http://localhost:8082/storage/deduct?productid=" + productid + "&count=" + count;
string result = resttemplate.postforobject(url, null, string.class);
// 3. 更新订单状态
order.setstatus(1); // 已完成
orderrepository.save(order);
return "订单创建成功";
}
}注意:@globaltransactional 注解是关键,它标识该方法需要开启全局事务。
6. 配置 resttemplate
在订单服务中,需要配置 resttemplate 以实现服务调用,并确保 seata 的 xid 能够透传:
@configuration
public class seataresttemplateconfig {
@bean
@loadbalanced
public resttemplate resttemplate() {
return new resttemplate();
}
/**
* 配置 resttemplate 拦截器,实现 xid 传递
*/
@bean
public resttemplate resttemplate(resttemplate resttemplate) {
resttemplate.setinterceptors(collections.singletonlist(new resttemplateinterceptor()));
return resttemplate;
}
/**
* 自定义拦截器,将 seata 的 xid 放入请求头
*/
public static class resttemplateinterceptor implements clienthttprequestinterceptor {
@override
public clienthttpresponse intercept(httprequest request, byte[] body,
clienthttprequestexecution execution) throws ioexception {
string xid = rootcontext.getxid();
if (stringutils.isnotblank(xid)) {
request.getheaders().add(rootcontext.key_xid, xid);
}
return execution.execute(request, body);
}
}
}测试分布式事务
1. 正常流程测试
请求订单创建接口:
curl "http://localhost:8081/order/create?productid=1&count=2&amount=100"
观察数据库:
- 订单表新增一条状态为 1 的记录
- 库存表对应商品库存减少 2
2. 异常回滚测试
修改库存服务,主动抛出异常:
@postmapping("/deduct")
public string deduct(@requestparam long productid, @requestparam integer count) {
storage storage = storagerepository.findbyproductid(productid);
if (storage.getcount() < count) {
throw new runtimeexception("库存不足");
}
storage.setcount(storage.getcount() - count);
storagerepository.save(storage);
// 模拟异常
if (productid == 1) {
throw new runtimeexception("模拟库存服务异常");
}
return "扣减成功";
}再次请求订单创建接口,观察结果:
- 订单表新增的记录状态为 0(说明未被更新)
- 库存表数量未减少
- undo_log 表中会记录回滚日志
常见问题及解决方案
1. 版本兼容性问题
spring boot 3.2.5 使用了 jakarta ee 9+(javax 包名改为 jakarta),而 seata 1.8.0 已经完全支持 jakarta,无需额外配置。如果遇到类找不到的问题,检查是否有依赖引入了旧的 javax 包。
2. xid 传递失败
跨服务调用时,如果下游服务无法获取 xid,事务会失效。解决方案:
- 使用支持 seata 的微服务组件(如 spring cloud alibaba)
- 手动实现拦截器传递 xid(如上面的 resttemplate 配置)
3. 数据源代理冲突
如果项目中同时使用 druid 等连接池,需要确保 seata 的数据源代理正确执行。seata 的 seata-spring-boot-starter 会自动代理数据源,无需额外配置。
4. 注册中心连接失败
检查 nacos 地址和端口是否正确,确保 seata server 已经成功注册到 nacos。
总结
本文详细介绍了 spring boot 3.2.5 整合 seata 1.8.0 的全过程,包括服务端部署、客户端配置、业务代码实现以及测试验证。seata 的 at 模式通过代理数据源和记录 undo_log,对业务代码无侵入,是分布式事务接入的首选方案。
在实际生产环境中,建议:
- 使用数据库模式存储事务日志,保证数据持久化
- 合理设置事务超时时间和重试策略
- 做好监控和告警,及时发现事务异常
到此这篇关于springboot3.2.5整合seata1.8.0详细教程的文章就介绍到这了,更多相关springboot整合seata内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论