目录
一、分布式事务面试题
1.多个数据库之间如何处理分布式事务?
2.若拿出如下场景,阁下将如何应对?
3.阿里巴巴的Seata-AT模式如何做到对业务的无侵入?
4.对于分布式事务问题,你知道的解决方案有哪些?请你谈谈?
二、分布式事务问题如何产生?请先看业务
分布式事务之前:
分布式事务改变后:
三、Seata简介
Seata的发展历程:
官网及源码
使用
Seata的分布式交易解决方案
四、Seata工作流程简介:
分布式事务的理解:
Seata对分布式事务的协调和控制就是1+3
分布式事务的执行流程-总结:(重点)
五、各事务模式(重点)
Seata AT 模式(常用)
适用场景:
前提
整体机制
整体机制的理解
一阶段过程;
二阶段异常回滚:
二阶段正常提交:
写隔离
以一个示例来说明:
读隔离
工作机制示例:
一阶段
二阶段-回滚
二阶段-提交
回滚日志表
Seata TCC 模式
适用场景:
Seata Saga 模式
适用场景:
Seata XA 模式
适用场景:
六、Seata-Server2.0.0安装
1.下载地址
2.下载版本
3.各种seata参数官网参考
关注属性(详细描述见全属性)
全属性
公共部分
server 端
client 端
4.Seata新手部署指南
5.mysql8.0数据库里面建库+建表
建库—创建seata库
建表—在seata库创建表
运行结果
6.更改配置
7.先启动Nacos2.2.3端口号8848
8.再启动seata-server-2.0.0
七、Seata案例实战-数据库和表准备(AT)
分布式事务案例—业务说明
1.创建3个业务数据库DATABASE
建库SQL
2. 按照上述3库分别建对应的undo log回滚日志表
undo_log建表sql
3.按照上述3库分别建对应业务表
t_order脚本SQL
t_account脚本SQL
t_storage脚本SQL
最终SQL(全)
建seata_order库+建t_order表+undo_log表
建seata_storage库+建t_storage 表+undo_log表
建seata_account库+建t_account 表+undo_log表
最终效果:
八、Seata系例实战-微服务编码落地实现(AT)
1.Mybaits一键生成
config.properties
generatorConfig.xml
双击执行一键生成
2.修改公共cloud-api-commons新增库存和账户两个Feign服务接口
3.新建订单Order微服务
(1).建Module
(2).改POM
(3).写YML
(4).主启动类
SpringBootApplication是一个组合注解,主要用于标记Spring Boot的主配置类,用于启动Spring Boot应用程序。它由以下三个注解组成:
(5).业务类
1).entities
2).OrderMapper
3).Service接口及实现
OrderService
OrderServiceImpl(重点)
4).Controller
4.新建库存Storage微服务
(1).建Module
(2).改POM
(3).写YML
(4).主启动类
(5).业务类
1).entities
2).StorageMapper
3).Service接口及实现
4).Controller
5.新建账户Account微服务
(1).建Module
(2).改POM
(3).写YML
(4).主启动类
(5).业务类
1).entities
2).AccountMapper
3).Service接口及实现
4).Controller
九、Seata案例实战-测试
服务启动情况
数据库初始化情况
1.正常下单
2).此时我们没有在订单模块添加@GlobalTransactional
3).正常下单,第1次
故障现象
导致原因
4).正常下单,第2次
数据库情况
2.超时异常出错,没有@GlobalTransactional
1).添加超时方法
2).故障情况
3.超时异常解决,添加@GlobalTransactional
前提条件
查看Seata后台:
全局事务ID
全局锁
数据回滚
业务中
回滚后
全局事务,正常扣除操作
总结
一、分布式事务面试题
1.多个数据库之间如何处理分布式事务?2.若拿出如下场景,阁下将如何应对?3.阿里巴巴的Seata-AT模式如何做到对业务的无侵入?4.对于分布式事务问题,你知道的解决方案有哪些?请你谈谈?
1.多个数据库之间如何处理分布式事务?

2.若拿出如下场景,阁下将如何应对?

冻结库存 是指在ERP系统中, 将部分库存暂时锁定,使其不能参与正常的出库和入库操作 。这种操作通常用于防止库存被意外或错误地使用,确保在特定时间段内库存的稳定性和可用性。冻结库存的方法包括设置库存冻结、使用库存锁定功能、创建保留订单和利用批次管理。
3.阿里巴巴的Seata-AT模式如何做到对业务的无侵入?
4.对于分布式事务问题,你知道的解决方案有哪些?请你谈谈?
- TCC(Try-Confirm-Cancel)又被称补偿事务
- 类似2PC的柔性分布式解决方案,2PC改良版
二、分布式事务问题如何产生?请先看业务
一次业务操作需要 跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。但是关系型数据库提供的能力是基于 单机事务的,一旦遇到分布式事务场景,就需要通过更多其他技术手段来解决问题。
分布式事务之前:
- 单机单库没这个问题
- 表结构的关系从1:1->1:N -> N:N
分布式事务改变后:

三、Seata简介
Seata的发展历程:
阿里巴巴作为国内最早一批进行应用分布式(微服务化)改造的企业,很早就遇到微服务架构下的分布式事务问题。2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案:2014 年,阿里中间件团队发布 TXC(Taobao Transaction Constructor),为集团内应用提供分布式事务服务。2016 年,TXC 在经过产品化改造后,以 GTS(Global Transaction Service) 的身份登陆阿里云,成为当时业界唯一一款云上分布式事务产品。在阿云里的公有云、专有云解决方案中,开始服务于众多外部客户。2019 年起,基于 TXC 和 GTS 的技术积累,阿里中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And Rollback, FESCAR),和社区一起建设这个分布式事务解决方案。2019 年 fescar(全称fast easy commit and rollback) 被重命名为了seata(simple extensiable autonomous transaction architecture)。TXC、GTS、Fescar 以及 seata 一脉相承,为解决微服务架构下的分布式事务问题交出了一份与众不同的答卷。Seata最初是阿里巴巴开发的,但现在已经捐赠给了Apache基金会,成为Apache项目的一部分。
- 单个数据库只能对本身进行事务的控制, 而seata是分布式事务框架,可以对多个数据库进行全局的事务控制,保证全局事务的运行正常的框架。
- 当事物发生超时或异常时,seata会对所管理数据库,统一进行数据回滚,保证数据的正确性。
官网及源码
使用
Seata的分布式交易解决方案

四、Seata工作流程简介:
分布式事务的理解:
- 分布式事务是由一批分支事务组成的全局事务,通常分支事务只是本地事务。
- 纵观整个分布式事务的管理,就是全局事务ID的传递和变更,要让开发者无感知。
Seata对分布式事务的协调和控制就是1+3
1+3:1个XID+TC+TM+RM
- 1个XID :XID是全局事务的唯一标识,它可以在服务的调用链路中传递,绑定到服务的事务上下文中。
- TC (Transaction Coordinator) - 事务协调者
- TM (Transaction Manager) - 事务管理器
- RM (Resource Manager) - 资源管理器
- TC(Transaction Coordinator)事务协调器:就是Seata,负责维护全局事务和分支事务的状态,驱动全局事务提交或回滚。
- TM(Transaction Manager) 事务管理器:标注全局@GlobalTransactional启动入口动作的微服务模块(比如订单模块),它是事务的发起者,负责定义全局事务的范围,并根据TC维护的全局事务和分支事务状态,做出开始事务、提交事务、回滚事务的决议。
- RM(Resource Manager)资源管理器:就是mysql数据库本身,可以是多个RM,负责管理分支事务上的资源,像TC注册分支事务,汇报分支事务状态,驱动分支事务的提交和回滚。

分布式事务的执行流程-总结:(重点)

@GlobalTransactional
public void doBusiness() {// 本地事务serviceA.doSomething();// 本地事务serviceB.doSomethingElse();
}
@GlobalTransactional
public void globalMethod() {// 调用另一个标记为@GlobalTransactional的方法nestedMethod();// 业务逻辑...
}
@GlobalTransactional
public void nestedMethod() {
// 业务逻辑...
}
五、各事务模式(重点)
- Seata AT 模式
- Seata TCC 模式
- Seata Saga 模式
- Seata XA 模式
Seata AT 模式(常用)
AT 模式是 Seata 创新的一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,我们使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。
适用场景:
- 适用场景:主要用于CRUD(创建、读取、更新、删除)操作较多的业务场景,尤其是当业务逻辑直接操作数据库,并且可以容忍短暂的数据不一致时。AT模式通过记录数据的前后镜像来实现撤销(回滚)操作,适合于大部分单纯依赖于单个数据库事务的微服务场景。
- 优点:简单易用,不需要改动业务代码,自动完成分布式事务的提交和回滚。
- 缺点:不适合跨多种存储资源的事务,且在高并发场景下性能可能受影响。
前提
- 基于支持本地 ACID 事务的关系型数据库。
- Java 应用,通过 JDBC 访问数据库。
整体机制
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
- 二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
整体机制的理解
一阶段过程;
1 解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,2 执行“业务 SQL”更新业务数据,在业务数据更新之后,3 其保存成“after image”,最后生成行锁。

二阶段异常回滚:
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用undo_log中的“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据, 如果不一致就说明有脏写,出现脏写就需要转人工处理。

二阶段正常提交:
因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将 一阶段保存的快照数据和行锁删掉,完成数据清理即可。

写隔离
- 一阶段本地事务提交前,需要确保先拿到 全局锁 。
- 拿不到 全局锁 ,不能提交本地事务。
- 拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
以一个示例来说明:


读隔离

工作机制示例:
Field | Type | Key |
id | bigint(20) | PRI |
name | varchar(100) | |
since | varchar(100) |
update product set name = 'GTS' where name = 'TXC';
一阶段
- 解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = 'TXC')等相关的信息。
- 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
select id, name, since from product where name = 'TXC';
id | name | since |
1 | TXC | 2014 |
3.执行业务 SQL:更新这条记录的 name 为 'GTS'。
4.查询后镜像:根据前镜像的结果,通过 主键 定位数据。
select id, name, since from product where id = 1;
id | name | since |
1 | GTS | 2014 |
5.插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到 UNDO_LOG 表中。
{"branchId": 641789253,"undoItems": [{"afterImage": {"rows": [{"fields": [{"name": "id","type": 4,"value": 1}, {"name": "name","type": 12,"value": "GTS"}, {"name": "since","type": 12,"value": "2014"}]}],"tableName": "product"},"beforeImage": {"rows": [{"fields": [{"name": "id","type": 4,"value": 1}, {"name": "name","type": 12,"value": "TXC"}, {"name": "since","type": 12,"value": "2014"}]}],"tableName": "product"},"sqlType": "UPDATE"}],"xid": "xid:xxx"
}
6.提交前,向 TC 注册分支:申请 product 表中,主键值等于 1 的记录的 全局锁 。
7.本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
8.将本地事务提交的结果上报给 TC。
二阶段-回滚
1.收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
2.通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
3.数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。
4.根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
update product set name = 'TXC' where id = 1;
5.提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
二阶段-提交
- 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
- 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。
回滚日志表
Field | Type |
branch_id | bigint PK |
xid | varchar(100) |
context | varchar(128) |
rollback_info | longblob |
log_status | tinyint |
log_created | datetime |
log_modified | datetime |
-- 注意此处0.7.0+ 增加字段 context
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,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
Seata TCC 模式
TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,是继 AT 模式后第二种支持的事务模式,最早由蚂蚁金服贡献。其分布式事务模型直接作用于服务层,不依赖底层数据库,可以灵活选择业务资源的锁定粒度,减少资源锁持有时间,可扩展性好,可以说是为独立部署的 SOA 服务而设计的。
适用场景:
- 适用场景:适用于需要显式控制事务边界的复杂业务流程,特别是在业务操作可以明确分为尝试(Try)、确认(Confirm)和取消(Cancel)三个阶段的情况下。TCC模式适合于执行时间较长,需要人工干预或第三方服务参与的分布式事务。
- 优点:灵活性高,可以精细控制事务的每个阶段,适用于复杂的业务逻辑。
- 缺点:需要用户显式地实现Try、Confirm、Cancel三个操作,增加了开发的复杂度。
Seata Saga 模式
Saga 模式是 SEATA 提供的长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
适用场景:
- 适用场景:适用于长事务场景,其中业务流程包含一系列的本地事务,这些本地事务需要按照一定的顺序执行。SAGA模式通过定义一系列的事务步骤和相对应的补偿操作(回滚操作)来管理事务,适合于微服务架构下的复杂业务流程。
- 优点:适合长事务处理,可以保证分布式事务的最终一致性。
- 缺点:需要定义每个步骤的补偿操作,对业务侵入性较高。
Seata XA 模式
XA 模式是从 1.2 版本支持的事务模式。XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。Seata XA 模式是利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种事务模式。
适用场景:
- 适用场景:适用于长事务场景,其中业务流程包含一系列的本地事务,这些本地事务需要按照一定的顺序执行。SAGA模式通过定义一系列的事务步骤和相对应的补偿操作(回滚操作)来管理事务,适合于微服务架构下的复杂业务流程。
- 优点:适合长事务处理,可以保证分布式事务的最终一致性。
- 缺点:需要定义每个步骤的补偿操作,对业务侵入性较高。
六、Seata-Server2.0.0安装
1.下载地址2.下载版本3.各种seata参数官网参考4.Seata新手部署指南5.mysql8.0数据库里面建库+建表6.更改配置7.先启动Nacos2.2.3端口号88488.再启动seata-server-2.0.0
1.下载地址
Seata Java Download | Apache Seata
2.下载版本
Release v2.0.0(Not Apache release) · apache/incubator-seata · GitHub

