1. Seata 是什么?
由于业务和技术的发展,单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证, 但是全局的数据一致性问题没法保证。
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。https://seata.io/zh-cn/docs/overview/what-is-seata.html
seata分布式事务处理过程的一ID+三组件模型:
- Transaction ID XID 全局唯一的事务ID
- 三组件概念
- TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
- TM (Transaction Manager) - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。
- RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
处理过程:
- TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID;
- XID在微服务调用链路的上下文中传播;
- RM向TC注册分支事务,将其纳入XID对应全局事务的管辖;
- TM向TC发起针对XID的全局提交或回滚决议;
- TC调度XID下管辖的全部分支事务完成提交或回滚请求。
2. seata安装
Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。
本例前置条件:
- 默认nacos已经安装完成并启动,并开启了持久化配置
- 使用mysql8.0+数据库
2.1. Server端安装
seata1.5.2安装 + nacos2.1.1服务注册和配置中心集成
1. 下载seata 1.5.2: https://github.com/seata/seata/releases
2. 放在合适的路径下解压缩: 注意路径中一定不能出现中文和空格,否则会导致之后的git命令无法成功执行
3. 在数据库中创建全局事务所需要的表: 全局事务会话信息由3块内容构成,全局事务–>分支事务–>全局锁,对应表global_table、branch_table、lock_table
-
在数据库中创建一个名叫seata的数据库
-
执行sql脚本创建数据表,该脚本可以在seata安装包中查看,或在git项目中https://github.com/seata/seata/blob/master/script/server/db/mysql.sql,获取最新的sql脚本。
- seata 1.5.0 新增了distributed_lock表用于 seata-server 异步任务调度,并将字符集改为了utf8mb4。
- 默认事务分组的由my_test_tx_group 修改为 default_tx_group
- 更多升级信息可以查看官方文档:https://seata.io/zh-cn/docs/ops/upgrade.html
-- -------------------------------- 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 = InnoDB
DEFAULT 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 = InnoDB
DEFAULT 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_and_branch_id` (`xid` , `branch_id`)
) ENGINE = InnoDB
DEFAULT 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 = InnoDB
DEFAULT 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);
- 修改seata - config下的application.yaml配置文件,使用nacos作为服务注册发现中心和配置中心,其他详细配置可以参照seata提供的模板application.example.yml
3.1. 在nacos中新建一个命名空间,并记录下空间ID
3.2. 修改application.yml:主要修改项seata 下的 config、registry、store
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: localhost:8848
namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50d
group: SEATA_GROUP
username: nacos
password: nacos
#data-id: seataServer.properties
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
server-addr: localhost:8848
group: SEATA_GROUP
namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50d
cluster: default
username: nacos
password: nacos
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key: ""
#secret-key: ""
store:
# support: file 、 db 、 redis
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
user: root
password: 123456
min-conn: 1
max-conn: 20
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 100
max-wait: 10000
server:
service-port: 8091 #If not configured, the default is '${server.port} + 1000'
max-commit-retry-timeout: -1
max-rollback-retry-timeout: -1
rollback-retry-timeout-unlock-enable: false
enable-check-auth: true
enable-parallel-request-handle: true
retry-dead-threshold: 130000
xaer-nota-retry-timeout: 60000
undo:
log-save-days: 7
log-delete-period: 86400000
session:
branch-async-queue-size: 5000 #branch async remove queue size
enable-branch-async-remove: false #enable to asynchronous remove branchSession
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
3.3. 修改config配置文件
将D:\seata\script\config-center下的config.txt复制一份到seata的目录下,与bin目录同级,并修改配置信息,主要修改store.mode和store.db(可以将不需要的配置信息删除掉,以防在nacos配置中心中产生太多无用的配置项,例如file和Redis的相关配置)
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none
#Transaction routing rules configuration, only for the client
service.vgroupMapping.default_tx_group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
#Log rule configuration, for client and server
log.exceptionRate=100
#Transaction storage configuration, only for the server. The file, DB, and redis configuration values are optional.
store.mode=db
store.lock.mode=db
store.session.mode=db
#Used for password encryption
store.publicKey=
#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://localhost:3306/seata?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
store.db.user=root
store.db.password=123456
store.db.minConn=1
store.db.maxConn=20
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=10000
#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false
#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
- 注意需要将store.mode的三项DB改为小写db
- rewriteBatchedStatements=true,数据库链接中这一句是特别要加的,可以提升执行效率
3.4. 使用git将上面修改好的seata的配置导入nacos配置中心
复制D:\seata\script\config-center\nacos下的nacos-config.sh到D:\seata\conf文件夹下
右键启用git命令:输入以下命令并回车执行,等待数据同步即可
sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 704a579f-f1ef-45fd-818e-4e5c113ca50d -u nacos -w nacos
参数详情:
-h -p 指定nacos的端口地址;
-g 指定配置的分组,注意,是配置的分组;
-t 指定命名空间ID;
-u -w指定nacos的用户名和密码,使用nacos注册和配置认证的才需要指定。
导入成功后可以在nacos中查看到配置信息
2.2. Client端集成
基于springcloud分布式项目集成seata-Client端,首先需要搭建一个简单的测试服务环境:使用典型的订单系统,创建分布式微服务系统:共三个微服务模块
- 订单管理:seata-order-service
- 库存管理:seata-storage-service
- 账户管理:seata-account-service
本例前置条件:
1. 使用openFeign作为微服务远程调用框架
2. 使用mybatis作为持久层框架
3. 使用nacos作为服务注册中心
4. 需要注意各框架版本,否则会出现版本冲突:详细信息查看https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明
2.2.1. 创建父Maven项目,进行依赖版本控制
主要的依赖项和使用版本:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<mysql.version>8.0.23</mysql.version>
<druid.version>1.2.8</druid.version>
<mybatis.spring.boot.version>1.3.2</mybatis.spring.boot.version>
</properties>
<dependencyManagement>
<dependencies>
<!--springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.12.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springCloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springCloud alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.9.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
2.2.2. 创建库存管理微服务:seata-storage-service
1. 添加pom依赖:在父项目下新建module seata-storage-service2002,并添加依赖
<artifactId>seata-storage-service2002</artifactId>
<dependencies>
<!--nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--seata-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2021.1</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--web-->
<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>
<!--mysql connector-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mysql druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2. 修改application.yaml配置,以下为全部配置信息
需要先创建一个名叫seata_storage的数据库,以便数据库链接的配置:
server:
port: 2002
spring:
application:
name: seata-storage-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_storage?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml
# seata 配置
seata:
# seata配置中心
tx-service-group: default_tx_group
config:
type: nacos
nacos:
namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50d
# nacos配置中心地址
server-addr: localhost:8848
# 分组
group: SEATA_GROUP
# nacos的账号和密码
userName: nacos
password: nacos
# seata的注册中心
registry:
type: nacos
nacos:
application: seata-server
server-addr: localhost:8848
namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50d
userName: nacos
password: nacos
service:
vgroup-mapping:
default_tx_group: default
3. 编写业务代码
整体包结构:
domain包:
CommonResult:
package com.atguigu.alibaba.seata.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月17日
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message)
{
this(code,message,null);
}
}
Storage:
package com.atguigu.alibaba.seata.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月18日
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Storage {
private Long id; //主键
private Long productId; //产品ID
private Integer total; //库存总量
private Integer used; //已用库存数量
private Integer residue; //剩余库存数量
}
数据库建表sql:在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 seata_storage.t_storage(`id`, `product_id`, `total`, `used`, `residue`)
VALUES ('1', '1', '100', '0','100');
SELECT * FROM t_storage;
dao包:
StorageDao:
package com.atguigu.alibaba.seata.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月18日
*/
@Mapper
public interface StorageDao {
public void decrease(@Param("productId") Long productId, @Param("count") Integer count);
}
在resources下创建一个名叫mapper的文件夹,用来放mybatis的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" >
<!--Dao的全路径-->
<mapper namespace="com.atguigu.alibaba.seata.dao.StorageDao">
<!--对应entity的全路径-->
<resultMap id="BaseResultMap" type="com.atguigu.alibaba.seata.domain.Storage">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="product_id" property="productId" jdbcType="BIGINT"/>
<result column="total" property="total" jdbcType="INTEGER"/>
<result column="used" property="used" jdbcType="INTEGER"/>
<result column="residue" property="residue" jdbcType="INTEGER"/>
</resultMap>
<update id="decrease">
update
t_storage
set
used = used + #{count}, residue = residue - #{count}
where
product_id = #{productId}
</update>
</mapper>
service包:
StorageService:
package com.atguigu.alibaba.seata.service;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月18日
*/
public interface StorageService {
public void decrease(@RequestParam("productId") Long productId,@RequestParam("count") Integer count);
}
service.impl包:
StorageServiceImpl:
package com.atguigu.alibaba.seata.service.impl;
import com.atguigu.alibaba.seata.dao.StorageDao;
import com.atguigu.alibaba.seata.service.StorageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月18日
*/
@Service
public class StorageServiceImpl implements StorageService {
private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);
@Resource
private StorageDao storageDao;
@Override
public void decrease(Long productId, Integer count) {
LOGGER.info("------->storage-service中扣减库存开始");
storageDao.decrease(productId,count);
LOGGER.info("------->storage-service中扣减库存结束");
}
}
controller包:
StorageController:
package com.atguigu.alibaba.seata.controller;
import com.atguigu.alibaba.seata.domain.CommonResult;
import com.atguigu.alibaba.seata.service.StorageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月18日
*/
@RestController
@Slf4j
public class StorageController {
@Resource
private StorageService storageService;
@RequestMapping("/storage/decrease")
public CommonResult decrease(@RequestParam("productId") Long productId,@RequestParam("count") Integer count){
storageService.decrease(productId, count);
return new CommonResult(200,"扣减库存成功!");
}
}
config包:配置mybatis的包扫描
MyBatisConfig:
package com.atguigu.alibaba.seata.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月18日
*/
@Configuration
@MapperScan({"com.atguigu.alibaba.seata.dao"})
public class MyBatisConfig {
}
主程序入口:
package com.atguigu.alibaba.seata;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月18日
*/
@EnableDiscoveryClient
@SpringBootApplication
public class StorageMain {
public static void main(String[] args) {
SpringApplication.run(StorageMain.class,args);
}
}
2.2.3. 创建账户管理微服务:seata-account-service
1. 添加pom依赖:在父项目下新建module seata-account-service2003,并添加依赖
<artifactId>seata-account-service2003</artifactId>
<dependencies>
<!--nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--seata-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2021.1</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--web-->
<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>
<!--mysql connector-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mysql druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2. 修改application.yaml配置,以下为全部配置信息
需要先创建一个名叫seata_account的数据库,以便数据库链接的配置:
server:
port: 2003
spring:
application:
name: seata-account-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_account?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml
# seata 配置
seata:
# seata配置中心
tx-service-group: default_tx_group
config:
type: nacos
nacos:
namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50d
# nacos配置中心地址
server-addr: localhost:8848
# 分组
group: SEATA_GROUP
# nacos的账号和密码
userName: nacos
password: nacos
# seata的注册中心
registry:
type: nacos
nacos:
application: seata-server
server-addr: localhost:8848
namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50d
userName: nacos
password: nacos
service:
vgroup-mapping:
default_tx_group: default
3. 编写业务代码
整体包结构:
domain包:
CommonResult:
package com.atguigu.alibaba.seata.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月17日
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message)
{
this(code,message,null);
}
}
Account:
package com.atguigu.alibaba.seata.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月18日
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
private Long id; //主键
private Long userId; //用户ID
private BigDecimal total; //账户总额
private BigDecimal used; //已使用额度
private BigDecimal residue; //剩余额度
}
数据库建表sql:在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=1 DEFAULT CHARSET=utf8;
INSERT INTO seata_account.t_account(`id`, `user_id`, `total`, `used`, `residue`)
VALUES ('1', '1', '1000', '0', '1000');
SELECT * FROM t_account;
dao包:
AccountDao:
package com.atguigu.alibaba.seata.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月18日
*/
@Mapper
public interface AccountDao {
public void decrease(@Param("userId") Long userId,@Param("money") BigDecimal money);
}
在resources下创建一个名叫mapper的文件夹,用来放mybatis的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.alibaba.seata.dao.AccountDao">
<resultMap id="BaseResultMap" type="com.atguigu.alibaba.seata.domain.Account">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="userId" column="user_id" jdbcType="BIGINT"/>
<result property="total" column="total" jdbcType="DECIMAL"/>
<result property="used" column="used" jdbcType="DECIMAL"/>
<result property="residue" column="residue" jdbcType="DECIMAL"/>
</resultMap>
<update id="decrease">
update
t_account
set
residue = residue - #{money}, used = used + #{money}
where
user_id = #{userId}
</update>
</mapper>
service包:
AccountService:
package com.atguigu.alibaba.seata.service;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月18日
*/
public interface AccountService {
public void decrease (@RequestParam("userId") Long userId,@RequestParam("money") BigDecimal money);
}
service.impl包:
AccountServiceImpl:
package com.atguigu.alibaba.seata.service.impl;
import com.atguigu.alibaba.seata.dao.AccountDao;
import com.atguigu.alibaba.seata.service.AccountService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月18日
*/
@Service
public class AccountServiceImpl implements AccountService {
private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
@Resource
private AccountDao accountDao;
@Override
public void decrease(Long userId, BigDecimal money) {
LOGGER.info("------->Account-service中扣减金额开始");
accountDao.decrease(userId, money);
LOGGER.info("------->Account-service中扣减金额结束");
}
}
controller包:
package com.atguigu.alibaba.seata.controller;
import com.atguigu.alibaba.seata.domain.CommonResult;
import com.atguigu.alibaba.seata.service.AccountService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.math.BigDecimal;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月18日
*/
@RestController
public class AccountController {
@Resource
private AccountService accountService;
@RequestMapping("/account/decrease")
public CommonResult decrease(@RequestParam("userId") Long userId,@RequestParam("money") BigDecimal money){
accountService.decrease(userId, money);
return new CommonResult(200,"扣减账户余额成功!");
}
}
config包:
MyBatisConfig:
package com.atguigu.alibaba.seata.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月18日
*/
@Configuration
@MapperScan({"com.atguigu.alibaba.seata.dao"})
public class MyBatisConfig {
}
主程序入口:
package com.atguigu.alibaba.seata;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月18日
*/
@EnableDiscoveryClient
@SpringBootApplication
public class AccountMain {
public static void main(String[] args) {
SpringApplication.run(AccountMain.class,args);
}
}
2.2.4. 创建订单管理微服务:seata-order-service
1. 添加pom依赖:在父项目下新建module seata-order-service2001,并添加依赖
<artifactId>seata-order-service2001</artifactId>
<dependencies>
<!--nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--seata 官方文档推荐依赖添加方式-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2021.1</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.0.RELEASE</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>
<!--mysql-druid-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2. 修改application.yaml配置,以下为全部配置信息
需要先创建一个名叫seata_order的数据库,以便数据库链接的配置:
server:
port: 2001
spring:
application:
name: seata-order-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml
# seata 配置
seata:
# seata配置中心
tx-service-group: default_tx_group
config:
type: nacos
nacos:
namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50d
# nacos配置中心地址
server-addr: localhost:8848
# 分组
group: SEATA_GROUP
# nacos的账号和密码
userName: nacos
password: nacos
# seata的注册中心
registry:
type: nacos
nacos:
application: seata-server
server-addr: localhost:8848
namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50d
userName: nacos
password: nacos
service:
vgroup-mapping:
default_tx_group: default
3. 编写业务代码
整体包结构:
domain包:
CommonResult:
package com.atguigu.alibaba.seata.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月17日
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message)
{
this(code,message,null);
}
}
Order:也就是entity,和数据库字段对应
package com.atguigu.alibaba.seata.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月17日
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Long id;
private Long userId;
private Long productId;
private Integer count;
private BigDecimal money;
private Integer status; //订单状态:0:创建中;1:已完结
}
数据库建表sql:在数据库中新建一个名叫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;
dao包:
OrderDao:
package com.atguigu.alibaba.seata.dao;
import com.atguigu.alibaba.seata.domain.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月17日
*/
@Mapper
public interface OrderDao {
//1 新建订单
public void create(Order order);
//2 修改订单状态,从零改为1
public void update(@Param("userId") Long userId, @Param("status") Integer status);
}
在resources下创建一个名叫mapper的文件夹,用来放mybatis的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" >
<!--Dao的全路径-->
<mapper namespace="com.atguigu.alibaba.seata.dao.OrderDao">
<!--对应entity的全路径-->
<resultMap id="BaseResultMap" type="com.atguigu.alibaba.seata.domain.Order">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="user_id" property="userId" jdbcType="BIGINT"/>
<result column="product_id" property="productId" jdbcType="BIGINT"/>
<result column="count" property="count" jdbcType="INTEGER"/>
<result column="money" property="money" jdbcType="DECIMAL"/>
<result column="status" property="status" jdbcType="INTEGER"/>
</resultMap>
<insert id="create">
insert into t_order (id,user_id,product_id,count,money,status)
values (null,#{userId},#{productId},#{count},#{money},0);
</insert>
<update id="update">
update t_order set status = 1
where user_id=#{userId} and status = #{status};
</update>
</mapper>
service包:因为订单服务需要调用库存和账户服务,所以需要使用openFeign进行远程调用服务接口,并且需要先行将这两个服务的接口创建好
StorageService:
package com.atguigu.alibaba.seata.service;
import com.atguigu.alibaba.seata.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月17日
*/
@Service("storageService")
@FeignClient(value = "seata-storage-service")
public interface StorageService {
@PostMapping(value = "/storage/decrease")
CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}
AccountService:
package com.atguigu.alibaba.seata.service;
import com.atguigu.alibaba.seata.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月17日
*/
@Service("accountService")
@FeignClient(value = "seata-account-service")
public interface AccountService {
@PostMapping(value = "/account/decrease")
CommonResult decrease(@RequestParam("userId") Long userId,@RequestParam("money") BigDecimal money);
}
OrderService:
package com.atguigu.alibaba.seata.service;
import com.atguigu.alibaba.seata.domain.Order;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月17日
*/
public interface OrderService {
void create(Order order);
}
service.impl包:
OrderServiceImpl:
package com.atguigu.alibaba.seata.service.impl;
import com.atguigu.alibaba.seata.dao.OrderDao;
import com.atguigu.alibaba.seata.domain.Order;
import com.atguigu.alibaba.seata.service.AccountService;
import com.atguigu.alibaba.seata.service.OrderService;
import com.atguigu.alibaba.seata.service.StorageService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月17日
*/
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Resource
private OrderDao orderDao;
@Resource
private StorageService storageService;
@Resource
private AccountService accountService;
@Override
@GlobalTransactional
public void create(Order order) {
log.info("----->开始新建订单");
//1 新建订单
orderDao.create(order);
//2 扣减库存
log.info("----->订单微服务开始调用库存,做扣减Count");
storageService.decrease(order.getProductId(),order.getCount());
log.info("----->订单微服务开始调用库存,做扣减end");
//3 扣减账户
log.info("----->订单微服务开始调用账户,做扣减Money");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("----->订单微服务开始调用账户,做扣减end");
//4 修改订单状态,从零到1,1代表已经完成
log.info("----->修改订单状态开始");
orderDao.update(order.getUserId(),0);
log.info("----->修改订单状态结束");
}
}
controller包:
OrderSeataController:
package com.atguigu.alibaba.seata.controller;
import com.atguigu.alibaba.seata.domain.CommonResult;
import com.atguigu.alibaba.seata.domain.Order;
import com.atguigu.alibaba.seata.service.OrderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月17日
*/
@RestController
public class OrderSeataController {
@Resource
private OrderService orderService;
@GetMapping("/order/create")
public CommonResult create(Order order){
orderService.create(order);
return new CommonResult(200,"订单创建成功");
}
}
config包:
package com.atguigu.alibaba.seata.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月17日
*/
@Configuration
@MapperScan({"com.atguigu.alibaba.seata.dao"})
public class MyBatisConfig {
}
主程序入口:
package com.atguigu.alibaba.seata;
import io.seata.spring.annotation.datasource.EnableAutoDataSourceProxy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author 强浩
* @version 1.0.0
* Copyright(c) YTANG All Rights Reserved
* @className
* @project 管理系统
* @date 2022年10月17日
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableAutoDataSourceProxy
public class OrderMain {
public static void main(String[] args) {
SpringApplication.run(OrderMain.class,args);
}
}
2.2.5. 集成Seata - Client
1. 添加seata依赖(编写业务代码时已经添加完毕)
2. 在所有需要进行全局事务管理的业务数据库下新建undo_log表、配置参数(仅AT模式)
-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table `undo_log`;
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,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
3. 数据源代理(不支持自动和手动配置并存):我这里使用的是seata-starter,默认开启了自动数据源代理,无需修改
4. 初始化GlobalTransactionScanner:自动,引入seata-spring-boot-starter、spring-cloud-starter-alibaba-seata等jar,无需修改
5. 实现xid跨服务传递:自动 springCloud用户可以引入spring-cloud-starter-alibaba-seata,内部已经实现xid传递,无需修改
官方部署指南:https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html
2.2.6. 业务使用
注解拦截:全局事务
在需要进行全局事务管理的入口接口上标注: @GlobalTransactional,例:
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Resource
private OrderDao orderDao;
@Resource
private StorageService storageService;
@Resource
private AccountService accountService;
@Override
@GlobalTransactional
public void create(Order order) {
log.info("----->开始新建订单");
//1 新建订单
orderDao.create(order);
//2 扣减库存
log.info("----->订单微服务开始调用库存,做扣减Count");
storageService.decrease(order.getProductId(),order.getCount());
log.info("----->订单微服务开始调用库存,做扣减end");
//3 扣减账户
log.info("----->订单微服务开始调用账户,做扣减Money");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("----->订单微服务开始调用账户,做扣减end");
//4 修改订单状态,从零到1,1代表已经完成
log.info("----->修改订单状态开始");
orderDao.update(order.getUserId(),0);
log.info("----->修改订单状态结束");
}
}
2.2.7. 测试
1. 启动seata-server:双击bin下的seata-server.bat
2. 启动三个微服务:
发送请求:这时应该可以正常创建数据并扣减数据
http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
3. 先不添加@GlobalTransactional注解
在关闭account账户服务的情况下,发送请求:这时数据库应该创建了订单信息和扣减了库存,但账户没有扣减,出现了数据的不一致
http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
4. 添加@GlobalTransactional注解,在关闭account账户服务的情况下
发送请求:这时数据库应该不会创建任何数据加粗样式