菜鸟的Spring Cloud Alibaba学习总结(三):Seata
说明
更新时间:2020/10/10 02:41,更新到了TC、TM、RM
本文主要对Spring Cloud Alibaba中的Seata进行学习与记录,偏向于实战,简单讲一下下面用到技术及版本,docker部署Seata1.3.0,Mysql采用8.0及以上,nacos采用1.3.1,重点讲一下Seata部署的方式,因为1.30版本跟0.9版本差距很大,代码不会过于详细,本文会持续更新,不断地扩充
注意:本文仅为记录学习轨迹,如有侵权,联系删除
一、概述
这里给出官网地址:http://seata.io/zh-cn/docs/overview/what-is-seata.html
Seata是什么
引用官网的话 ,Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
解决的问题
我们知道在普通的单体应用中,服务层里面的事务控制可以用@Transactional注解来实现事务的控制,但是如果是分布式的情况下,这个注解就不管用了,因为它们根本不在同一个服务中,这个时候就可以用Seata这个框架,就像官网所说的,致力于提供高性能和简单易用的分布式事务服务。
术语表
核心开发
关于其他的详细信息可以去官网直接看,这里重点讲一下开发,Seata的开发主要包括服务端和客户端的开发。下面直接进入实战,简单说一下技术选择和版本号
技术点 | 版本 |
---|---|
docker | / |
Seata | 1.3.0 |
Mysql | 8.0及以上 |
nacos | 1.3.2 |
二、Seata服务端开发
(1)前期准备
先去官网下载Seata1.3.0版,在进行docker部署之前,先了解一下Seata1.3.0版本的相关知识。
下载地址:http://seata.io/zh-cn/blog/download.html
如果下不了可以评论区留言,下载压缩包后解压即可,在conf里面有两个十分重要的文件,file.cof和registry.cof,怎么用后面会细讲
(2)数据库准备
先是数据库的准备,这里用的mysql,,点开上图的README-zh.md,里面有链接,可以下载SQL
浏览器进入链接后,进入db,复制里面mysql的语句
里面的sql语句这里给大家复制出来了
-- -------------------------------- 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_gmt_modified_status` (`gmt_modified`, `status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- 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 = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(96),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
创建名为seata的数据库,再执行上面的sql语句
数据库创建成功
(3)Nacos服务中心的注册
这里说一下,这是一个不可少的步骤,需要将Seata的相关配置注册到Nacos服务中心,Nacos的创建和部署这里就不再重复,感兴趣的可以看一下本人之前的博客,地址:菜鸟的Spring Cloud Alibaba学习总结(一):Nacos
首先需要有配置文件,这样才能注册到Nacos中,还是上面的README-zh.md文件,现在就知道了为什么要先下载Seata的目的了吧,很多的配置文件都可以从里面的README.md获取到。
将里面的config.txt文件拷贝下来,放在seata安装目录的同级目录下
修改config.txt里面的内容,根据需要进行修改,这里给出我的配置
service.vgroupMapping.order_tx_group=default
service.default.grouplist=39.96.22.34:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://39.96.22.34:3306/seata?userSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
下面对这些参数进行简单的讲解,具体可以看官网的配置说明:http://seata.io/zh-cn/docs/user/configurations.html
配置完之后需要从上面拷贝一个脚本下来,用该脚本将配置注册到nacos中
将nacos-config.sh放到seata安装目录,bin同级的目录下
先启动nacos之后,在该目录下右键,点击Git Bush Here,这是git工具的功能,通过git进行配置的注册
之后执行命令:sh nacos-config.sh -h nacos部署的ip
,默认采用nacos端口号为8848
因为我部署的nacos端口号是8080的一个集群,通过8080端口号进行转发,所以修改了脚本的端口号8080,如果需要修改端口号可以进该脚本nacos-config.sh,修改里面的端口号为自己对应端口号即可
注册成功后,可以登录nacos查看配置文件,会生成配置文件conf.txt对应的配置
ok,目前已经成功了一半,下面开始用docker部署Seata
(5)docker部署Seata
镜像的拉取:docker pull seataio/seata-server:1.3.0
挂载文件的创建,位置可以自己定义,这里给出我的文件创建,创建seata文件夹,里面创建seata-config文件夹,里面创建file.conf和registry.conf两个文件
编辑file.conf
## transaction log store, only used in seata-server
service {
#transaction service group mapping
#指定测试的事务组名称
vgroup_mapping.order_tx_group = "default"
#only support when registry.type=file, please don't set multiple addresses
#指定默认组 地址和端口,可以设置多个地址
default.grouplist = "39.96.22.34:8091"
#disable seata
#禁用全局事务=false 即开启服务
disableGlobalTransaction = false
}
store {
## store mode: file、db、redis
mode = "db"
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://39.96.22.34:3306/seata?userSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8"
user = "root"
password = "123456"
minConn = 5
maxConn = 30
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
注意,里面的配置可以参考我们下载的seata里面的conf目录里面的file.conf,里面还有file,redis模式的配置,由于这里mode=db,选择了db数据库模式,所以file模式和redis模式就不会生效了,所以这里就删掉了,需要的时候可以再配,里面的所有配置都跟前面的conf.txt文件里面的配置一致。
编辑registry.conf
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
# 服务名,自定义,建议默认seata-server
application = "seata-server"
# nacos的部署地址
serverAddr = "39.96.22.34:8080"
# nacos的部署地址的分组
group = "SEATA_GROUP"
# nacos的部署地址的命名空间
namespace = ""
# nacos的部署地址节点默认
cluster = "default"
# nacos的用户密码
username = "nacos"
password = "nacos"
}
file {
name = "/root/seata-config/file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
# nacos的部署地址
serverAddr = "39.96.22.34:8080"
# nacos的部署地址的命名空间
namespace = ""
# nacos的部署地址的分组
group = "SEATA_GROUP"
# nacos的用户和密码
username = "nacos"
password = "nacos"
}
file {
name = "/root/seata-config/file.conf"
}
}
配置可以参考我们下载的seata里面的conf目录里面的registry.conf,里面还有zk,consul等模式的配置,由于这里registry的type=nacos,选择了nacos的配置,config的type=nacos,选择了nacos的配置,所以其他的consul等配置就不会生效了,所以这里就删掉了,需要的时候可以再配。
启动容器
docker run --name seata01 -d -p 8091:8091 -e SEATA_CONFIG_NAME=file:/root/seata/config/registry -e SEATA_IP=39.96.22.34 -v /home/cainiao/seata/seata-config/:/root/seata/config --net=bridge --restart=always docker.io/seataio/seata-server:1.3.0
注意:-v后面的挂载文件的路径要根据自己的挂载文件的路径来
登录nacos,根据挂载的配置文件所配置的,生成了对应的服务
到此为止,Seata的服务端部署结束
三、Seata客户端开发
这里简单说一下下面要做的事情,开发3个微服务,订单服务、库存服务和账户服务,调用的逻辑为下单后,库存减少、账户扣款、下单结束,这个过程需要调用这3个服务,并且需要事务的控制,下面的客户端开发只讲一些重点的配置
(1)数据库准备
每一个服务都需要一个独立的数据库,分别为seata_account、seata_order和seata_storage
之后在对应数据库表创建对应表,完整的sql如下
-- 创建3个数据库
create database seata_order;
create database seata_storage;
create database seata_account;
-- 订单数据库表创建
use seata_order;
create table t_order (
`id` bigint(20) not null auto_increment primary key,
`user_id` bigint(20) default null comment '用户ID',
`product_id` bigint(20) default null comment '产品ID',
`count` int(11) default null comment '数量',
`money` decimal(18,2) default null comment '金额',
`status` int(1) default null comment '订单状态:0-创建中,1-已完结'
) engine=INNODB auto_increment = 1 default charset = 'utf8';
select * from t_order;
-- 库存数据库表创建
use seata_storage;
create table t_storage (
`id` bigint(20) not null auto_increment primary key,
`product_id` bigint(20) 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;
-- 账户数据库表创建
use seata_account;
create table t_account (
`id` bigint(20) not null auto_increment primary key,
`user_id` bigint(20) default null comment '用户ID',
`total` decimal(18,2) default null comment '总额度',
`used` decimal(18,2) default null comment '使用额度',
`residue` decimal(18,2) default '0' null comment '剩余额度'
) engine=INNODB auto_increment = 1 default charset = 'utf8';
insert into t_account(`id`, `user_id`, `total`, `used`, `residue`) values('1', '1', '10000' , '0', '10000');
select * from t_account;
事务回滚表(暂且这样叫吧)undo_log的添加,上面的3个数据库表都要创建这样的一张表来支持seata的事务
-- 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(20) NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(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 = utf8 COMMENT ='AT transaction mode undo table';
这张表也可以在README-zh.md里面的链接找到
创建完的数据库
(2)微服务的创建
这里一共要创建3个服务,订单、库存和账户,这里以订单模块seata-order-service2001为例,接着之前的==菜鸟的Spring Cloud Alibaba学习总结(二)==的项目继续写
pom
引入坐标依赖pom
<dependencies>
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<!-- 因为兼容版本问题,所以需要剔除它自己的seata的包 -->
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入我们使用的自己的seata对应的版本的依赖,而不是使用starter默认的版本-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.3.0</version>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--alibaba druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--hutool 测试雪花算法-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-captcha</artifactId>
<version>5.2.0</version>
</dependency>
</dependencies>
配置文件
创建配置文件,这里要创建file.conf、registry.conf和application.yml3个配置文件,首先是file.conf
transport {
# tcp, unix-domain-socket
type = "TCP"
#NIO, NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
# the client batch send request enable
enableClientBatchSendRequest = true
#thread factory for netty
threadFactory {
bossThreadPrefix = "NettyBoss"
workerThreadPrefix = "NettyServerNIOWorker"
serverExecutorThread-prefix = "NettyServerBizHandler"
shareBossWorker = false
clientSelectorThreadPrefix = "NettyClientSelector"
clientSelectorThreadSize = 1
clientWorkerThreadPrefix = "NettyClientWorkerThread"
# netty boss thread size
bossThreadSize = 1
#auto default pin or 8
workerThreadSize = "default"
}
shutdown {
# when destroy server, wait seconds
wait = 3
}
serialization = "seata"
compressor = "none"
}
service {
#transaction service group mapping
vgroupMapping.order_tx_group = "default"
#only support when registry.type=file, please don't set multiple addresses
default.grouplist = "39.96.22.34:8091"
#degrade, current not support
enableDegrade = false
#disable seata
disableGlobalTransaction = false
}
client {
rm {
asyncCommitBufferLimit = 10000
lock {
retryInterval = 10
retryTimes = 30
retryPolicyBranchRollbackOnConflict = true
}
reportRetryCount = 5
tableMetaCheckEnable = false
reportSuccessEnable = false
sagaBranchRegisterEnable = false
}
tm {
commitRetryCount = 5
rollbackRetryCount = 5
defaultGlobalTransactionTimeout = 60000
degradeCheck = false
degradeCheckPeriod = 2000
degradeCheckAllowTimes = 10
}
undo {
dataValidation = true
onlyCareUpdateColumns = true
logSerialization = "jackson"
logTable = "undo_log"
}
log {
exceptionRate = 100
}
}
配置跟前面服务端讲的file.conf要保持一致,像vgroupMapping.order_tx_group、nacos部署的地址,分组等信息一定要一致,然后是registry.conf
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
loadBalance = "RandomLoadBalance"
loadBalanceVirtualNodes = 10
nacos {
application = "seata-server"
serverAddr = "39.96.22.34:8080"
group = "SEATA_GROUP"
namespace = ""
username = "nacos"
password = "nacos"
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = "0"
password = ""
timeout = "0"
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
consul {
serverAddr = "127.0.0.1:8500"
}
etcd3 {
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3、springCloudConfig
type = "nacos"
nacos {
serverAddr = "39.96.22.34:8080"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
appId = "seata-server"
apolloMeta = "http://192.168.1.204:8801"
namespace = "application"
apolloAccesskeySecret = ""
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
这里的配置也是跟前面的registry.conf保持一致,只是这里一些没用的模式,像zk、consul模式等这里没有删掉,前面的删掉了,这里删不删都可以,最后是application.yml
server:
port: 2001
spring:
application:
name: seata-order-service
cloud:
alibaba:
seata:
# 自定义事务组名称需要与seata-server中的对应,我们之前在seata的配置文件中配置的名字
tx-service-group: order_tx_group
nacos:
discovery:
server-addr: 39.96.22.34:8080
# namespace: ffee2bbe-5ba5-4e6e-a822-6a00abe11769
datasource:
# 当前数据源操作类型
type: com.alibaba.druid.pool.DruidDataSource
# mysql驱动类
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://39.96.22.34:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath*:mapper/*.xml
主启动类
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) //取消数据源的自动创建
@EnableDiscoveryClient
@EnableFeignClients
public class SeataAccountMain2003 {
public static void main(String[] args) {
SpringApplication.run(SeataAccountMain2003.class,args);
}
}
config包
数据源配置类DataSourceProxyConfig
@Configuration
public class DataSourceProxyConfig {
@Value("${mybatis.mapperLocations}")
private String mapperLocations;
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource() {
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSourceProxy);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(resolver.getResources(mapperLocations));
return bean.getObject();
}
}
mybatis配置类MyBatisConfig
@Configuration
@MapperScan({"com.zsc.mapper"})
public class MyBatisConfig {
}
entity包
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
/**
* 主键
*/
private Long id;
/**
* 用户id
*/
private Long userId;
/**
* 产品id
*/
private Long productId;
/**
* 数量
*/
private Integer count;
/**
* 金额
*/
private BigDecimal money;
/**
* 订单状态:0:创建中;1:已创建
*/
private Integer status;
}
mapper包
@Mapper
@Repository
public interface OrderMapper {
/**
* 1 新建订单
* @param order
* @return
*/
int create(Order order);
/**
* 2 修改订单状态,从0改为1
* @param userId
* @param status
* @return
*/
int update(@Param("userId") Long userId, @Param("status") Integer status);
}
对应的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.zsc.mapper.OrderMapper">
<resultMap id="BaseResultMap" type="com.zsc.entity.Order">
<id column="id" property="id" jdbcType="BIGINT"></id>
<result column="user_id" property="userId" jdbcType="BIGINT"></result>
<result column="product_id" property="productId" jdbcType="BIGINT"></result>
<result column="count" property="count" jdbcType="INTEGER"></result>
<result column="money" property="money" jdbcType="DECIMAL"></result>
<result column="status" property="status" jdbcType="INTEGER"></result>
</resultMap>
<insert id="create" parameterType="com.zsc.entity.Order" useGeneratedKeys="true"
keyProperty="id">
insert into t_order(user_id,product_id,count,money,status) values (#{userId},#{productId},#{count},#{money},0);
</insert>
<update id="update">
update t_order set status =1 where user_id =#{userId} and status=#{status};
</update>
</mapper>
server包
接口
public interface OrderServer {
/**
* 创建订单
* @param order
*/
String create(Order order);
}
利用openfeign调用其他服务的接口
@FeignClient(value = "seata-account-service")
public interface AccountServer {
@RequestMapping("/Account/{id}")
public String update(@PathVariable("id") Long userId, @RequestParam("used") BigDecimal used);
}
@FeignClient(value = "seata-storage-service")
public interface StorageServer {
@RequestMapping("/Storage/{id}")
public String update(@PathVariable("id") Long productId, @RequestParam("count") Integer count);
}
OrderServer的实现类
@Service
@Slf4j
public class OrderServerImpl implements OrderServer {
@Autowired
private OrderMapper orderMapper;
@Autowired
private AccountServer accountServer;
@Autowired
private StorageServer storageServer;
@Override
@GlobalTransactional(name = "order_tx_group",rollbackFor = Exception.class)
public String create(Order order) {
log.info("用户开始下订单......");
orderMapper.create(order);
log.info("开始更新库存......");
String result01 = storageServer.update(order.getProductId(), order.getCount());
log.info("库存更新成功......");
int a = 10/0;
log.info("开始更新用户账户......");
String result02 = accountServer.update(order.getUserId(), order.getMoney());
log.info("账户更新结束......");
int result03 = orderMapper.update(order.getUserId(), 0);
log.info("订单操作结束......");
return "用户下单成功";
}
}
注意:上面加了@GlobalTransactional注解,order_tx_group自定义全局事务组,表示里面的所有数据库操作要么都成功,要么都失败,而且里面可以捕捉Exception,发生异常后事务回滚
controller包
@RestController
public class OrderController {
@Autowired
private OrderServer orderServer;
@GetMapping("/order")
public String create(Order order){
String result = orderServer.create(order);
return result;
}
}
还有其他两个模块这里就不细说了,配置文件file.confregistry.conf一样的,application.yml基本一致
测试,加了@GlobalTransactional,并且在里面故意加了10/0的异常,发现事务回滚了,没加的事务没有回滚
四、TC、TM、RM
TC (Transaction Coordinator) - 事务协调者 | 维护全局和分支事务的状态,驱动全局事务提交或回滚。 |
TM (Transaction Manager) - 事务管理器 | 定义全局事务的范围:开始全局事务、提交或回滚全局事务。 |
RM (Resource Manager) - 资源管理器 | 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。 |
用上面的3个微服务来讲就是,RM表示里面涉及到的一个个数据库操作,TM表示@GlobalTransactional所加的那个方法,该方法里面就有一个个数据库操作,TC表示协调这些包括回滚事务的协调者