3.各种seata参数官网参考
参数配置 | Apache Seata
关注属性(详细描述见全属性)
server 端 | client 端 |
registry.type | registry.type |
config.type | config.type |
#store.mode=db 需要以下配置 | service.vgroupMapping.my_test_tx_group |
store.db.driverClassName | service.default.grouplist |
store.db.url | service.disableGlobalTransaction |
store.db.user | |
store.db.password | |
#store.mode=redis 需要以下配置 | |
store.redis.host | |
store.redis.port | |
store.redis.database | |
store.redis.password | |
#store.mode=raft 需要以下配置 | |
server.raft.group | |
server.raft.server-addr | |
server.raft.snapshot-interval |
全属性
公共部分
key | desc | remark | change record |
transport.type | socket 通信方式 | TCP、UNIX_DOMAIN_SOCKET,默认 TCP | |
transport.server | socket 通道类型 | NIO、NATIVE(根据操作系统类型和 socket 通信方式选择 KQueue 或 Epoll,注意 Windows 只支持 NIO,选择这种方式会抛出异常) | |
transport.threadFactory.bossThreadSize | Netty 通信模型 Boss group 线程数 | 默认 1 | |
transport.threadFactory.workerThreadSize | Netty 通信模型 Worker group 线程数 | 可配置线程数或选择特定线程工作模式下的线程数,线程的默认工作模式有 4 种:Auto(2*CPU 核数 + 1)、Pin(CPU 核数,适用于计算密集型任务)、BusyPin(CPU 核数 + 1,适用于计算密集型且内存比较有限的场景)、Default(2*CPU 核数,适用于 IO 密集型任务),默认值为 Default 模式 | |
transport.shutdown.wait | 服务端 Netty 线程池关闭前等待服务下线时间 | 默认 3 秒 | |
transport.serialization | client 和 server 通信编解码方式 | seata(ByteBuf)、protobuf、kryo、hessian、fst,默认 seata | |
transport.compressor | client 和 server 通信数据压缩方式 | none、gzip、zip、sevenz、bzip2、lz4、deflater、zstd,默认 none | 1.2.0 之前:gzip 1.2.0:zip、sevenz、bzip2 1.3.0:lz4 1.4.1:deflater 1.5.1:zstd |
transport.heartbeat | client 和 server 通信心跳检测开关 | 默认 true 开启 | |
registry.type | 注册中心类型 | 默认 file,支持 file 、nacos 、redis、eureka、zk、consul、etcd3、sofa、custom | 1.6.0 版本 Sever 端支持可同时注册到多个注册中心,以逗号分隔注册中心名 |
config.type | 配置中心类型 | 默认 file,支持 file、nacos 、apollo、zk、consul、etcd3、springcloud、custom |
server 端
key | desc | remark | change record |
transport.enableTcServerBatchSendResponse | TC 批量发送回复消息开关 | 默认 false | 1.5.1 版本新增,建议为 true,可解决 client 批量消息时的线头阻塞问题 |
transport.rpcRmRequestTimeout | RM 二阶段下发请求超时时间 | 默认 15 秒 | |
transport.rpcTmRequestTimeout | TM 二阶段下发请求超时时间 | 默认 30 秒 | |
transport.rpcTcRequestTimeout | TC 二阶段下发请求超时时间 | 默认 15 秒 | 1.5.1 版本新增 |
server.undo.logSaveDays | undo 保留天数 | 默认 7 天,log_status=1(附录 3)和未正常清理的 undo | |
server.undo.logDeletePeriod | undo 清理线程间隔时间 | 默认 86400000,单位毫秒 | |
server.maxCommitRetryTimeout | 二阶段提交重试超时时长 | 单位 ms,s,m,h,d,对应毫秒,秒,分,小时,天,默认毫秒。默认值-1 表示无限重试。公式: timeout>=now-globalTransactionBeginTime,true 表示超时则不再重试(注: 达到超时时间后将不会做任何重试,有数据不一致风险,除非业务自行可校准数据,否者慎用) | |
server.maxRollbackRetryTimeout | 二阶段回滚重试超时时长 | 同 commit | |
server.recovery.committingRetryPeriod | 二阶段提交未完成状态全局事务重试提交线程间隔时间 | 默认 1000,单位毫秒 | |
server.recovery.asynCommittingRetryPeriod | 二阶段异步提交状态重试提交线程间隔时间 | 默认 1000,单位毫秒 | |
server.recovery.rollbackingRetryPeriod | 二阶段回滚状态重试回滚线程间隔时间 | 默认 1000,单位毫秒 | |
server.recovery.timeoutRetryPeriod | 超时状态检测重试线程间隔时间 | 默认 1000,单位毫秒,检测出超时将全局事务置入回滚会话管理器 | |
server.rollbackRetryTimeoutUnlockEnable | 二阶段回滚超时后是否释放锁 | 默认 false | |
server.distributedLockExpireTime | Sever 端事务管理全局锁超时时间 | 默认 10000,单位毫秒 | 1.5.1 版本新增 |
server.server.xaerNotaRetryTimeout | 防止 XA 分支事务悬挂的重试超时时间 | 默认 60000,单位毫秒 | 1.5.1 版本新增 |
server.session.branchAsyncQueueSize | 分支事务 Session 异步删除线程池队列大小 | 默认 5000 | 1.5.1 版本新增 |
server.session.enableBranchAsyncRemove | 分支事务 Session 异步删除开关 | 默认 false | 1.5.1 版本新增 |
server.enableParallelRequestHandle | 对于批量请求消息的并行处理开关 | 默认 true | 1.5.2 版本新增 |
server.enableParallelHandleBranch | 二阶段并行下发开关 | 默认 false | 2.0.0 版本新增 |
server.applicationDataLimitCheck | 是否开启应用数据大小检查 | 默认 false | |
server.applicationDataLimit | 应用数据大小限制 | 默认 64000 | |
server.raft.group | raft 存储模式下的 group,client 的事务分组对应的值要与之对应,如 service.vgroup-mapping.default_tx_group=default | default | 2.0.0 版本新增 |
server.raft.server-addr | raft 集群列表如 192.168.0.111:9091,192.168.0.112:9091,192.168.0.113:9091 | 2.0.0 版本新增 | |
server.raft.snapshot-interval | 间隔多久做一次内存快照,每做一次快照将暂停状态机,但是能提高停机恢复速度 | 默认 600 秒 | 2.0.0 版本新增 |
server.raft.apply-batch | 任务累积批次后提交至 leader | 默认 32 | 2.0.0 版本新增 |
server.raft.max-append-bufferSize | raft 日志存储缓冲区最大大小 | 默认 256K | 2.0.0 版本新增 |
server.raft.max-replicator-inflight-msgs | 在启用 pipeline 请求情况下,最大 in-flight 请求数 | 默认 256 | 2.0.0 版本新增 |
server.raft.disruptor-buffer-size | 内部 disruptor buffer 大小,如果是写入吞吐量较高场景,需要适当调高该值, | 默认 16384 | 2.0.0 版本新增 |
server.raft.election-timeout-ms | 超过多久没有 leader 的心跳开始重选举 | 默认 1000 毫秒 | 2.0.0 版本新增 |
server.raft.reporter-enabled | raft 自身的监控是否开启 | 默认 false | 2.0.0 版本新增 |
server.raft.reporter-initial-delay | 监控输出间隔 | 默认 60 秒 | 2.0.0 版本新增 |
server.raft.serialization | 序列化方式,目前仅支持 jackson | 默认 jackson | 2.0.0 版本新增 |
server.raft.compressor | raftlog 和 snapshot 的压缩方式,支持 gzip, zstd, lz4 | none | 2.0.0 版本新增 |
server.raft.sync | raftlog 同步刷盘 | true | 2.0.0 版本新增 |
store.mode | 事务会话信息存储方式 | file 本地文件(不支持 HA),db 数据库、redis、raft 支持 HA | 1.5.1 版本改用 lock 和 session 分离存储,2.0.0 开始支持 raft 模式 |
store.lock.mode | 事务锁信息存储方式 | file 本地文件(不支持 HA),db 数据库,redis(支持 HA);配置为空时,取 store.mode 配置项值,raft 模式不允许指定 | 1.5.1 版本新增,session 和 lock 可分离存储 |
store.session.mode | 事务回话信息存储方式 | file 本地文件(不支持 HA),db 数据库,redis(支持 HA);配置为空时,取 store.mode 配置项值。raft 模式不允许单独指定 | 1.5.1 版本新增,session 和 lock 可分离存储 |
store.publicKey | db 或 redis 存储密码解密公钥 | 1.4.2 版本支持 | |
store.file.dir | file 模式文件存储文件夹名 | 默认 sessionStore | |
store.file.maxBranchSessionSize | file 模式文件存储分支 session 最大字节数 | 默认 16384(16kb),单位 byte | |
store.file.maxGlobalSessionSize | file 模式文件存储全局 session 最大字节数 | 默认 512b,单位 byte | |
store.file.fileWriteBufferCacheSize | file 模式文件存储 buffer 最大缓存大小 | 默认 16384(16kb),单位 byte,写入 session 等数据量大于该值时会抛出异常 | |
store.file.flushDiskMode | file 模式文件存储刷盘策略 | 默认 async,可选 sync | |
store.file.sessionReloadReadSize | file 模式文件存储 Server 节点重启后从备份文件中恢复的 session 或 lock key 上限个数 | 默认 100 | |
store.db.datasource | db 模式数据源类型 | dbcp、druid、hikari;无默认值,store.mode=db 时必须指定 | |
store.db.dbType | db 模式数据库类型 | mysql、oracle、db2、sqlserver、sybaee、h2、sqlite、access、postgresql、oceanbase;无默认值,store.mode=db 时必须指定。 | |
store.db.driverClassName | db 模式数据库驱动 | store.mode=db 时必须指定 | |
store.db.url | db 模式数据库 url | store.mode=db 时必须指定,在使用 mysql 作为数据源时,建议在连接参数中加上 rewriteBatchedStatements=true (详细原因请阅读附录 7) | |
store.db.user | db 模式数据库账户 | store.mode=db 时必须指定 | |
store.db.password | db 模式数据库账户密码 | store.mode=db 时必须指定 | |
store.db.minConn | db 模式数据库初始连接数 | 默认 1 | |
store.db.maxConn | db 模式数据库最大连接数 | 默认 20 | |
store.db.maxWait | db 模式获取连接时最大等待时间 | 默认 5000,单位毫秒 | |
store.db.globalTable | db 模式全局事务表名 | 默认 global_table | |
store.db.branchTable | db 模式分支事务表名 | 默认 branch_table | |
store.db.lockTable | db 模式全局锁表名 | 默认 lock_table | |
store.db.queryLimit | db 模式查询全局事务一次的最大条数 | 默认 100 | |
store.db.distributedLockTable | db 模式 Sever 端事务管理全局锁存储表名 | 默认 distributed_lock,多 Sever 集群下保证同时只有一个 Sever 处理提交或回滚 | 1.5.1 版本新增 |
store.redis.mode | redis 模式 | 默认 single,可选 sentinel | 1.4.2 版本新增 sentinel 模式 |
store.redis.single.host | 单机模式下 redis 的 host,兼容 1.4.2 之前的版本,该配置为空时选取 store.redis.host 作为配置项 | 1.4.2 版本新增 | |
store.redis.single.port | 单机模式下 redis 的 port,兼容 1.4.2 之前的版本,该配置为空时选取 store.redis.port 作为配置项 | 1.4.2 版本新增 | |
store.redis.sentinel.masterName | sentinel 模式下 redis 的主库名称 | 1.4.2 版本新增 | |
store.redis.sentinel.sentinelHosts | sentinel 模式下 sentinel 的 hosts | 多 hosts 以逗号分隔 | 1.4.2 版本新增 |
store.redis.host | redis 模式 ip | 默认 127.0.0.1 | 1.4.2 版本弃用 |
store.redis.port | redis 模式端口 | 默认 6379 | 1.4.2 版本弃用 |
store.redis.maxConn | redis 模式最大连接数 | 默认 10 | |
store.redis.minConn | redis 模式最小连接数 | 默认 1 | |
store.redis.database | redis 模式默认库 | 默认 0 | |
store.redis.password | redis 模式密码(无可不填) | 默认 null | |
store.redis.queryLimit | redis 模式一次查询最大条数 | 默认 100 | |
store.redis.type | redis 模式主要使用的方式: lua, pippline | pippline | |
metrics.enabled | 是否启用 Metrics | 默认 true 开启,在 False 状态下,所有与 Metrics 相关的组件将不会被初始化,使得性能损耗最低 | |
metrics.registryType | 指标注册器类型 | Metrics 使用的指标注册器类型,默认为内置的 compact(简易)实现,这个实现中的 Meter 仅使用有限内存计数,性能高足够满足大多数场景;目前只能设置一个指标注册器实现 | |
metrics.exporterList | 指标结果 Measurement 数据输出器列表 | 默认 prometheus,多个输出器使用英文逗号分割,例如"prometheus,jmx",目前仅实现了对接 prometheus 的输出器 | |
metrics.exporterPrometheusPort | prometheus 输出器 Client 端口号 | 默认 9898 |
client 端
key | desc | remark | change record |
seata.enabled | 是否开启 spring-boot 自动装配 | true、false,(SSBS)专有配置,默认 true(附录 4) | |
seata.enableAutoDataSourceProxy=true | 是否开启数据源自动代理 | true、false,seata-spring-boot-starter(SSBS)专有配置,SSBS 默认会开启数据源自动代理,可通过该配置项关闭. | |
seata.useJdkProxy=false | 是否使用 JDK 代理作为数据源自动代理的实现方式 | true、false,(SSBS)专有配置,默认 false,采用 CGLIB 作为数据源自动代理的实现方式 | |
transport.enableClientBatchSendRequest | 客户端事务消息请求是否批量合并发送 | 默认 true,false 单条发送 | |
transport.enableTmClientChannelCheckFailFast | 客户端 TM 快速失败检查 | 默认 true,false 不检测 | |
transport.enableRmClientChannelCheckFailFast | 客户端 RM 快速失败检查 | 默认 true,false 不检测 | |
client.log.exceptionRate | 日志异常输出概率 | 默认 100,目前用于 undo 回滚失败时异常堆栈输出,百分之一的概率输出,回滚失败基本是脏数据,无需输出堆栈占用硬盘空间 | |
service.vgroupMapping.my_test_tx_group | 事务群组(附录 1) | my_test_tx_group 为分组,配置项值为 TC 集群名 | |
service.default.grouplist | TC 服务列表(附录 2) | 仅注册中心为 file 时使用 | |
service.disableGlobalTransaction | 全局事务开关 | 默认 false。false 为开启,true 为关闭 | |
client.tm.degradeCheck | 降级开关 | 默认 false。业务侧根据连续错误数自动降级不走 seata 事务(详细介绍请阅读附录 6) | |
client.tm.degradeCheckAllowTimes | 升降级达标阈值 | 默认 10 | |
client.tm.degradeCheckPeriod | 服务自检周期 | 默认 2000,单位 ms.每 2 秒进行一次服务自检,来决定 | |
client.rm.reportSuccessEnable | 是否上报一阶段成功 | true、false,从 1.1.0 版本开始,默认 false.true 用于保持分支事务生命周期记录完整,false 可提高不少性能 | |
client.rm.asyncCommitBufferLimit | 异步提交缓存队列长度 | 默认 10000。 二阶段提交成功,RM 异步清理 undo 队列 | |
client.rm.lock.retryInterval | 校验或占用全局锁重试间隔 | 默认 10,单位毫秒 | |
client.rm.lock.retryTimes | 校验或占用全局锁重试次数 | 默认 30 | |
client.rm.lock.retryPolicyBranchRollbackOnConflict | 分支事务与其它全局回滚事务冲突时锁策略 | 默认 true,优先释放本地锁让回滚成功 | |
client.rm.reportRetryCount | 一阶段结果上报 TC 重试次数 | 默认 5 次 | 1.4.1 版本新增 |
client.rm.tableMetaCheckEnable | 自动刷新缓存中的表结构 | 默认 false | 1.5.1 版本新增 |
client.rm.tableMetaCheckerInterval | 定时刷新缓存中表结构间隔时间 | 默认 60 秒 | |
client.rm.sagaBranchRegisterEnable | 是否开启 saga 分支注册 | Saga 模式中分支状态存储在状态机本地数据库中,可通过状态机进行提交或回滚,为提高性能可考虑不用向 TC 注册 Saga 分支,但需考虑状态机的可用性,默认 false | |
client.rm.sagaJsonParser | saga 模式中数据序列化方式 | 默认 fastjson,可选 jackson | 1.5.1 版本新增 |
client.rm.tccActionInterceptorOrder | tcc 拦截器顺序 | 默认 Ordered.HIGHEST_PRECEDENCE + 1000,保证拦截器在本地事务拦截器之前执行,也可自定义 tcc 和业务开发的拦截器执行顺序 | 1.5.1 版本新增 |
client.rm.applicationDataLimitCheck | 客户端应用数据是否开启限制 | 默认 false | |
client.rm.applicationDataLimit | 客户端应用数据上报限制 | 默认 64000 | |
client.tm.commitRetryCount | 一阶段全局提交结果上报 TC 重试次数 | 默认 1 次,建议大于 1 | |
client.tm.rollbackRetryCount | 一阶段全局回滚结果上报 TC 重试次数 | 默认 1 次,建议大于 1 | |
client.tm.defaultGlobalTransactionTimeout | 全局事务超时时间 | 默认 60 秒,TM 检测到分支事务超时或 TC 检测到 TM 未做二阶段上报超时后,发起对分支事务的回滚 | 1.4.0 版本新增 |
client.tm.interceptorOrder | TM 全局事务拦截器顺序 | 默认 Ordered.HIGHEST_PRECEDENCE + 1000,保证拦截器在本地事务拦截器之前执行,也可自定义全局事务和业务开发的拦截器执行顺序 | 1.5.1 版本新增 |
client.undo.dataValidation | 二阶段回滚镜像校验 | 默认 true 开启,false 关闭 | |
client.undo.logSerialization | undo 序列化方式 | 默认 jackson | |
client.undo.logTable | 自定义 undo 表名 | 默认 undo_log | |
client.undo.onlyCareUpdateColumns | 只生成被更新列的镜像 | 默认 true | |
client.undo.compress.enable | undo log 压缩开关 | 默认 true | 1.4.1 版本新增 |
client.undo.compress.type | undo log 压缩算法 | 默认 zip,可选 NONE(不压缩)、GZIP、ZIP、SEVENZ、BZIP2、LZ4、DEFLATER、ZSTD | 1.4.1 版本新增 |
client.undo.compress.threshold | undo log 压缩阈值 | 默认值 64k,压缩开关开启且 undo log 大小超过阈值时才进行压缩 | 1.4.1 版本新增 |
client.rm.sqlParserType | sql 解析类型 | 默认 druid,可选 antlr |
4.Seata新手部署指南
- client
存放client端sql脚本 (包含 undo_log表) ,参数配置
- config-center
各个配置中心参数导入脚本,config.txt(包含server和client,原名nacos-config.txt)为通用参数文件
- server
数据库官网地址: https://github.com/apache/incubator-seata/tree/2.x/script

