seata
简介TC
(Transaction Coordinator
) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM
(Transaction Manager
) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM
(Resource Manager
) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
四种事务模式:
AT
模式:提供无侵入自动补偿的事务模式。自动补偿的 sql
是系统生成的。TCC
模式: 反向补偿的 sql
需要自己手动去写。XA
模式:支持已实现 XA
接口的数据库的 XA
模式。SAGA
模式:为长事务提供有效的解决方案,提供编排式与注解式。自动补偿(又称反向补偿): 例如有 a,b,c
三个服务,现在 a
分别调用服务 b
和 c
,为了确保 b
和 c
的调用同时成功或者同时失败,那么就要使用分布式事务。假设先调用 b
在调用 c
, b
调用完成之后,事务就提交了,然后调用 c
,c
服务出错,那么现在需要回滚。此时 b
需要回滚,但是回滚并不是我们传统意义上的回滚,而是通过一条 sql
将 b
服务中的数据进行复原,这个过程就是反向补偿。
seata-server
seata
所提供的 seata-server 本质上就是⼀个 SpringBoot
。
bin
目录下的 seata-server.bat
文件7091
是后台管理页面访问的端口,7091
是通信端口。application.example.yml
案例的配置文件地址:https://seata.io/zh-cn/docs/user/quickstart.html
新建两个数据库,并在每个数据库中创建 UNDO_LOG
表
account
库:
# ************************************************************
# Sequel Pro SQL dump
# Version 5446
#
# https://www.sequelpro.com/
# https://github.com/sequelpro/sequelpro
#
# Host: 127.0.0.1 (MySQL 5.7.26)
# Database: account
# Generation Time: 2022-06-04 04:01:18 +0000
# ************************************************************
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
SET NAMES utf8mb4;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
# Dump of table account_tbl
# ------------------------------------------------------------
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`money` int(11) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `account_tbl` WRITE;
/*!40000 ALTER TABLE `account_tbl` DISABLE KEYS */;
INSERT INTO `account_tbl` (`id`, `user_id`, `money`)
VALUES
(1,'javaboy',999000);
/*!40000 ALTER TABLE `account_tbl` ENABLE KEYS */;
UNLOCK TABLES;
# Dump of table undo_log
# ------------------------------------------------------------
DROP TABLE IF EXISTS `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,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
order
库:
# ************************************************************
# Sequel Pro SQL dump
# Version 5446
#
# https://www.sequelpro.com/
# https://github.com/sequelpro/sequelpro
#
# Host: 127.0.0.1 (MySQL 5.7.26)
# Database: order
# Generation Time: 2022-06-04 04:01:35 +0000
# ************************************************************
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
SET NAMES utf8mb4;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
# Dump of table order_tbl
# ------------------------------------------------------------
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
`money` int(11) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `order_tbl` WRITE;
/*!40000 ALTER TABLE `order_tbl` DISABLE KEYS */;
INSERT INTO `order_tbl` (`id`, `user_id`, `commodity_code`, `count`, `money`)
VALUES
(16,'javaboy','1111',10,1000),
(17,'javaboy','1111',10,1000),
(18,'javaboy','1111',10,1000),
(20,'javaboy','1111',10,1000);
/*!40000 ALTER TABLE `order_tbl` ENABLE KEYS */;
UNLOCK TABLES;
# Dump of table undo_log
# ------------------------------------------------------------
DROP TABLE IF EXISTS `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,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
storage
库:
# ************************************************************
# Sequel Pro SQL dump
# Version 5446
#
# https://www.sequelpro.com/
# https://github.com/sequelpro/sequelpro
#
# Host: 127.0.0.1 (MySQL 5.7.26)
# Database: storage
# Generation Time: 2022-06-04 04:01:47 +0000
# ************************************************************
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
SET NAMES utf8mb4;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
# Dump of table storage_tbl
# ------------------------------------------------------------
DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `storage_tbl` WRITE;
/*!40000 ALTER TABLE `storage_tbl` DISABLE KEYS */;
INSERT INTO `storage_tbl` (`id`, `commodity_code`, `count`)
VALUES
(2,'1111',60);
/*!40000 ALTER TABLE `storage_tbl` ENABLE KEYS */;
UNLOCK TABLES;
# Dump of table undo_log
# ------------------------------------------------------------
DROP TABLE IF EXISTS `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,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
seata
AT
模式需要 UNDO_LOG 表
**-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
-- 分支事务的 id
`branch_id` bigint(20) NOT NULL,
-- 全局事务的 id
`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;**
新建 seata-ax
的 maven
项目,并在该项目下创建子模块(spring boot
项目):
创建 eureka
的 spirng boot
项目,并添加如下依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
添加配置:
# 应用名称
spring.application.name=eureka
# 应用服务 WEB 访问端口
server.port=8761
# 不获取其他的服务,也不将自己注册到其他的 eureka 上面去
eureka.client.fetch-registry=false
eureka.client.register-with-eureka=false
启动类上添加开启 eureka
的注解:
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
创建 maven
项目,common
模块:
创建同一的响应处理类:
public class RespBean {
private Integer status;
private String msg;
private Object data;
public static RespBean ok(String msg,Object data){
return new RespBean(200,msg,data);
}
public static RespBean ok(String msg){
return new RespBean(200,msg,null);
}
public static RespBean error(String msg,Object data){
return new RespBean(400,msg,data);
}
public static RespBean error(String msg){
return new RespBean(200,msg,null);
}
public RespBean() {
}
public RespBean(Integer status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
创建全局异常处理类:
@RestControllerAdvice
public class GlobalException {
@ExceptionHandler(RuntimeException.class)
// 给一个错误的状态码,让事务回滚
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public RespBean runtimeException(RuntimeException e){
return RespBean.error(e.getMessage());
}
}
account
模块添加项目依赖:
<dependency>
<groupId>org.javaboygroupId>
<artifactId>commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.4version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
添加配置:
# 应用名称
spring.application.name=account
# 应用服务 WEB 访问端口
server.port=1111
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mappers/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=org.javaboy.account.mybatis.entity
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://192.168.10.10:3306/account?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
# 注册到 eureka 上
eureka.client.service-url.defalutZone=http://localhost:8761/eureka
# seata 事务分组信息
spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
在 resource
文件目录下添加 file.conf
和 registry.conf
文件,将 account
项目注册到 seata
TC(全局事务协调者)
中
file.conf
文件:
transport {
# tcp udt 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,will not be used for UDT
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.my_test_tx_group = "default"
#only support when registry.type=file, please don't set multiple addresses
default.grouplist = "127.0.0.1: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
}
tm {
commitRetryCount = 5
rollbackRetryCount = 5
}
undo {
dataValidation = true
logSerialization = "jackson"
logTable = "undo_log"
}
log {
exceptionRate = 100
}
}
registry.conf
文件:
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "eureka"
nacos {
application = "seata-server"
serverAddr = "localhost"
namespace = ""
username = ""
password = ""
}
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 = "file"
nacos {
serverAddr = "localhost"
namespace = ""
group = "SEATA_GROUP"
username = ""
password = ""
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
appId = "seata-server"
apolloMeta = "http://192.168.1.204:8801"
namespace = "application"
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
添加 AccountMapper
接口:
@Mapper
@Repository
public interface AccountMapper {
@Update("update account_tbl set money=money-#{money} where user_id=#{account} ")
int updateAccount(@Param("account") String account,@Param("money") double money);
@Select("select money from account_tbl where user_id =#{account} ")
double getMoneyByAccount(String account);
}
添加 AccountService
:
@Service
public class AccountService {
@Autowired
private AccountMapper accountMapper;
// 扣款接口
public boolean deducAccount(String account,Double money){
accountMapper.updateAccount(account,money);
double m = accountMapper.getMoneyByAccount(account);
if (m>=0){
return true;
}
throw new RuntimeException("账户余额不足扣款失败!");
}
}
添加 AccountController
接口:
@RestController
public class AccountController {
@Autowired
private AccountService accountService;
@PostMapping("/deductAccount")
public RespBean deduct(String account,Double money){
if (accountService.deducAccount(account,money)){
return RespBean.ok("扣款成功!");
}
return RespBean.error("口款失败!");
}
}
order
模块添加依赖:
<dependency>
<groupId>org.javaboygroupId>
<artifactId>commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.4version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
添加 配置:
# 应用名称
spring.application.name=order
# 应用服务 WEB 访问端口
server.port=1113
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mappers/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=org.javaboy.order.mybatis.entity
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://192.168.10.10:3306/order?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
# 注册到 eureka 上
eureka.client.service-url.defalutZone=http://localhost:8761/eureka
# seata 事务分组信息
spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
在 resource
文件目录下添加 file.conf
和 registry.conf
文件。(文件和上面模块一样此处省略…)
启动类上添加 @EnableEurekaClient
和 @EnableFeignClients
注解。
创建 AccountFeign
调用 account
模块中的接口:
@FeignClient("ACCOUNT")
public interface AccountFeign {
@PostMapping("/deductAccount")
RespBean deduct(@RequestParam("account") String account, @RequestParam("money") Double money);
}
创建 OrderMapper
接口:
@Mapper
@Repository
public interface OrderMapper {
@Insert("insert into order_tbl(user_id,commodity_code,count,money) values(#{userId},#{commodityCode},#{count},#{money})")
int addOrder(@Param("userId") String userId, @Param("commodityCode") String commodityCode,
@Param("count") Integer count,@Param("money") Double money);
}
创建 Orderservice
类:
@Service
public class Orderservice {
@Autowired
private AccountFeign accountFeign;
@Autowired
private OrderMapper orderMapper;
/**
* 创建订单
* @param account 账号
* @param productId 商品 id
* @param count 购买数量
* @return
*/
public boolean createOrder(String account,String productId,Integer count){
// 先去扣款,假设每件商品都是 100 元
RespBean respBean = accountFeign.deduct(account, count * 100.0);
int i = orderMapper.addOrder(account, productId, count, count * 100.0);
return i==1 && respBean.getStatus() == 200;
}
}
创建 OrderController
类:
@RestController
public class OrderController {
@Autowired
private Orderservice orderservice;
@PostMapping("createOrder")
public RespBean creatOrder(String account,String productId,Integer count){
if (orderservice.createOrder(account,productId,count)){
return RespBean.ok("下单成功!~");
}
return RespBean.error("下单失败!");
}
}
storage
模块添加依赖:
<dependency>
<groupId>org.javaboygroupId>
<artifactId>commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.4version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
添加配置:
# 应用名称
spring.application.name=storage
# 应用服务 WEB 访问端口
server.port=1114
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mappers/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=org.javaboy.storage.mybatis.entity
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://192.168.10.10:3306/storage?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
# 注册到 eureka 上
eureka.client.service-url.defalutZone=http://localhost:8761/eureka
# seata 事务分组信息
spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
在 resource
文件目录下添加 file.conf
和 registry.conf
文件。(文件和上面模块一样此处省略…)
创建 StorageMapper
接口:
@Mapper
@Repository
public interface StorageMapper {
@Update("update storage_tbl set count=count-#{count} where commodity_code=#{productId}")
int deductStorage(@Param("productId") String productId, @Param("count") Integer count);
@Select("select count from storage_tbl where commodity_code=#{commodityCode}")
int getCountByCommodityCode(String commodityCode);
}
创建 StorageService
业务类:
@Service
public class StorageService {
@Autowired
private StorageMapper storageMapper;
public boolean deduct(String productId,Integer count){
int i = storageMapper.deductStorage(productId, count);
int resut = storageMapper.getCountByCommodityCode(productId);
if (resut>=0){
return true;
}
throw new RuntimeException("库存不足,扣库存失败!");
}
}
创建接口类:
@RestController
public class StorageController {
@Autowired
private StorageService storageService;
@PostMapping("deduct")
public RespBean deduct(String prodectId,Integer count){
if (storageService.deduct(prodectId,count)){
return RespBean.ok("扣库存成功!");
}
return RespBean.error("扣库存失败!");
}
}
business
模块添加项目依赖:
<dependency>
<groupId>org.javaboygroupId>
<artifactId>commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
添加配置:
# 应用名称
spring.application.name=business
# 应用服务 WEB 访问端口
server.port=1112
# 注册到 eureka 上
eureka.client.service-url.defalutZone=http://localhost:8761/eureka
# seata 事务分组信息
spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
启动类添加注解:
@EnableFeignClients
@EnableDiscoveryClient
添加 StorageFeign
和 OrderFeign
接口:
@FeignClient("STORAGE")
public interface StorageFeign {
@PostMapping("/deduct")
RespBean deduct(@RequestParam("prodectId") String prodectId, @RequestParam("count") Integer count);
}
@FeignClient("ORDER")
public interface OrderFeign {
@PostMapping("/createOrder")
RespBean creatOrder(@RequestParam("account") String account, @RequestParam("productId")String productId,@RequestParam("count") Integer count);
}
添加业务类,并添加全局异常处理的注解:
@Service
public class BusinessService {
@Autowired
private StorageFeign storageFeign;
@Autowired
private OrderFeign orderFeign;
@GlobalTransactional
public void purchase(String account,String productId,Integer count){
orderFeign.creatOrder(account,productId,count);
storageFeign.deduct(productId,count);
}
}
创建 BusinessController
接口:
@RestController
public class BusinessController {
@Autowired
private BusinessService businessService;
@PostMapping("order")
public RespBean order(String account, String productId, Integer count) {
try {
businessService.purchase(account, productId, count);
return RespBean.ok("下单成功!");
} catch (Exception e) {
e.printStackTrace();
return RespBean.error("下单失败!");
}
}
}
测试: