欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 能源 > 13. springCloud AlibabaSeata处理分布式事务

13. springCloud AlibabaSeata处理分布式事务

2025/6/6 16:49:45 来源:https://blog.csdn.net/weixin_39671217/article/details/148340794  浏览:    关键词:13. springCloud AlibabaSeata处理分布式事务

目录

一、分布式事务面试题

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.对于分布式事务问题,你知道的解决方案有哪些?请你谈谈?

1.2PC(两阶段提交)
2.3PC(三阶段提交)
3.TCC方案
  • TCC(Try-Confirm-Cancel)又被称补偿事务
  • 类似2PC的柔性分布式解决方案,2PC改良版
4.LocalMessage本地消息表;
5.独立消息微服务+RabbitMQ/KafKa组件,实现可靠消息最终一致性方案;
6.最大努力通知方案;

二、分布式事务问题如何产生?请先看业务

上述面试问题都指向一个重要问题
一次业务操作需要 跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。
但是
关系型数据库提供的能力是基于 单机事务的,一旦遇到分布式事务场景,就需要通过更多其他技术手段来解决问题。

分布式事务之前:

  • 单机单库没这个问题
  • 表结构的关系从1:1->1:N -> N:N

分布式事务改变后:

单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,
业务操作需要调用三个服务来完成。
此时 每个服务自己内部的数据一致性由 本地 事务来保证,但是 全局 的数据一致性问题没法保证
结论:迫切希望提供一种分布式事务框架,解决微服务架构下的分布式事务问题。

三、Seata简介

Simple Extensible Autonomous Transaction Architecture 简单可扩展自治事务框架
Apache Seata(incubating) 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

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会对所管理数据库,统一进行数据回滚,保证数据的正确性。

官网及源码

官网地址: Apache Seata
Github地址: GitHub - apache/incubator-seata: :fire: Seata is an easy-to-use, high-performance, open source distributed transaction solution.

使用

本地@Transactional
全局@GlobalTransactional

Seata的分布式交易解决方案

(TC→TM→RM)分别什么意思
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

四、Seata工作流程简介:

分布式事务的理解:

  • 分布式事务是由一批分支事务组成的全局事务,通常分支事务只是本地事务。
  • 纵观整个分布式事务的管理,就是全局事务ID的传递和变更,要让开发者无感知。

Seata对分布式事务的协调和控制就是1+3

1+3:1个XID+TC+TM+RM
  • 1个XID :XID是全局事务的唯一标识,它可以在服务的调用链路中传递,绑定到服务的事务上下文中。
官网3个概念(重点)
  • TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
  • TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
对TC->TM->RM的理解:
  1. TC(Transaction Coordinator)事务协调器就是Seata,负责维护全局事务和分支事务的状态,驱动全局事务提交或回滚。
  2. TM(Transaction Manager) 事务管理器标注全局@GlobalTransactional启动入口动作的微服务模块(比如订单模块),它是事务的发起者,负责定义全局事务的范围,并根据TC维护的全局事务和分支事务状态,做出开始事务、提交事务、回滚事务的决议。
  3. RM(Resource Manager)资源管理器就是mysql数据库本身,可以是多个RM,负责管理分支事务上的资源,像TC注册分支事务,汇报分支事务状态,驱动分支事务的提交和回滚。

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

三个组件相互协作, TC以Seata 服务器(Server)形式独立部署,TM和RM则是以Seata Client的形式集成在微服务中运行 流程如下
1.TM(事务管理器) 向 TC (事务协调者)申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
2.XID 在微服务调用链路的上下文中传播;
3.RM(资源管理器) 向 TC (事务协调者)注册分支事务,将其纳入 XID 对应全局事务的管辖;
4.TM(事务管理器) 向 TC (事务协调者)发起针对 XID 的全局提交或回滚决议;
5.TC (事务协调者) 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
常用 @GlobalTransactional方式一:
一般控制3到5个本地事务,超过则使用方法嵌套的方式。
@GlobalTransactional
public void doBusiness() {// 本地事务serviceA.doSomething();// 本地事务serviceB.doSomethingElse();
}
常用 @GlobalTransactional方式二:
@GlobalTransactional
public void globalMethod() {// 调用另一个标记为@GlobalTransactional的方法nestedMethod();// 业务逻辑...
}
@GlobalTransactional
public void nestedMethod() {
// 业务逻辑...
}