5.mysql8.0数据库里面建库+建表
使用seata前,需根据官网要求创建默认库、表
建库—创建seata库
create database seata;
use seata;
建表—在seata库创建表
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(`xid` VARCHAR(128) NOT NULL,`transaction_id` BIGINT,`status` TINYINT NOT NULL,`application_id` VARCHAR(32),`transaction_service_group` VARCHAR(32),`transaction_name` VARCHAR(128),`timeout` INT,`begin_time` BIGINT,`application_data` VARCHAR(2000),`gmt_create` DATETIME,`gmt_modified` DATETIME,PRIMARY KEY (`xid`),KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(`branch_id` BIGINT NOT NULL,`xid` VARCHAR(128) NOT NULL,`transaction_id` BIGINT,`resource_group_id` VARCHAR(32),`resource_id` VARCHAR(256),`branch_type` VARCHAR(8),`status` TINYINT,`client_id` VARCHAR(64),`application_data` VARCHAR(2000),`gmt_create` DATETIME(6),`gmt_modified` DATETIME(6),PRIMARY KEY (`branch_id`),KEY `idx_xid` (`xid`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(`row_key` VARCHAR(128) NOT NULL,`xid` VARCHAR(128),`transaction_id` BIGINT,`branch_id` BIGINT NOT NULL,`resource_id` VARCHAR(256),`table_name` VARCHAR(32),`pk` VARCHAR(36),`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',`gmt_create` DATETIME,`gmt_modified` DATETIME,PRIMARY KEY (`row_key`),KEY `idx_status` (`status`),KEY `idx_branch_id` (`branch_id`),KEY `idx_xid` (`xid`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;CREATE TABLE IF NOT EXISTS `distributed_lock`
(`lock_key` CHAR(20) NOT NULL,`lock_value` VARCHAR(20) NOT NULL,`expire` BIGINT,primary key (`lock_key`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
运行结果

6.更改配置
参考模版配置:application.example.yml
seata支持的参数配置:nacos, consul, apollo, zk, etcd3seata支持的注册中心:support: nacos, eureka, redis, zk, consul, etcd3, sofaseata存储方式:file 、 db 、 redis 、 raftsecurity.ignore忽略的静态资源: 指定后缀 css、js、png、jpeg等
seata:config:# support: nacos, consul, apollo, zk, etcd3type: fileregistry:# support: nacos, eureka, redis, zk, consul, etcd3, sofatype: filestore:# support: file 、 db 、 redis 、 raftmode: file# server:# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
# Copyright 1999-2019 Seata.io Group.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.server:port: 7091spring:application:name: seata-serverlogging:config: classpath:logback-spring.xmlfile:path: ${log.home:${user.home}/logs/seata}extend:logstash-appender:destination: 127.0.0.1:4560kafka-appender:bootstrap-servers: 127.0.0.1:9092topic: logback_to_logstashconsole:user:username: seatapassword: seataseata:config:type: nacosnacos:server-addr: 127.0.0.1:8848namespace:group: SEATA_GROUP #后续自己在nacos里面新建,不想新建SEATA_GROUP,就写DEFAULT_GROUPusername: nacospassword: nacosregistry:type: nacosnacos:application: seata-serverserver-addr: 127.0.0.1:8848group: SEATA_GROUP #后续自己在nacos里面新建,不想新建SEATA_GROUP,就写DEFAULT_GROUPnamespace:cluster: defaultusername: nacospassword: nacos store:mode: dbdb:datasource: druiddb-type: mysqldriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueuser: rootpassword: 123isokmin-conn: 10max-conn: 100global-table: global_tablebranch-table: branch_tablelock-table: lock_tabledistributed-lock-table: distributed_lockquery-limit: 1000max-wait: 5000# server:# service-port: 8091 #If not configured, the default is '${server.port} + 1000'security:secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017tokenValidityInMilliseconds: 1800000ignore:urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**
7.先启动Nacos2.2.3端口号8848
在nacos-server2.2.3\bin下执行cmd启动命令:startup.cmd -m standalone访问地址: http://localhost:8848/nacos默认账号密码:nacos
8.再启动seata-server-2.0.0
在seata-server-2.0.0\bin下执行cmd启动命令: seata-server.bat访问地址: http://localhost:7091默认账号密码:seata


七、Seata案例实战-数据库和表准备(AT)
备注:需先启动nacos后启动seata成功
分布式事务案例—业务说明
这里我们创建三个服务,一个订单服务,一个库存服务,一个账户服务。当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。

1.创建3个业务数据库DATABASE
- seata order:存储订单的数据库;
- seata storage:存储库存的数据库;
- seata account:存储账户信息的数据库;
建库SQL
CREATE DATABASE seata_order;
CREATE DATABASE seata_storage;
CREATE DATABASE seata_account;
2. 按照上述3库分别建对应的undo log回滚日志表
订单-库存-账户3个库下都需要建各自的undo log回滚日志表
undo_log建表sql
AT模式专用,其他模式不需要
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',`xid` VARCHAR(128) 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(6) NOT NULL COMMENT 'create datetime',`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);

3.按照上述3库分别建对应业务表
- t_order脚本SQL
- t_account脚本SQL
- t_storage脚本SQL
t_order脚本SQL
CREATE TABLE t_order(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`product_id` BIGINT(11)DEFAULT NULL COMMENT '产品id',
`count` INT(11) DEFAULT NULL COMMENT '数量',
`money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
`status` INT(1) DEFAULT NULL COMMENT '订单状态: 0:创建中; 1:已完结'
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
SELECT * FROM t_order;
t_account脚本SQL
CREATE TABLE t_account(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
`used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用账户余额',
`residue` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度'
)ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;INSERT INTO t_account(`id`,`user_id`,`total`,`used`,`residue`)VALUES('1','1','1000','0','1000');SELECT * FROM t_account;
t_storage脚本SQL
CREATE TABLE t_storage(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
`total` INT(11) DEFAULT NULL COMMENT '总库存',
`used` INT(11) DEFAULT NULL COMMENT '已用库存',
`residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;INSERT INTO t_storage(`id`,`product_id`,`total`,`used`,`residue`)VALUES('1','1','100','0','100');SELECT * FROM t_storage;
最终SQL(全)
建seata_order库+建t_order表+undo_log表建seata_storage库+建t_storage 表+undo_log表建seata_account库+建t_account 表+undo_log表
建seata_order库+建t_order表+undo_log表
#orderCREATE DATABASE seata_order;USE seata_order; CREATE TABLE t_order(`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',`product_id` BIGINT(11)DEFAULT NULL COMMENT '产品id',`count` INT(11) DEFAULT NULL COMMENT '数量',`money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',`status` INT(1) DEFAULT NULL COMMENT '订单状态: 0:创建中; 1:已完结')ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; SELECT * FROM t_order;-- for AT mode you must to init this sql for you business database. the seata server not need it.CREATE TABLE IF NOT EXISTS `undo_log`
(`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',`xid` VARCHAR(128) 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(6) NOT NULL COMMENT 'create datetime',`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
建seata_storage库+建t_storage 表+undo_log表
#storage
CREATE DATABASE seata_storage;USE seata_storage;CREATE TABLE t_storage(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
`total` INT(11) DEFAULT NULL COMMENT '总库存',
`used` INT(11) DEFAULT NULL COMMENT '已用库存',
`residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;INSERT INTO t_storage(`id`,`product_id`,`total`,`used`,`residue`)VALUES('1','1','100','0','100');SELECT * FROM t_storage;-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',`xid` VARCHAR(128) 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(6) NOT NULL COMMENT 'create datetime',`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
建seata_account库+建t_account 表+undo_log表
#accountcreate database seata_account;use seata_account;CREATE TABLE t_account(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`total` DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
`used` DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',
`residue` DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度')ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;INSERT INTO t_account(`id`,`user_id`,`total`,`used`,`residue`)VALUES('1','1','1000','0','1000');SELECT * FROM t_account;-- for AT mode you must to init this sql for you business database. the seata server not need it.CREATE TABLE IF NOT EXISTS `undo_log`
(`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',`xid` VARCHAR(128) 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(6) NOT NULL COMMENT 'create datetime',`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
最终效果:

八、Seata系例实战-微服务编码落地实现(AT)
业务需求1.Mybaits一键生成2.修改公共cloud-api-commons新增库存和账户两个Feign服务接口3.新建订单Order微服务4.新建库存Storage微服务5.新建账户Account微服务
1.Mybaits一键生成
利用mybaitis 根据表生成对象实体类、mapper、mapper.xml
config.properties
分别执行seata_order、seata_storage、seata_account
# seata_order
#jdbc.driverClass = com.mysql.cj.jdbc.Driver
#jdbc.url = jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
#jdbc.user = root
#jdbc.password =123456# seata_storage
#jdbc.driverClass = com.mysql.cj.jdbc.Driver
#jdbc.url = jdbc:mysql://localhost:3306/seata_storage?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
#jdbc.user = root
#jdbc.password =123456# seata_account
#jdbc.driverClass = com.mysql.cj.jdbc.Driver
#jdbc.url = jdbc:mysql://localhost:3306/seata_account?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
#jdbc.user = root
#jdbc.password =123456
generatorConfig.xml
分别进行生成 t_orde、 t_storage、t_account
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfigurationPUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN""http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"><generatorConfiguration><properties resource="config.properties"/><context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat"><property name="beginningDelimiter" value="`"/><property name="endingDelimiter" value="`"/><plugin type="tk.mybatis.mapper.generator.MapperPlugin"><property name="mappers" value="tk.mybatis.mapper.common.Mapper"/><property name="caseSensitive" value="true"/></plugin><jdbcConnection driverClass="${jdbc.driverClass}"connectionURL="${jdbc.url}"userId="${jdbc.user}"password="${jdbc.password}"></jdbcConnection><javaModelGenerator targetPackage="${package.name}.entities" targetProject="src/main/java"/><sqlMapGenerator targetPackage="${package.name}.mapper" targetProject="src/main/java"/><javaClientGenerator targetPackage="${package.name}.mapper" targetProject="src/main/java" type="XMLMAPPER"/><table tableName="t_pay" domainObjectName="Pay"><generatedKey column="id" sqlStatement="JDBC"/></table><!-- seata_order --><!--<table tableName="t_order" domainObjectName="Order"><generatedKey column="id" sqlStatement="JDBC"/></table>--><!--seata_storage--><!--<table tableName="t_storage" domainObjectName="Storage"><generatedKey column="id" sqlStatement="JDBC"/></table>--><!--seata_account--><!--<table tableName="t_account" domainObjectName="Account"><generatedKey column="id" sqlStatement="JDBC"/></table>--></context>
</generatorConfiguration>
双击执行一键生成

2.修改公共cloud-api-commons新增库存和账户两个Feign服务接口
package com.atguigu.cloud.apis;import com.atguigu.cloud.resp.ResultData;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;@FeignClient(value = "seata-storage-service")
public interface StorageFeignApi
{//扣减库存@PostMapping(value = "/storage/decrease")ResultData decrease(@RequestParam("productId")Long productId,@RequestParam("count") Integer count);
}
package com.atguigu.cloud.apis;
import com.atguigu.cloud.resp.ResultData;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;@FeignClient(value = "seata-account-service")
public interface AccountFeignApi
{//扣减账号余额@PostMapping("/account/decrease")ResultData decrease(@RequestParam("userId")Long userId,@RequestParam("money") Long money);
}
3.新建订单Order微服务
(1).建Module(2).改POM(3).写YML(4).主启动类(5).业务类
(1).建Module
新建子模块 seata-order-service2001
(2).改POM
主要依赖是:nacos、seata、openfeign、loadbalancer、以及(cloud-api-commons)公共类的依赖 ,剩余的为springboot基础依赖、druid连接池等。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>cloud2024</artifactId><groupId>com.atguigu.cloud</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>seata-order-service2001</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- nacos 服务注册与发现--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--alibaba-seata 分布式事务--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId></dependency><!--openfeign 模块调用--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--loadbalancer 需要支持负载均衡--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!--cloud-api-commons 公共类--><dependency><groupId>com.atguigu.cloud</groupId><artifactId>cloud-api-commons</artifactId><version>1.0-SNAPSHOT</version></dependency><!--web + actuator--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--SpringBoot集成druid数据库连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency><!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html --><dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId></dependency><!--mybatis和springboot整合--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!--Mysql数据库驱动8 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--persistence--><dependency><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId></dependency><!--通用Mapper4--><dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency><!-- fastjson2 --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version><scope>provided</scope></dependency><!--test--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
(3).写YML
默认配置、nacos配置、数据库配置、mybatis、seata、日志配置
server:port: 2001
spring:application:name: seata-order-servicecloud:nacos:discovery:server-addr: localhost:8848 #nacos服务注册中心地址
# ==========applicationName + druid-mysql8 driver===================datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueusername: rootpassword: 123isok
# ========================mybatis===================
mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.atguigu.cloud.entitiesconfiguration:map-underscore-to-camel-case: true #可以将数据库中的带下划线字段映射到实体类的驼峰命名属性上。
# ========================seata===================
seata:registry:type: nacosnacos:server-addr: 127.0.0.1:8848namespace: ""group: SEATA_GROUPapplication: seata-servertx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称,可多个service:vgroup-mapping:default_tx_group: default # 事务组与TC服务集群的映射关系 default_tx_group要与tx-service-group参数一致data-source-proxy-mode: AT #可写可不写默认就是AT模式logging:level:io:seata: info


public void afterPropertiesSet() throws Exception {if (0 == this.vgroupMapping.size()) {this.vgroupMapping.put("default_tx_group", "default");this.vgroupMapping.put("my_test_tx_group", "default");}if (0 == this.grouplist.size()) {this.grouplist.put("default", "127.0.0.1:8091");}
}
seata:registry:type: nacosnacos:server-addr: 127.0.0.1:8848namespace: ""group: SEATA_GROUPapplication: seata-servertx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称,可多个service:vgroup-mapping:default_tx_group: default # 事务组与TC服务集群的映射关系 default_tx_group要与tx-service-group参数一致data-source-proxy-mode: AT #可写可不写默认就是AT模式
详细过度版(了解即可,太详细也不好维护)
#seata:
# registry: # seata注册配置
# type: nacos # seata注册类型
# nacos:
# application: seata-server #seata应用名称
# server-addr: 127.0.0.1:8848
# namespace: ""
# group: SEATA_GROUP
# cluster: default
# config: # seata配置抓取
# nacos:
# server-addr: 127.0.0.1:8848
# namespace: ""
# group: SEATA_GROUP
# username: nacos
# password: nacos
# tx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称
# service:
# vgroup-mapping:
# default_tx_group: default # 事务群组的映射配置关系
# data-source-proxy-mode: AT
# application-id: seata-server
(4).主启动类
package com.atguigu.cloud;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import tk.mybatis.spring.annotation.MapperScan;@SpringBootApplication
@MapperScan("com.atguigu.cloud.mapper")//import tk.mybatis.spring.annotation.MapperScan;
@EnableDiscoveryClient//服务器注册与发现
@EnableFeignClients//注解添加到主类或配置类上,可以启用Feign客户端的自动发现和创建。Spring Cloud会自动扫描指定包及其子包中的所有标记了@FeignClient的接口,并为每个接口创建一个动态代理实现,该实现会根据配置的参数和方法调用,自动构造HTTP请求
public class SeataOrderMainApp2001
{public static void main(String[] args) {SpringApplication.run(SeataOrderMainApp2001.class,args);}
}
SpringBootApplication是一个组合注解,主要用于标记Spring Boot的主配置类,用于启动Spring Boot应用程序。它由以下三个注解组成:
@SpringBootConfiguration:这个注解相当于@Configuration,表明该类是一个配置类,用于定义Bean和配置Spring容器 12。@EnableAutoConfiguration:开启自动配置功能,根据项目中的依赖自动配置Spring应用,如数据库连接、Web服务器等,减少手动配置的工作量 12。 @ComponentScan:组件扫描器,自动扫描当前包及其子包中带有特定注解(如@Component、@Service、@Repository、@Controller等)的类,并将其注册为Spring Bean
(5).业务类
entitiesOrderMapperService接口及实现Controller
1).entities
Order实体类实现Serializable接口添加注解@ToString
package com.atguigu.cloud.entities;import lombok.ToString;
import javax.persistence.*;
import java.io.Serializable;/*** 表名:t_order
*/
@Table(name = "t_order")
@ToString
public class Order implements Serializable {@Id@GeneratedValue(generator = "JDBC")private Long id;/*** 用户id*/@Column(name = "user_id")private Long userId;/*** 产品id*/@Column(name = "product_id")private Long productId;/*** 数量*/private Integer count;/*** 金额*/private Long money;/*** 订单状态: 0:创建中; 1:已完结*/private Integer status;/*** @return id*/public Long getId() {return id;}/*** @param id*/public void setId(Long id) {this.id = id;}/*** 获取用户id** @return userId - 用户id*/public Long getUserId() {return userId;}/*** 设置用户id** @param userId 用户id*/public void setUserId(Long userId) {this.userId = userId;}/*** 获取产品id** @return productId - 产品id*/public Long getProductId() {return productId;}/*** 设置产品id** @param productId 产品id*/public void setProductId(Long productId) {this.productId = productId;}/*** 获取数量** @return count - 数量*/public Integer getCount() {return count;}/*** 设置数量** @param count 数量*/public void setCount(Integer count) {this.count = count;}/*** 获取金额** @return money - 金额*/public Long getMoney() {return money;}/*** 设置金额** @param money 金额*/public void setMoney(Long money) {this.money = money;}/*** 获取订单状态: 0:创建中; 1:已完结** @return status - 订单状态: 0:创建中; 1:已完结*/public Integer getStatus() {return status;}/*** 设置订单状态: 0:创建中; 1:已完结** @param status 订单状态: 0:创建中; 1:已完结*/public void setStatus(Integer status) {this.status = status;}
}
2).OrderMapper
package com.atguigu.cloud.service;
import com.atguigu.cloud.entities.Order;
import tk.mybatis.mapper.common.Mapper;
public interface OrderMapper extends Mapper<Order> {}
resources文件夹下新建mapper文件夹后添加
OrderMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.cloud.mapper.OrderMapper"><resultMap id="BaseResultMap" type="com.atguigu.cloud.entities.Order"><!--WARNING - @mbg.generated--><id column="id" jdbcType="BIGINT" property="id" /><result column="user_id" jdbcType="BIGINT" property="userId" /><result column="product_id" jdbcType="BIGINT" property="productId" /><result column="count" jdbcType="INTEGER" property="count" /><result column="money" jdbcType="DECIMAL" property="money" /><result column="status" jdbcType="INTEGER" property="status" /></resultMap>
</mapper>
3).Service接口及实现
OrderService
package com.atguigu.cloud.service;
import com.atguigu.cloud.entities.Order;public interface OrderService {/*** 创建订单* @param order*/void create(Order order);
}
OrderServiceImpl(重点)
package com.atguigu.cloud.service.impl;import com.atguigu.cloud.apis.AccountFeignApi;
import com.atguigu.cloud.apis.StorageFeignApi;
import com.atguigu.cloud.entities.Order;
import com.atguigu.cloud.mapper.OrderMapper;
import com.atguigu.cloud.service.OrderService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;
/*** 下订单->减库存->扣余额->改(订单)状态*/
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {@Resourceprivate OrderMapper orderMapper;@Resource//订单微服务通过OpenFeign去调用库存微服务private StorageFeignApi storageFeignApi;@Resource//订单微服务通过OpenFeign去调用账户微服务private AccountFeignApi accountFeignApi;@Override@GlobalTransactional(name = "zzyy-create-order",rollbackFor = Exception.class)//AT
// @GlobalTransactional@Transactional(rollbackFor = Exception.class)//XApublic void create(Order order) {//xid 全局事务id的检查String xid = RootContext.getXID();//1.新建订单log.info("===================================开始新建订单 xid_order:"+xid);//订单状态status 0:创建中;1:已完结order.setStatus(0);int result = orderMapper.insert(order);//插入订单成功后获得插入mysql的实体对象Order orderFromDB=null;if(result>0){//从mysql查询刚插入的记录orderFromDB = orderMapper.selectOne(order);//orderFromDB = orderMapper.selectByPrimaryKey(order.getId());log.info("-------->新建订单成功,orderFromDB info:"+orderFromDB);System.out.println();//2.扣减库存log.info("--------->订单微服务开始调用Stroage库存,做抵扣count");storageFeignApi.decrease(orderFromDB.getProductId(),orderFromDB.getCount());log.info("-------->订单微服务结束调用Storage库存,做扣减完成");System.out.println();//3.扣减账号余款log.info("--------->订单为服务器开始调用Account账号,做扣减money");accountFeignApi.decrease(orderFromDB.getUserId(),orderFromDB.getMoney());log.info("--------->订单为服务器结束调用Account账号,做扣减完成");//4.修改订单状态//订单状态status 0:创建中;1:已完结log.info("--------->修改订单状态");orderFromDB.setStatus(1);//创建查询条件Example whereCondition = new Example(Order.class);Example.Criteria criteria = whereCondition.createCriteria();criteria.andEqualTo("userId",orderFromDB.getUserId());criteria.andEqualTo("status",0);//更新操作:根据Example特定的查询条件,找到相应的记录,并基于传入的实体对象来更新记录中的字段值。int updateResult = orderMapper.updateByExampleSelective(orderFromDB, whereCondition);log.info("--------->修改订单状态完成 "+updateResult);log.info("---------> orderFromDB info: "+orderFromDB);}System.out.println();log.info("====================================结束新建订单 xdi_order: "+xid);}
}
4).Controller
package com.atguigu.cloud.controller;
import com.atguigu.cloud.entities.Order;
import com.atguigu.cloud.resp.ResultData;
import com.atguigu.cloud.service.OrderService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class OrderController {@Resourceprivate OrderService orderService;/*** 创建订单* @param order* @return*/@GetMapping("/order/create")public ResultData create(Order order){orderService.create(order);return ResultData.success(order);}
}
4.新建库存Storage微服务
(1).建Module(2).改POM(3).写YML(4).主启动类(5).业务类
(1).建Module
建子模块 seata-storage-service2002
(2).改POM
主要依赖是: nacos、seata、openfeign、loadbalancer、以及公共类 的依赖,剩余的为springboot基础依赖、druid连接池等。与子模块 seata-order-service2001 pom相同
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>cloud2024</artifactId><groupId>com.atguigu.cloud</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>seata-storage-service2002</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- nacos --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--alibaba-seata--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId></dependency><!--openfeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--loadbalancer--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!--cloud_commons_utils--><dependency><groupId>com.atguigu.cloud</groupId><artifactId>cloud-api-commons</artifactId><version>1.0-SNAPSHOT</version></dependency><!--web + actuator--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--SpringBoot集成druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency><!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html --><dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId></dependency><!--mybatis和springboot整合--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!--Mysql数据库驱动8 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--persistence--><dependency><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId></dependency><!--通用Mapper4--><dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency><!-- fastjson2 --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version><scope>provided</scope></dependency><!--test--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
(3).写YML
spring:application:name: seata-storage-service
datasource:druid:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata_storage?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueusername: rootpassword: 123isok
server:port: 2002
spring:application:name: seata-storage-servicecloud:nacos:discovery:server-addr: localhost:8848 #nacos服务注册中心地址# ==========applicationName + druid-mysql8 driver===================datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata_storage?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueusername: rootpassword: 123isok
# ========================mybatis===================
mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.atguigu.cloud.entitiesconfiguration:map-underscore-to-camel-case: true #可以将数据库中的带下划线字段映射到实体类的驼峰命名属性上。
# ========================seata===================
seata:registry:type: nacosnacos:server-addr: 127.0.0.1:8848namespace: ""group: SEATA_GROUPapplication: seata-servertx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称,可多个service:vgroup-mapping:default_tx_group: default # 事务组与TC服务集群的映射关系 default_tx_group要与tx-service-group参数一致data-source-proxy-mode: AT #可写可不写默认就是AT模式logging:level:io:seata: info
(4).主启动类
package com.atguigu.cloud;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import tk.mybatis.spring.annotation.MapperScan;@SpringBootApplication
@MapperScan("com.atguigu.cloud.mapper")//import tk.mybatis.spring.annotation.MapperScan;
@EnableDiscoveryClient//服务器注册与发现
@EnableFeignClients
public class SeataStorageMainApp2002
{public static void main(String[] args) {SpringApplication.run(SeataStorageMainApp2002.class,args);}
}
(5).业务类
通过mybatis_generator2024自动生成实体类entities、storageMapper1).entities2).StorageMapper3).Service接口及实现4).Controller
1).entities
Storage实体类实现Serializable接口添加注解@ToString
package com.atguigu.cloud.entities;import lombok.ToString;import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;/*** 表名:t_storage
*/
@Table(name = "t_storage")
@ToString
public class Storage implements Serializable{@Id@GeneratedValue(generator = "JDBC")private Long id;/*** 产品id*/@Column(name = "product_id")private Long productId;/*** 总库存*/private Integer total;/*** 已用库存*/private Integer used;/*** 剩余库存*/private Integer residue;/*** @return id*/public Long getId() {return id;}/*** @param id*/public void setId(Long id) {this.id = id;}/*** 获取产品id** @return productId - 产品id*/public Long getProductId() {return productId;}/*** 设置产品id** @param productId 产品id*/public void setProductId(Long productId) {this.productId = productId;}/*** 获取总库存** @return total - 总库存*/public Integer getTotal() {return total;}/*** 设置总库存** @param total 总库存*/public void setTotal(Integer total) {this.total = total;}/*** 获取已用库存** @return used - 已用库存*/public Integer getUsed() {return used;}/*** 设置已用库存** @param used 已用库存*/public void setUsed(Integer used) {this.used = used;}/*** 获取剩余库存** @return residue - 剩余库存*/public Integer getResidue() {return residue;}/*** 设置剩余库存** @param residue 剩余库存*/public void setResidue(Integer residue) {this.residue = residue;}
}
2).StorageMapper
StorageMapper
package com.atguigu.cloud.mapper;
import com.atguigu.cloud.entities.Storage;
import org.apache.ibatis.annotations.Param;
import tk.mybatis.mapper.common.Mapper;public interface StorageMapper extends Mapper<Storage> {/*** 扣减库存*/void decrease(@Param("productId")Long productId,@Param("count")Integer count);}
StorageMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.cloud.mapper.StorageMapper"><resultMap id="BaseResultMap" type="com.atguigu.cloud.entities.Storage"><!--WARNING - @mbg.generated--><id column="id" jdbcType="BIGINT" property="id" /><result column="product_id" jdbcType="BIGINT" property="productId" /><result column="total" jdbcType="INTEGER" property="total" /><result column="used" jdbcType="INTEGER" property="used" /><result column="residue" jdbcType="INTEGER" property="residue" /></resultMap><update id="decrease">UPDATEt_storageSETused = used + #{count},residue = residue - #{count}WHERE product_id = #{productId}</update></mapper>
3).Service接口及实现
package com.atguigu.cloud.service;public interface StorageService {/*** 扣减库存* @param productId* @param count*/void decrease(Long productId,Integer count);
}
package com.atguigu.cloud.mapper.impl;
import com.atguigu.cloud.mapper.StorageMapper;
import com.atguigu.cloud.service.StorageService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;@Service
@Slf4j
public class StorageServiceImpl implements StorageService {@Resourceprivate StorageMapper storageMapper;@Overridepublic void decrease(Long productId, Integer count) {log.info("------->storage-service中扣减库存开始");storageMapper.decrease(productId,count);log.info("------->storage-service中扣减库存成功");}
}
4).Controller
package com.atguigu.cloud.controller;import com.atguigu.cloud.resp.ResultData;
import com.atguigu.cloud.service.StorageService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class StorageController {@Resourceprivate StorageService storageService;@RequestMapping("/storage/decrease")public ResultData decrease(Long productId,Integer count){storageService.decrease(productId,count);return ResultData.success("扣减库存成功!");}
}
5.新建账户Account微服务
(1).建Module(2).改POM(3).写YML(4).主启动类(5).业务类
(1).建Module
建子模块 seata-account-service2003
(2).改POM
主要依赖是:nacos、seata、openfeign、loadbalancer、以及(cloud-api-commons)公共类的依赖,剩余的为springboot基础依赖、druid连接池等。与子模块 seata-order-service2001 pom相同
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>cloud2024</artifactId><groupId>com.atguigu.cloud</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>seata-account-service2003</artifactId><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- nacos --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--alibaba-seata--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId></dependency><!--openfeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--loadbalancer--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!--cloud_commons_utils--><dependency><groupId>com.atguigu.cloud</groupId><artifactId>cloud-api-commons</artifactId><version>1.0-SNAPSHOT</version></dependency><!--web + actuator--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--SpringBoot集成druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency><!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html --><dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId></dependency><!--mybatis和springboot整合--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!--Mysql数据库驱动8 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--persistence--><dependency><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId></dependency><!--通用Mapper4--><dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency><!-- fastjson2 --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version><scope>provided</scope></dependency><!--test--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
(3).写YML
spring:application:name: seata-account-service
datasource:druid:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata_account?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueusername: rootpassword: 123isok
server:port: 2003
spring:application:name: seata-account-servicecloud:nacos:discovery:server-addr: localhost:8848 #nacos服务注册中心地址# ==========applicationName + druid-mysql8 driver===================datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata_account?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueusername: rootpassword: 123isok
# ========================mybatis===================
mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.atguigu.cloud.entitiesconfiguration:map-underscore-to-camel-case: true #可以将数据库中的带下划线字段映射到实体类的驼峰命名属性上。
# ========================seata===================
seata:registry:type: nacosnacos:server-addr: 127.0.0.1:8848namespace: ""group: SEATA_GROUPapplication: seata-servertx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称,可多个service:vgroup-mapping:default_tx_group: default # 事务组与TC服务集群的映射关系 default_tx_group要与tx-service-group参数一致data-source-proxy-mode: AT #可写可不写默认就是AT模式logging:level:io:seata: info
(4).主启动类
package com.atguigu.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import tk.mybatis.spring.annotation.MapperScan;@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("com.atguigu.cloud.mapper") //import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
public class SeataAccountMainApp2003
{public static void main(String[] args) {SpringApplication.run(SeataAccountMainApp2003.class,args);}
}
(5).业务类
通过mybatis_generator2024自动生成实体类entities、accountMapper1).entities2).AccountMapper3).Service接口及实现4).Controller
1).entities
Account实体类
- 实现Serializable接口
- 添加注解@ToString
package com.atguigu.cloud.entities;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;/*** 表名:t_account
*/
@Table(name = "t_account")
@ToString
public class Account implements Serializable {/*** id*/@Id@GeneratedValue(generator = "JDBC")private Long id;/*** 用户id*/@Column(name = "user_id")private Long userId;/*** 总额度*/private Long total;/*** 已用账户余额*/private Long used;/*** 剩余可用额度*/private Long residue;/*** 获取id** @return id - id*/public Long getId() {return id;}/*** 设置id** @param id id*/public void setId(Long id) {this.id = id;}/*** 获取用户id** @return userId - 用户id*/public Long getUserId() {return userId;}/*** 设置用户id** @param userId 用户id*/public void setUserId(Long userId) {this.userId = userId;}/*** 获取总额度** @return total - 总额度*/public Long getTotal() {return total;}/*** 设置总额度** @param total 总额度*/public void setTotal(Long total) {this.total = total;}/*** 获取已用账户余额** @return used - 已用账户余额*/public Long getUsed() {return used;}/*** 设置已用账户余额** @param used 已用账户余额*/public void setUsed(Long used) {this.used = used;}/*** 获取剩余可用额度** @return residue - 剩余可用额度*/public Long getResidue() {return residue;}/*** 设置剩余可用额度** @param residue 剩余可用额度*/public void setResidue(Long residue) {this.residue = residue;}
}
2).AccountMapper
package com.atguigu.cloud.mapper;
import com.atguigu.cloud.entities.Account;
import org.apache.ibatis.annotations.Param;
import tk.mybatis.mapper.common.Mapper;public interface AccountMapper extends Mapper<Account> {/*** 本次消费金额*/void decrease(@Param("userId")Long userId,@Param("money") Long money);
}
resources文件夹下新建mapper文件夹后添加
AccountMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.cloud.mapper.AccountMapper"><resultMap id="BaseResultMap" type="com.atguigu.cloud.entities.Account"><!--WARNING - @mbg.generated--><id column="id" jdbcType="BIGINT" property="id" /><result column="user_id" jdbcType="BIGINT" property="userId" /><result column="total" jdbcType="DECIMAL" property="total" /><result column="used" jdbcType="DECIMAL" property="used" /><result column="residue" jdbcType="DECIMAL" property="residue" /></resultMap><update id="decrease">update t_accountsetresidue = residue - #{money},used = used + #{money}where user_id = #{userId}</update>
</mapper>
3).Service接口及实现
package com.atguigu.cloud.service;public interface AccountService {/*** 扣减账户余额* @param userId* @param money*/void decrease(Long userId, Long money);
}
package com.atguigu.cloud.service.impl;
import com.atguigu.cloud.mapper.AccountMapper;
import com.atguigu.cloud.service.AccountService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;@Service
@Slf4j
public class AccountServiceImpl implements AccountService
{@ResourceAccountMapper accountMapper;@Overridepublic void decrease(Long userId, Long money) {log.info("------->account-service中扣减账户余额开始");accountMapper.decrease(userId,money);//设置超时// myTimeOut();//设置异常// int age = 10/0; log.info("------->account-service中扣减账户余额结束");}/*** 模拟超时异常,全局事务回滚* openFeign 默认读取数据超时时间是60秒,设置65秒则颐一定超时*/private static void myTimeOut(){try{TimeUnit.SECONDS.sleep(65);}catch (InterruptedException e){e.printStackTrace();}}
}
4).Controller
package com.atguigu.cloud.controller;import com.atguigu.cloud.resp.ResultData;
import com.atguigu.cloud.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
public class AccountController
{@ResourceAccountService accountService;@RequestMapping("/account/decrease")public ResultData decrease(@RequestParam("userId")Long userId,@RequestParam("money")Long money){accountService.decrease(userId,money);return ResultData.success("扣减账户余额成功!");}
}
九、Seata案例实战-测试
服务启动情况数据库初始化情况1.正常下单2.超时异常出错,没有@GlobalTransactional3.超时异常解决,添加@GlobalTransactional
服务启动情况
- 启动Nacos
- 启动Seata
- 启动订单微服务2001
- 启动库存微服务2002
- 启动账户微服务2003
数据库初始化情况



1.正常下单
1).下订单->减库存->扣余额->改(订单)状态

2).此时我们没有在订单模块添加@GlobalTransactional
访问地址:
- http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
- 1号用户花费100块钱买了10个1号产品
3).正常下单,第1次
故障现象导致原因解决方案
故障现象


导致原因
springboot+springboot与springalibaba版本不兼容。参考官网对版本进行相应的调整springcloud alibaba 版本声明官网地址: 版本发布说明-阿里云Spring Cloud Alibaba官网
Spring Cloud Alibaba Version | Spring Cloud Version | Spring Boot Version |
2023.0.1.0* | Spring Cloud 2023.0.1 | 3.2.4 |
2023.0.0.0-RC1 | Spring Cloud 2023.0.0 | 3.2.0 |
Spring Cloud Alibaba Version | Sentinel Version | Nacos Version | RocketMQ Version | Seata Version |
2023.0.1.0 | 1.8.6 | 2.3.2 | 5.1.4 | 2.0.0 |
2023.0.0.0-RC1 | 1.8.6 | 2.3.0 | 5.1.4 | 2.0.0 |
<spring.boot.version>3.2.4</spring.boot.version>
<spring.cloud.version>2023.0.1</spring.cloud.version>
<spring.cloud.alibaba.version>2023.0.1.0</spring.cloud.alibaba.version>
4).正常下单,第2次