五、各事务模式重点)

  • Seata AT 模式
  • Seata TCC 模式
  • Seata Saga 模式
  • Seata XA 模式
备注:AT模式因其简单易用和自动完成事务的特性,可能在一些基本的分布式事务场景中较为常用。然而,在复杂的业务逻辑或需要强一致性保证的场景下,TCC、SAGA或XA模式可能更受欢迎。

Seata AT 模式(常用)

AT 模式是 Seata 创新的一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,我们使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。
适用场景:
  • 适用场景:主要用于CRUD(创建、读取、更新、删除)操作较多的业务场景,尤其是当业务逻辑直接操作数据库,并且可以容忍短暂的数据不一致时。AT模式通过记录数据的前后镜像来实现撤销(回滚)操作,适合于大部分单纯依赖于单个数据库事务的微服务场景。
  • 优点:简单易用,不需要改动业务代码,自动完成分布式事务的提交和回滚。
  • 缺点:不适合跨多种存储资源的事务,且在高并发场景下性能可能受影响。
前提
  • 基于支持本地 ACID 事务的关系型数据库。
  • Java 应用,通过 JDBC 访问数据库。
整体机制
两阶段提交协议的演变:
  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。
整体机制的理解
一阶段过程;
一阶段,Seata 会拦截“业务 SQL”,
1  解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,
2  执行“业务 SQL”更新业务数据,在业务数据更新之后,
3  其保存成“after image”,最后生成行锁。
以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
二阶段异常回滚:
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。
回滚方式便是用undo_log中的“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,
如果两份数据完全一致就说明没有脏写,可以还原业务数据, 如果不一致就说明有脏写,出现脏写就需要转人工处理。
二阶段正常提交:
因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将 一阶段保存的快照数据和行锁删掉,完成数据清理即可。
写隔离
  • 一阶段本地事务提交前,需要确保先拿到 全局锁 。
  • 拿不到 全局锁 ,不能提交本地事务。
  • 拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。
以一个示例来说明:
两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。
tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的  全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的  全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待  全局锁 。
tx1 二阶段全局提交,释放  全局锁 。tx2 拿到  全局锁 提交本地事务。
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果 tx2 仍在等待该数据的  全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的  全局锁 等锁超时,放弃  全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。
因为整个过程  全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生  脏写 的问题。
读隔离
在数据库本地事务隔离级别  读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是  读未提交(Read Uncommitted) 。
如果应用在特定场景下,必需要求全局的  读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。
SELECT FOR UPDATE 语句的执行会申请  全局锁 ,如果  全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到  全局锁 拿到,即读取的相关数据是  已提交 的,才返回。
出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。
工作机制示例:
以一个示例来说明整个 AT 分支的工作过程。
业务表:product

Field

Type

Key

id
bigint(20)
PRI
name
varchar(100)
since
varchar(100)

AT 分支事务的业务逻辑:
update product set name = 'GTS' where name = 'TXC';

一阶段
过程:
  1. 解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = 'TXC')等相关的信息。
  2. 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
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。

二阶段-提交
  1. 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
  2. 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。
回滚日志表
UNDO_LOG Table:不同数据库在类型上会略有差别。
以 MySQL 为例:
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端口号8848
8.再启动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新手部署指南

​Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。
资源目录介绍
点击查看(或根据版本分支选择对应的资源目录) ​
  • client
存放client端sql脚本 (包含 undo_log表) ,参数配置
  • config-center
各个配置中心参数导入脚本,config.txt(包含server和client,原名nacos-config.txt)为通用参数文件
  • server
server端数据库脚本 (包含 lock_table、branch_table 与 global_table) 及各个容器配置
以下是client、config-center、server对应资源数据库地址
数据库官网地址: https://github.com/apache/incubator-seata/tree/2.x/script

5.mysql8.0数据库里面建库+建表

使用seata前,需根据官网要求创建默认库、表
建库—创建seata库
create database seata;
use seata;
建表—在seata库创建表
建表官网数据库地址: incubator-seata/script/server/db at develop · apache/incubator-seata · GitHub
官网MYSQL脚本内容
-- -------------------------------- 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.yml
参考模版配置:application.example.yml
application参数配置:
seata支持的参数配置:
nacos, consul, apollo, zk, etcd3
seata支持的注册中心:
support: nacos, eureka, redis, zk, consul, etcd3, sofa
seata存储方式:
file 、 db 、 redis 、 raft
security.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'
application.yml(nacos 与mysql参数配置全)
#  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
此时Nacos服务管理—服务列表已存在
表示seata微服务成功入驻进nacos

七、Seata案例实战-数据库和表准备(AT)

订单+库存+账号 3个业务数据库mysql准备
备注:需先启动nacos后启动seata成功

分布式事务案例—业务说明

这里我们创建三个服务,一个订单服务,一个库存服务,一个账户服务。
当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,
再通过远程调用账户服务来扣减用户账户里面的余额,
最后在订单服务中修改订单状态为已完成。该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
下订单 → 减库存  → 扣余额  → 改(订单)状态
首先,如何定义分布式事务?
我们说,分布式事务是由一批分支事务(Branch Transaction)组成的全局事务(Global Transaction),通常分支事务只是局部事务。

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回滚日志表
seata官网: incubator-seata/script/client/at/db/mysql.sql at 2.x · apache/incubator-seata · GitHub
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)

订单/库存/账户业务微服务Java开发准备
业务需求
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服务接口

StorageFeignApi
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);
}
AccountFeignApi
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
Namespace+Group+Datald三者关系?为什么这么设计?
对应说明
上图落地的对应源码(笔记最下面还有):io.seata.spring.boot.autoconfigure.properties.client.ServiceProperties
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");}
}
YML 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模式

YML seata部分参数配置详细版
详细过度版(了解即可,太详细也不好维护)
#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‌
@MapperScan 注解是 MyBatis-Spring-Boot-Starter 提供的一个注解,用于指定 MyBatis 映射器接口(Mapper Interfaces)的位置,以便框架能够在启动时自动扫描并注册这些接口。这使得开发者不需要单独为每个映射器接口编写 XML 配置文件,从而简化了配置。
@EnableDiscoveryClient 注解的作用是启用Spring Cloud的服务发现功能,它使得应用能够自动注册到服务注册中心(如Nacos),并能够发现其他服务。这个注解是Spring Cloud提供的,用于启动服务发现客户端,它会根据配置自动配置服务发现的相关组件。
@EnableFeignClients 注解添加到主类或配置类上,可以启用Feign客户端的自动发现和创建。Spring Cloud会自动扫描指定包及其子包中的所有标记了@FeignClient的接口,并为每个接口创建一个动态代理实现,该实现会根据配置的参数和方法调用,自动构造HTTP请求‌
(5).业务类
entities
OrderMapper
Service接口及实现
Controller
1).entities
通过mybatis_generator2024自动生成实体类entities、OrderMapper
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
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
yml配置与seata-order-service2001配置基本一致,小部分进行修改即可
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、storageMapper
1).entities
2).StorageMapper
3).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);}
resources文件夹下新建mapper文件夹后添加
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接口及实现
StorageService
package com.atguigu.cloud.service;public interface StorageService {/*** 扣减库存* @param productId* @param count*/void decrease(Long productId,Integer count);
}
StorageServiceImpl
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
yml配置与seata-order-service2001配置基本一致,小部分进行修改即可
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、accountMapper
1).entities
2).AccountMapper
3).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接口及实现
AccountService
package com.atguigu.cloud.service;public interface  AccountService {/*** 扣减账户余额* @param userId* @param money*/void decrease(Long userId, Long money);
}
AccountServiceImpl 
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.超时异常出错,没有@GlobalTransactional
3.超时异常解决,添加@GlobalTransactional

服务启动情况

  • 启动Nacos
  • 启动Seata
  • 启动订单微服务2001
  • 启动库存微服务2002
  • 启动账户微服务2003

数据库初始化情况

SELECT *  FROM  `seata_order`.`t_order`
SELECT * FROM `seata_storage`.`t_storage`
SELECT *  FROM  `seata_account`.`t_account`;

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官网
2023.x 分支
适配 Spring Boot 3.2,Spring Cloud 2023.x 版本及以上的 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 版本及其自身所适配的各组件对应版本如下表所示:
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
解决方案
根据alibaba官网给出的版本兼容进行调整
<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个扣减
订单新增1个库存扣减,账户扣减100

 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).故障情况
当库存和账户金额扣减后,订单状态并没有设置为已经完成,没有从零改为1
数据库情况
订单没有设置完成1,而库存与账户已扣除。

 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;//设置异常

总结

当该全局事务中,存在异常或者超时时,数据库的数据,将进行数据回滚。
当该全局事务中,不存在异常或者超时时,将正常对多个数据库进行增减并提交业务操作。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词