数据库情况



2.超时异常出错,没有@GlobalTransactional
1).添加超时方法
seata-account-service2003微服务中的AccountserviceImpl添加超时方法myTimeOut();
package com.atguigu.cloud.service.impl;import com.atguigu.cloud.mapper.AccountMapper;
import com.atguigu.cloud.service.AccountService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;@Service
@Slf4j
public class AccountServiceImpl implements AccountService
{@ResourceAccountMapper accountMapper;@Overridepublic void decrease(Long userId, Long money) {log.info("------->account-service中扣减账户余额开始");accountMapper.decrease(userId,money);myTimeOut();//设置超时// int age = 10/0;//设置异常log.info("------->account-service中扣减账户余额结束");}/*** 模拟超时异常,全局事务回滚* openFeign 默认读取数据超时时间是60秒,设置65秒则颐一定超时*/private static void myTimeOut(){try{TimeUnit.SECONDS.sleep(65);}catch (InterruptedException e){e.printStackTrace();}}
}
2).故障情况



3.超时异常解决,添加@GlobalTransactional
前提条件
AccountServiceImpl保留超时方法OrderServiceImpl添加@GlobalTransactional
@Override@GlobalTransactional(name = "zzyy-create-order",rollbackFor = Exception.class) //ATpublic void create(Order order)
{。。。。。。
}

查看Seata后台:
备注先运行地址,否则查看不到全局事务:http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
全局事务ID

tx-service-group: default_tx_group # 事务组,由它获得TC服务的集群名称,全集事务名称:zzyy-create-order@GlobalTransactional(name = " zzyy-create-order",rollbackFor = Exception.class)//AT
全局锁

globalTransactional全局事务id:TransactionalId,分支事务id:branchId。
数据回滚
下单后数据库3个库数据并没有任何改变,被回滚了

业务中

回滚后
order记录都添加不进来,全部回退。

全局事务,正常扣除操作
去除 以下设置的超时与异常方法,则全局是事务正常进行。myTimeOut();//设置超时// int age = 10/0;//设置异常
