SpringCloudAlibaba整合Seata

发布时间 2023-09-21 12:17:00作者: xclyydss

Seata(全称为Simple Extensible Autonomous Transaction Architecture)是一个开源的分布式事务解决方案,用于解决分布式系统中的事务一致性问题。在分布式系统中,由于各个服务可能分布在不同的服务器上,涉及的数据库也可能不同,因此需要一种机制来保证分布式事务的原子性、一致性、隔离性和持久性(ACID特性)。
Seata 提供了一套完整的解决方案来支持分布式事务,主要包括以下核心功能:
全局事务管理: Seata 提供全局事务管理功能,允许用户在分布式环境下对多个参与者服务进行事务的管理。通过全局事务管理,Seata能够确保在跨多个服务的操作中,要么所有操作都提交成功,要么全部失败,从而保持数据的一致性。
分布式事务协调: Seata 使用一个协调器(Coordinator)来协调全局事务的各个参与者,确保它们按照统一的规则执行事务,最终提交或者回滚。
事务日志存储: Seata 使用可靠的存储来记录分布式事务的状态和日志信息,以便在故障发生时能够进行恢复和回滚。
高可用性: Seata 提供高可用的部署方式,支持多个协调器实例的集群部署,确保在某个协调器故障时仍能继续提供服务。
多种事务模式: Seata 支持 AT(自动型事务)模式和 TCC(Try-Confirm-Cancel)模式等多种事务模式,以适应不同场景下的需求。
总的来说,Seata旨在解决分布式事务的复杂性,使得开发人员能够更加方便地在分布式系统中实现事务管理,提供了更高的事务一致性和可靠性。

这里我只演示AT模式,因为是使用起来最简单的模式

首先我们需要去下载安装一下seata的服务中心:https://github.com/seata/seata/releases/tag/v1.6.1
注意这个版本要和一开始讲的springcloudalibaba版本一致(我们这里用的是seata1.6.1)
不记得可以回去看下版本说明 完整springcloudalibaba项目搭建教程(新手入门)

 

我们将下载下来的压缩包上传到/usr/local目录下

 

然后直接解压

tar -zxvf seata-server-1.6.1.tar.gz
1
为了实现seata的高可用,我们给它配置数据源为db的模式,也就是接入mysql,注意mysql版本需要5.7及以上
我们先去刚才解压的文件里面找到需要的sql文件,下载下来

cd /usr/local/seata/script/server/db
1


新建一个数据库

运行刚才的sql文件

我们还需要用到nacos
我们新建一个命名空间

记住这个空间的id,下面要用

 

然后我们进入/usr/local/seata/conf/

cd /usr/local/seata/conf/
1


修改里面的application.yml文件
记得把里面的nacos配置和mysql配置换成自己的

特别注意:

security:
secretKey: 88888888888888888888888888888888 # 这里要注意, csdn说我泄露隐私,所以这里不要改成这个啊!!!!!!!!!!!!!
1
2
# 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: 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: 192.168.43.128:8848
namespace: f6758f0e-1bbe-484f-9247-e9e6d1df5a25
group: SEATA_GROUP
username: nacos
password: nacos
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
server-addr: 192.168.43.128:8848
group: SEATA_GROUP
namespace: f6758f0e-1bbe-484f-9247-e9e6d1df5a25
username: nacos
password: nacos
store:
# support: file 、 db 、 redis
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.43.128:3306/seata?rewriteBatchedStatements=true
user: root
password: px123456
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 1000
max-wait: 5000
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: 88888888888888888888888888888888 # 这里要注意, csdn说我泄露隐私,所以这里不要改成这个啊!!!!!!!!!!!!!
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login


我们再找到/usr/local/seata/script/config-center/下的config.txt文件

 

主要还是修改里面store.db数据源配置

#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=file
store.lock.mode=file
store.session.mode=file
#Used for password encryption
store.publicKey=

#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100

#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.jdbc.Driver
store.db.url=jdbc:mysql://192.168.43.128:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=px123456
store.db.minConn=5
store.db.maxConn=30
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=5000

#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=
store.redis.queryLimit=100

#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

我们还需要将一些配置文件注入到nacos中

cd /usr/local/seata/script/config-center/nacos/
1


用下面的命令执行里面nacos-config.sh

-h nacos的ip
-p nacos的端口
-t 刚才nacos空间id
-u nacos的登录用户
-w nacos的登录密码

sh nacos-config.sh -h 192.168.43.128 -p 8848 -g SEATA_GROUP -t f6758f0e-1bbe-484f-9247-e9e6d1df5a25 -u nacos -w nacos
1
注意下面箭头两个地方,一个是提示too many arguments,这个是因为config.txt里有类似这种“store.publicKey=”,就是等于号后面是空的,不过不影响使用,有强迫症的可以把这种改成“ =“” ”,就是在后面加上英语双引号
下面那个init nacos config fail不用管,脚本到那里已经执行完了

 

我们去nacos看下刚创建的空间
可以看到文件都进去了

 

最后我们去启动seata
先进入/usr/local/seata/bin/目录

cd /usr/local/seata/bin/
1
启动里面的脚本:sh seata-server.sh

 

启动成功后我们访问:http://192.168.43.128:7091/

 

密码账号都是seata
下面就是seata的控制台

 

seata服务搭建好我们就可以开始整合代码了
首先我们得先在项目里面接入数据库,内容有些多,我就直接按下面的项目目录一个个文件放上来,建议大家直接去githup上拉一下代码(https://github.com/PX1206/SpringCloudAlibaba)

我们先去父项目的pom里面指定引入mysql、mybatisplus以及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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>


<groupId>com.sakura.springcloud</groupId>
<artifactId>springcloudalibaba</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springcloudalibaba</name>
<description>springcloudalibaba</description>

<modules>
<module>order</module>
<module>stock</module>
<module>product</module>
</modules>

<packaging>pom</packaging>

<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.7.7</spring-boot.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
<mysql.version>5.1.47</mysql.version>
<mybatis-plus.version>3.5.2</mybatis-plus.version>
<druid.version>1.2.11</druid.version>
</properties>
<dependencies>
<!--springboot基本场景启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<!--springboot测试场景启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<!-- springboot版本管理器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- springCloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- springCloudAlibaba -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!--mybatisPlus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

</project>

接着调整order服务

 

我们在数据库新建一个数据库order

 

然后新建一张order表

CREATE TABLE `t_order` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id,自增',
`order_no` varchar(45) DEFAULT NULL COMMENT '订单号',
`product_no` varchar(45) DEFAULT NULL COMMENT '商品编号',
`num` int(11) DEFAULT NULL COMMENT '数量',
`total_price` int(11) DEFAULT NULL COMMENT '总价格,单位为分',
`create_dt` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建日期',
`update_dt` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改日期',
`status` int(11) DEFAULT '1' COMMENT '状态:1正常 0删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='订单表';

先直接覆盖pom文件引入jar包

<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.sakura.springcloud</groupId>
<artifactId>springcloudalibaba</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>order</artifactId>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- nacos服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- nacos配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- 新版本已不再默认开启bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>


<!-- loadbalancer启动器 新版本的springcloud去掉了ribbon自带的负载均衡器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<!-- openfeign启动器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- sentinel启动器 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- 加入mysql连接器 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<!-- mybatis-plus启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

<!-- druid启动器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>

<!-- validation校验启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

</dependencies>
</project>


然后在application.yml里面配置好相关配置信息

server:
port: 8020

logging:
level:
com.sakura.product.feign: debug # 这里我们可以单独配置feign的日志级别

spring:
profiles:
active: dev
datasource:
url: jdbc:mysql://192.168.43.128:3306/stock?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: px123456
# 使用druid数据源
druid:
#2.连接池配置
#初始化连接池的连接数量 大小,最小,最大
initial-size: 5
min-idle: 5
max-active: 20
#配置获取连接等待超时的时间
max-wait: 60000
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 30000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: true
test-on-return: false
# 是否缓存preparedStatement,也就是PSCache 官方建议MySQL下建议关闭 个人建议如果想用SQL防火墙 建议打开
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filter:
stat:
merge-sql: true
slow-sql-millis: 5000
cloud:
sentinel:
transport:
dashboard: 192.168.43.128:9100

mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: AUTO
logic-delete-value: "Y" # 逻辑已删除值(默认为 Y)
logic-not-delete-value: "N" #逻辑未删除值(默认为 N)
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: com.sakura.stock.entity

feign:
sentinel:
enabled: true # 开启openfeign中的sentinel
client:
config:
product-service:
logger-level: basic
connect-timeout: 5000 # 连接超时时间,默认2s
read-timeout: 6000 #请求处理超时时间,默认5s
request-interceptors:
- com.sakura.stock.interceptor.feign.CustomFeignInterceptor # 开启自定义拦截器


OrderController里面我新加了一个addOrder方法,之前测试的一些方法可以删掉也可以放着,我这里是为了照顾之前的模块就不删了

package com.sakura.order.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.sakura.order.feign.ProductFeignService;
import com.sakura.order.feign.StockFeignService;
import com.sakura.order.param.AddOrderParam;
import com.sakura.order.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author Sakura
* @date 2023/7/19 11:25
*/
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {

@Autowired
StockFeignService stockFeignService;
@Autowired
ProductFeignService productFeignService;

@Value("${user.name}")
private String userName;

@Value("${user.age}")
private String userAge;

@Value("${user.sex}")
private String userSex;

@Autowired
OrderService orderService;

@RequestMapping("/add")
public String add(){
System.out.println("下单成功");
String msg = stockFeignService.reduct();
String productMsg = productFeignService.get(1);
return userName + userAge + userSex + "下单成功" + msg + "-" + productMsg;
}

@RequestMapping("/get")
@SentinelResource(value = "get", blockHandler = "getBlockHandler")
public String get(){
return "获取订单成功";
}

// 流控方法必须和原方法类型一致参数一致
// 一定要加上BlockException
public String getBlockHandler(BlockException blockException){
// 我们可以在这个方法里面处理流控后的业务逻辑
return "get接口被流控";
}

@RequestMapping("/flow")
public String flow() throws Exception {
Thread.sleep(3000);
return "正常访问";
}

@RequestMapping("/addOrder")
public String addOrder(@RequestBody AddOrderParam addOrderParam){
return orderService.addOrder(addOrderParam);
}

}


下面这些其实都是逆向工程生成的

对应Order实体

package com.sakura.order.entify;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.util.Date;

/**
* @author Sakura
* @date 2023/7/28 15:13
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@TableName("t_order")
public class Order extends BaseEntity {

private static final long serialVersionUID = 1L;

/**
* 自增id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;

/**
* 订单号
*/
private String orderNo;

/**
* 商品编号
*/
private String productNo;

/**
* 数量
*/
private Integer num;


/**
* 总价格
*/
private Integer totalPrice;

/**
* 创建日期
*/
private Date createDt;

/**
* 修改日期
*/
private Date updateDt;

/**
* 状态:1正常 0删除
*/
private Integer status;

}

还有它继承的BaseEntity

package com.sakura.order.entify;

import java.io.Serializable;

public abstract class BaseEntity implements Serializable{
private static final long serialVersionUID = -7176390653391227433L;

}


然后OrderMapper,这里因为用的mybatisplus,所以里面没有生成方法

package com.sakura.order.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sakura.order.entify.Order;
import org.apache.ibatis.annotations.Mapper;

/**
* @author Sakura
* @date 2023/7/28 15:13
*/
@Mapper
public interface OrderMapper extends BaseMapper<Order> {


}


对应的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.sakura.order.mapper.OrderMapper">

</mapper>


再就是service里面的OrderService

package com.sakura.order.service;

import com.sakura.order.param.AddOrderParam;

/**
* @author Sakura
* @date 2023/7/28 16:18
*/
public interface OrderService {

String addOrder(AddOrderParam addOrderParam);
}


impl里面的OrderServiceImpl

package com.sakura.order.service.impl;

import com.sakura.order.entify.Order;
import com.sakura.order.feign.ProductFeignService;
import com.sakura.order.feign.StockFeignService;
import com.sakura.order.mapper.OrderMapper;
import com.sakura.order.param.AddOrderParam;
import com.sakura.order.service.OrderService;
import lombok.extern.java.Log;
import org.apache.http.client.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
* @author Sakura
* @date 2023/7/28 16:18
*/
@Service
@Log
public class OrderServiceImpl implements OrderService {

@Autowired
OrderMapper orderMapper;
@Autowired
ProductFeignService productFeignService;
@Autowired
StockFeignService stockFeignService;

@Override
public String addOrder(AddOrderParam addOrderParam) {

// 先去查询商品库存信息
Integer num = stockFeignService.getProductNum(addOrderParam.getProductNo());
log.info("商品库存数量:" + num);
if (num == null || num < 1 || num < addOrderParam.getNum()) {
return "商品库存不足";
}
Order order = new Order();
// 根据当前日期加随机数生成一个订单号
order.setOrderNo(DateUtils.formatDate(new Date(), "yyyyMMddHHmmssSSS")
+ (int) ((Math.random() * 9 + 1) * 10000000));
order.setProductNo(addOrderParam.getProductNo());
order.setNum(addOrderParam.getNum());
// 去商品服务获取商品单价
Integer unitPrice = productFeignService.getUnitPrice(addOrderParam.getProductNo());
log.info("商品单价:" + unitPrice);
if (unitPrice == null || unitPrice < 0) {
return "商品价格异常";
}
order.setTotalPrice(addOrderParam.getNum() * unitPrice);
order.setStatus(1);
orderMapper.insert(order);

return "商品下单成功:" + order.getOrderNo() + " 库存:" + num + " 单价:" + unitPrice;
}
}

feign里面的ProductFeignService

package com.sakura.order.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

/**
* @description: stock服务接口
* @author: Sakura
* @date: 2023/7/20 14:38
*/
@FeignClient(name = "product-service", path = "/product")
public interface ProductFeignService {

@RequestMapping("/get/{id}")
String get(@PathVariable("id") Integer id);

@GetMapping("/getUnitPrice/{productNo}")
Integer getUnitPrice(@PathVariable("productNo") String productNo);
}


StockFeignService

package com.sakura.order.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

/**
* @description: stock服务接口
* @author: Sakura
* @date: 2023/7/20 14:38
*/
@FeignClient(name = "stock-service", path = "/stock", fallback = StockFeignServiceFallback.class)
public interface StockFeignService {

@RequestMapping("/reduct")
String reduct();

@GetMapping("/getProductNum/{productNo}")
Integer getProductNum(@PathVariable("productNo") String productNo);
}


StockFeignServiceFallback

package com.sakura.order.feign;

import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;

/**
* @author Sakura
* @date 2023/7/27 17:30
*/
@Component
public class StockFeignServiceFallback implements StockFeignService {

@Override
public String reduct() {
// 我们可以在这里处理降级逻辑,比如记录日志或者发短信提示管理用户等
return "stock服务异常降级";
}

@Override
public Integer getProductNum(@PathVariable("productNo") String productNo) {
return 0;
}
}


一定要记得关闭interceptor里面的统一拦截器,因为里面有个修改url的

package com.sakura.order.interceptor.feign;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author Sakura
* @date 2023/7/20 17:24
*/
public class CustomFeignInterceptor implements RequestInterceptor {
Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
public void apply(RequestTemplate requestTemplate) {
logger.info("进入feign自定义拦截器");
// 我们可以在这里记录日志或者添加参数以及修改参数等
//requestTemplate.header("123", "123");
//requestTemplate.query("456", "456");
// 我们把参数从1改成5
//requestTemplate.uri("/get/5");
}
}


最后就是AddOrderParam请求参数

package com.sakura.order.param;

import com.sakura.order.entify.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

/**
* 订单详情表
*
* @author Sakura
* @since 2022-08-30
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class AddOrderParam extends BaseEntity {
private static final long serialVersionUID = 1L;

/**
* 商品编号
*/
@NotBlank(message = "商品编号不能为空")
private String productNo;

/**
* 数量
*/
@NotNull(message = "商品数量不能为空")
@Min(1)
private Integer num;

}


product服务也差不多
对了,product模块因为是之前复制过来的,包名搞错了,应该是com.sakura.product

 

新建一个product数据库

 

加一张product表

CREATE TABLE `t_product` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id,自增',
`product_name` varchar(45) DEFAULT NULL COMMENT '商品名称',
`product_no` varchar(45) DEFAULT NULL COMMENT '商品编号',
`unit_price` int(11) DEFAULT NULL COMMENT '单价,单位为分',
`create_dt` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建日期',
`update_dt` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改日期',
`status` int(11) DEFAULT '1' COMMENT '状态:1正常 0删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='商品表';

先调整pom文件

<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.sakura.springcloud</groupId>
<artifactId>springcloudalibaba</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>product</artifactId>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- nacos服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- nacos配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- 新版本已不再默认开启bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>


<!-- loadbalancer启动器 新版本的springcloud去掉了ribbon自带的负载均衡器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<!-- openfeign启动器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- sentinel启动器 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- 加入mysql连接器 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<!-- mybatis-plus启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

<!-- druid启动器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>

<!-- validation校验启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

</dependencies>
</project>

ProductController里面加了个getUnitPrice方法

package com.sakura.product.controller;

import com.sakura.product.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author Sakura
* @date 2023/7/19 11:35
*/
@RestController
@RequestMapping("/product")
public class ProductController {

@Value("${server.port}")
String port;

@Autowired
private ProductService productService;

@RequestMapping("/get/{id}")
public String get(@PathVariable("id") Integer id) throws Exception {
System.out.println("查询商品信息" + id);
// Thread.sleep(5000);
return "查询商品信息" + id + "-" + port;
}

/**
* @description: 获取商品单价
* @param productNo
* @return [java.lang.String]
* @author: Sakura
* @date: 2023/7/28 16:51
*/
@GetMapping("/getUnitPrice/{productNo}")
public Integer getUnitPrice(@PathVariable("productNo") String productNo) throws Exception {
return productService.getUnitPrice(productNo);
}

}

BaseEntity

package com.sakura.product.entify;

import java.io.Serializable;

public abstract class BaseEntity implements Serializable{
private static final long serialVersionUID = -7176390653391227433L;

}


Product 实体类

package com.sakura.product.entify;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.util.Date;

/**
* @author Sakura
* @date 2023/7/28 15:13
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@TableName("t_product")
public class Product extends BaseEntity {

private static final long serialVersionUID = 1L;

/**
* 自增id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;

/**
* 商品名称
*/
private String productName;

/**
* 商品编号
*/
private String productNo;

/**
* 单价
*/
private Integer unitPrice;

/**
* 创建日期
*/
private Date createDt;

/**
* 修改日期
*/
private Date updateDt;

/**
* 状态:1正常 0删除
*/
private Integer status;

}

CustomFeignInterceptor

package com.sakura.product.interceptor.feign;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author Sakura
* @date 2023/7/20 17:24
*/
public class CustomFeignInterceptor implements RequestInterceptor {
Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
public void apply(RequestTemplate requestTemplate) {
logger.info("进入feign自定义拦截器");
// // 我们可以在这里记录日志或者添加参数以及修改参数等
// requestTemplate.header("123", "123");
// requestTemplate.query("456", "456");
// // 我们把参数从1改成5
// requestTemplate.uri("/get/5");
}
}

ProductMapper

package com.sakura.product.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sakura.product.entify.Product;
import org.apache.ibatis.annotations.Mapper;

/**
* @author Sakura
* @date 2023/7/28 15:13
*/
@Mapper
public interface ProductMapper extends BaseMapper<Product> {


}


ProductServiceImpl

package com.sakura.product.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sakura.product.entify.Product;
import com.sakura.product.mapper.ProductMapper;
import com.sakura.product.service.ProductService;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
* @author Sakura
* @date 2023/7/28 16:18
*/
@Service
@Log
public class ProductServiceImpl implements ProductService {

@Autowired
ProductMapper productMapper;

@Override
public Integer getUnitPrice(String productNo) {
Product product = productMapper.selectOne(
Wrappers.<Product>lambdaQuery()
.eq(Product::getProductNo, productNo)
.eq(Product::getStatus, 1));
if (product == null || product.getUnitPrice() == null) {
return -1;
}

return product.getUnitPrice();
}

ProductService

package com.sakura.product.service;

/**
* @author Sakura
* @date 2023/7/28 16:18
*/
public interface ProductService {

Integer getUnitPrice(String productNo);

}

ProductApplication

package com.sakura.product;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
* @author Sakura
* @date 2023/7/19 11:43
*/
@SpringBootApplication
@EnableFeignClients
public class ProductApplication {

public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}

}

ProductMapper.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.sakura.product.mapper.ProductMapper">

</mapper>

stock服务和product基本一样

 

先建一个stock数据库

 

加一张stock表

CREATE TABLE `t_stock` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
`product_no` varchar(45) DEFAULT NULL COMMENT '商品编号',
`product_num` int(11) DEFAULT NULL COMMENT '商品数量',
`create_dt` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建日期',
`update_dt` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改日期',
`status` int(11) DEFAULT '1' COMMENT '状态:1正常 0删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='库存表';

先调整pom

<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.sakura.springcloud</groupId>
<artifactId>springcloudalibaba</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>stock</artifactId>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- nacos服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- nacos配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- 新版本已不再默认开启bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>


<!-- loadbalancer启动器 新版本的springcloud去掉了ribbon自带的负载均衡器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<!-- openfeign启动器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- sentinel启动器 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- 加入mysql连接器 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<!-- mybatis-plus启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

<!-- druid启动器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>

<!-- validation校验启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

</dependencies>
</project>

然后application.yml

server:
port: 8020

logging:
level:
com.sakura.stock.feign: debug # 这里我们可以单独配置feign的日志级别

spring:
profiles:
active: dev
datasource:
url: jdbc:mysql://192.168.43.128:3306/stock?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: px123456
# 使用druid数据源
druid:
#2.连接池配置
#初始化连接池的连接数量 大小,最小,最大
initial-size: 5
min-idle: 5
max-active: 20
#配置获取连接等待超时的时间
max-wait: 60000
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 30000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: true
test-on-return: false
# 是否缓存preparedStatement,也就是PSCache 官方建议MySQL下建议关闭 个人建议如果想用SQL防火墙 建议打开
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filter:
stat:
merge-sql: true
slow-sql-millis: 5000
cloud:
sentinel:
transport:
dashboard: 192.168.43.128:9100

mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: AUTO
logic-delete-value: "Y" # 逻辑已删除值(默认为 Y)
logic-not-delete-value: "N" #逻辑未删除值(默认为 N)
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: com.sakura.stock.entity

feign:
sentinel:
enabled: true # 开启openfeign中的sentinel
client:
config:
product-service:
logger-level: basic
connect-timeout: 5000 # 连接超时时间,默认2s
read-timeout: 6000 #请求处理超时时间,默认5s
request-interceptors:
- com.sakura.stock.interceptor.feign.CustomFeignInterceptor # 开启自定义拦截器

StockController

package com.sakura.stock.controller;

import com.sakura.stock.service.StockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author Sakura
* @date 2023/7/19 11:35
*/
@RestController
@RequestMapping("/stock")
public class StockController {

@Value("${server.port}")
String port;

@Autowired
StockService stockService;

@RequestMapping("/reduct")
public String reduct(){
// 抛异常测试openfeign整合sentinel熔断降级
int a = 1/0;
System.out.println("扣减库存");
return "扣减库存" + port;
}

@GetMapping("/getProductNum/{productNo}")
public Integer getProductNum(@PathVariable("productNo") String productNo){
return stockService.getProductNum(productNo);
}

}

BaseEntity,这玩意复制了几次,正常的应该有base模块统一维护这些公共的类,大家自己加一下

package com.sakura.stock.entify;

import java.io.Serializable;

public abstract class BaseEntity implements Serializable{
private static final long serialVersionUID = -7176390653391227433L;

}


Stock

package com.sakura.stock.entify;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.util.Date;

/**
* @author Sakura
* @date 2023/7/28 15:13
*/
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@TableName("t_stock")
public class Stock extends BaseEntity {

private static final long serialVersionUID = 1L;

/**
* 自增id
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;

/**
* 商品编号
*/
private String productNo;

/**
* 商品数量
*/
private Integer productNum;

/**
* 创建日期
*/
private Date createDt;

/**
* 修改日期
*/
private Date updateDt;

/**
* 状态:1正常 0删除
*/
private Integer status;

}

CustomFeignInterceptor

package com.sakura.stock.interceptor.feign;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author Sakura
* @date 2023/7/20 17:24
*/
public class CustomFeignInterceptor implements RequestInterceptor {
Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
public void apply(RequestTemplate requestTemplate) {
logger.info("进入feign自定义拦截器");
// // 我们可以在这里记录日志或者添加参数以及修改参数等
// requestTemplate.header("123", "123");
// requestTemplate.query("456", "456");
// // 我们把参数从1改成5
// requestTemplate.uri("/get/5");
}
}

StockMapper

package com.sakura.stock.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sakura.stock.entify.Stock;
import org.apache.ibatis.annotations.Mapper;

/**
* @author Sakura
* @date 2023/7/28 15:13
*/
@Mapper
public interface StockMapper extends BaseMapper<Stock> {


}

StockServiceImpl

package com.sakura.stock.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sakura.stock.entify.Stock;
import com.sakura.stock.mapper.StockMapper;
import com.sakura.stock.service.StockService;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @author Sakura
* @date 2023/7/28 16:18
*/
@Service
@Log
public class StockServiceImpl implements StockService {

@Autowired
StockMapper stockMapper;

@Override
public Integer getProductNum(String productNo) {
Stock stock = stockMapper.selectOne(
Wrappers.<Stock>lambdaQuery()
.eq(Stock::getProductNo, productNo)
.eq(Stock::getStatus, 1));
if (stock == null || stock.getProductNum() == null) {
return 0;
}
return stock.getProductNum();
}
}


StockService

package com.sakura.stock.service;

/**
* @author Sakura
* @date 2023/7/28 16:18
*/
public interface StockService {

Integer getProductNum(String productNo);

}


StockApplication

package com.sakura.stock;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
* @author Sakura
* @date 2023/7/19 11:43
*/
@SpringBootApplication
@EnableFeignClients
public class StockApplication {

public static void main(String[] args) {
SpringApplication.run(StockApplication.class, args);
}

}


StockMapper.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.sakura.stock.mapper.StockMapper">

</mapper>

好了项目基本架子搭好了,复制粘贴手都麻了
在启动这几个服务前我们先在数据库加几条数据

先是stock库的stock表

 

然后product库product表

 

启动这几个服务,我们在postman里面测试一下
请求成功,服务是正常的

 

数据库正常插入数据

 

最后我们来整合seata

我们先在order的pom文件中加入seata的jar包

<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.sakura.springcloud</groupId>
<artifactId>springcloudalibaba</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>order</artifactId>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- nacos服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- nacos配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- 新版本已不再默认开启bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>


<!-- loadbalancer启动器 新版本的springcloud去掉了ribbon自带的负载均衡器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<!-- openfeign启动器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- sentinel启动器 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- seata启动器 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

<!-- 加入mysql连接器 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<!-- mybatis-plus启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

<!-- druid启动器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>

<!-- validation校验启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

</dependencies>
</project>

然后在application.yml中加入seata配置

server:
port: 8010

logging:
level:
com.sakura.order.feign: debug # 这里我们可以单独配置feign的日志级别

spring:
profiles:
active: dev
datasource:
url: jdbc:mysql://192.168.43.128:3306/order?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: px123456
# 使用druid数据源
druid:
#2.连接池配置
#初始化连接池的连接数量 大小,最小,最大
initial-size: 5
min-idle: 5
max-active: 20
#配置获取连接等待超时的时间
max-wait: 60000
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 30000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: true
test-on-return: false
# 是否缓存preparedStatement,也就是PSCache 官方建议MySQL下建议关闭 个人建议如果想用SQL防火墙 建议打开
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filter:
stat:
merge-sql: true
slow-sql-millis: 5000
cloud:
sentinel:
transport:
dashboard: 192.168.43.128:9100

seata:
tx-service-group: order-tx-group
service:
vgroupMapping:
order-tx-group: default
config:
type: nacos
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: f6758f0e-1bbe-484f-9247-e9e6d1df5a25
group: SEATA_GROUP
username: ${spring.cloud.nacos.config.username}
password: ${spring.cloud.nacos.config.password}
registry:
type: nacos
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: f6758f0e-1bbe-484f-9247-e9e6d1df5a25
group: SEATA_GROUP
username: ${spring.cloud.nacos.discovery.username}
password: ${spring.cloud.nacos.discovery.password}


mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: AUTO
logic-delete-value: "Y" # 逻辑已删除值(默认为 Y)
logic-not-delete-value: "N" #逻辑未删除值(默认为 N)
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: com.sakura.order.entity

feign:
sentinel:
enabled: true # 开启openfeign中的sentinel
client:
config:
product-service:
logger-level: basic
connect-timeout: 5000 # 连接超时时间,默认2s
read-timeout: 6000 #请求处理超时时间,默认5s
request-interceptors:
- com.sakura.order.interceptor.feign.CustomFeignInterceptor # 开启自定义拦截器

20230814更新:
这里要注意一下
这个启动后会报错:can not get cluster name in registry config ‘service.vgroupMapping.order-tx-group’, please make sure registry config correct

seata:
tx-service-group: order-tx-group
service:
vgroupMapping:
order-tx-group: default

解决也很简单
我们在nacos的seata配置里面加一个配置,我这里是在user模块时才想起来的,大家改成自己模块就好

 

为了测试效果我们改一下OrderServiceImpl ,@GlobalTransactional先注释掉

package com.sakura.order.service.impl;

import com.sakura.order.entify.Order;
import com.sakura.order.feign.ProductFeignService;
import com.sakura.order.feign.StockFeignService;
import com.sakura.order.mapper.OrderMapper;
import com.sakura.order.param.AddOrderParam;
import com.sakura.order.service.OrderService;
import io.seata.spring.annotation.GlobalLock;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.java.Log;
import org.apache.http.client.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
* @author Sakura
* @date 2023/7/28 16:18
*/
@Service
@Log
public class OrderServiceImpl implements OrderService {

@Autowired
OrderMapper orderMapper;
@Autowired
ProductFeignService productFeignService;
@Autowired
StockFeignService stockFeignService;

@Override
// @GlobalLock
// @GlobalTransactional
public String addOrder(AddOrderParam addOrderParam) {

Order order1 = new Order();
order1.setOrderNo("6341341");
orderMapper.insert(order1);

// 先去查询商品库存信息
Integer num = stockFeignService.getProductNum(addOrderParam.getProductNo());
log.info("商品库存数量:" + num);
if (num == null || num < 1 || num < addOrderParam.getNum()) {
return "商品库存不足";
}
Order order = new Order();
// 根据当前日期加随机数生成一个订单号
order.setOrderNo(DateUtils.formatDate(new Date(), "yyyyMMddHHmmssSSS")
+ (int) ((Math.random() * 9 + 1) * 10000000));
order.setProductNo(addOrderParam.getProductNo());
order.setNum(addOrderParam.getNum());
// 去商品服务获取商品单价
Integer unitPrice = productFeignService.getUnitPrice(addOrderParam.getProductNo());
log.info("商品单价:" + unitPrice);
if (unitPrice == null || unitPrice < 0) {
return "商品价格异常";
}
order.setTotalPrice(addOrderParam.getNum() * unitPrice);
order.setStatus(1);
orderMapper.insert(order);

return "商品下单成功:" + order.getOrderNo() + " 库存:" + num + " 单价:" + unitPrice;
}
}

同样的我们调整一下product服务

<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.sakura.springcloud</groupId>
<artifactId>springcloudalibaba</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>product</artifactId>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- nacos服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- nacos配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- 新版本已不再默认开启bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>


<!-- loadbalancer启动器 新版本的springcloud去掉了ribbon自带的负载均衡器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<!-- openfeign启动器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- sentinel启动器 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- seata启动器 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

<!-- 加入mysql连接器 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<!-- mybatis-plus启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

<!-- druid启动器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>

<!-- validation校验启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

</dependencies>
</project>

然后application.yml

server:
port: 8030

logging:
level:
com.sakura.product.feign: debug # 这里我们可以单独配置feign的日志级别

spring:
profiles:
active: dev
datasource:
url: jdbc:mysql://192.168.43.128:3306/product?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: px123456
# 使用druid数据源
druid:
#2.连接池配置
#初始化连接池的连接数量 大小,最小,最大
initial-size: 5
min-idle: 5
max-active: 20
#配置获取连接等待超时的时间
max-wait: 60000
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 30000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: true
test-on-return: false
# 是否缓存preparedStatement,也就是PSCache 官方建议MySQL下建议关闭 个人建议如果想用SQL防火墙 建议打开
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filter:
stat:
merge-sql: true
slow-sql-millis: 5000
cloud:
sentinel:
transport:
dashboard: 192.168.43.128:9100

seata:
tx-service-group: product-tx-group
service:
vgroupMapping:
product-tx-group: default
config:
type: nacos
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: f6758f0e-1bbe-484f-9247-e9e6d1df5a25
group: SEATA_GROUP
username: ${spring.cloud.nacos.config.username}
password: ${spring.cloud.nacos.config.password}
registry:
type: nacos
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: f6758f0e-1bbe-484f-9247-e9e6d1df5a25
group: SEATA_GROUP
username: ${spring.cloud.nacos.discovery.username}
password: ${spring.cloud.nacos.discovery.password}

mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: AUTO
logic-delete-value: "Y" # 逻辑已删除值(默认为 Y)
logic-not-delete-value: "N" #逻辑未删除值(默认为 N)
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: com.sakura.product.entity

feign:
sentinel:
enabled: true # 开启openfeign中的sentinel
client:
config:
product-service:
logger-level: basic
connect-timeout: 5000 # 连接超时时间,默认2s
read-timeout: 6000 #请求处理超时时间,默认5s
request-interceptors:
- com.sakura.product.interceptor.feign.CustomFeignInterceptor # 开启自定义拦截器

稍微改下ProductServiceImpl 的getUnitPrice方法,让它报错

package com.sakura.product.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sakura.product.entify.Product;
import com.sakura.product.mapper.ProductMapper;
import com.sakura.product.service.ProductService;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;

/**
* @author Sakura
* @date 2023/7/28 16:18
*/
@Service
@Log
public class ProductServiceImpl implements ProductService {

@Autowired
ProductMapper productMapper;

@Override
public Integer getUnitPrice(String productNo) {
Product product = productMapper.selectOne(
Wrappers.<Product>lambdaQuery()
.eq(Product::getProductNo, productNo)
.eq(Product::getStatus, 1));
if (product == null || product.getUnitPrice() == null) {
return -1;
}

int a = 1/0;

return product.getUnitPrice();
}
}

最后我们还需要改一下stock服务

<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.sakura.springcloud</groupId>
<artifactId>springcloudalibaba</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>stock</artifactId>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- nacos服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- nacos配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- 新版本已不再默认开启bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>


<!-- loadbalancer启动器 新版本的springcloud去掉了ribbon自带的负载均衡器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<!-- openfeign启动器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- sentinel启动器 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- seata启动器 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

<!-- 加入mysql连接器 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<!-- mybatis-plus启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

<!-- druid启动器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>

<!-- validation校验启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

</dependencies>
</project>

application.yml

server:
port: 8020

logging:
level:
com.sakura.stock.feign: debug # 这里我们可以单独配置feign的日志级别

spring:
profiles:
active: dev
datasource:
url: jdbc:mysql://192.168.43.128:3306/stock?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: px123456
# 使用druid数据源
druid:
#2.连接池配置
#初始化连接池的连接数量 大小,最小,最大
initial-size: 5
min-idle: 5
max-active: 20
#配置获取连接等待超时的时间
max-wait: 60000
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 30000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: true
test-on-return: false
# 是否缓存preparedStatement,也就是PSCache 官方建议MySQL下建议关闭 个人建议如果想用SQL防火墙 建议打开
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filter:
stat:
merge-sql: true
slow-sql-millis: 5000
cloud:
sentinel:
transport:
dashboard: 192.168.43.128:9100

seata:
tx-service-group: stock-tx-group
service:
vgroupMapping:
stock-tx-group: default
config:
type: nacos
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: f6758f0e-1bbe-484f-9247-e9e6d1df5a25
group: SEATA_GROUP
username: ${spring.cloud.nacos.config.username}
password: ${spring.cloud.nacos.config.password}
registry:
type: nacos
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: f6758f0e-1bbe-484f-9247-e9e6d1df5a25
group: SEATA_GROUP
username: ${spring.cloud.nacos.discovery.username}
password: ${spring.cloud.nacos.discovery.password}

mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: AUTO
logic-delete-value: "Y" # 逻辑已删除值(默认为 Y)
logic-not-delete-value: "N" #逻辑未删除值(默认为 N)
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: com.sakura.stock.entity

feign:
sentinel:
enabled: true # 开启openfeign中的sentinel
client:
config:
product-service:
logger-level: basic
connect-timeout: 5000 # 连接超时时间,默认2s
read-timeout: 6000 #请求处理超时时间,默认5s
request-interceptors:
- com.sakura.stock.interceptor.feign.CustomFeignInterceptor # 开启自定义拦截器


再改下StockServiceImpl,每次都会扣减数量1个

package com.sakura.stock.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sakura.stock.entify.Stock;
import com.sakura.stock.mapper.StockMapper;
import com.sakura.stock.service.StockService;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @author Sakura
* @date 2023/7/28 16:18
*/
@Service
@Log
public class StockServiceImpl implements StockService {

@Autowired
StockMapper stockMapper;

@Override
public Integer getProductNum(String productNo) {
Stock stock = stockMapper.selectOne(
Wrappers.<Stock>lambdaQuery()
.eq(Stock::getProductNo, productNo)
.eq(Stock::getStatus, 1));
if (stock == null || stock.getProductNum() == null) {
return 0;
}
// 修改库存
stock.setProductNum(stock.getProductNum() -1);
stockMapper.updateById(stock);

return stock.getProductNum();
}
}

差点忘了,我们还需要在三个数据库都加上undo_log表,seata的事务回滚都是基于这张表的

DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`branch_id` bigint(20) NOT NULL COMMENT '分支事务ID',
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '全局事务ID',
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '上下文',
`rollback_info` longblob NOT NULL COMMENT '回滚信息',
`log_status` int(11) NOT NULL COMMENT '状态,0正常,1全局已完成',
`log_created` datetime(6) NOT NULL COMMENT '创建时间',
`log_modified` datetime(6) NOT NULL COMMENT '修改时间',
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Compact;

好了我们可以开始测试了
可以看到报错了

 

我们去order表看一下,数据存进去了,因为@GlobalTransactional注释了没有效果

 

再看一下stock表,数量也减1了

 

我们放开@GlobalTransactional,重启服务

 

再次请求还是报错

 

刷新order表,没有

 

刷新stock表,还是99

 

seata全局事务生效了,说明整合成功
好了,这块内容很多,配置一大堆,,但是整合好后使用还是很方便的,目前看就两个注解
一个@GlobalTransactional
还有一个@GlobalLock

@GlobalTransactional:当一个方法被 “@GlobalTransactional” 注解标记时,Seata 将自动为该方法创建一个全局事务上下文,该事务上下文会在方法执行过程中传播到其他被调用的事务性质的方法。如果在全局事务中的任何一个参与者(包括被标记的方法以及其内部调用的其他事务性质的方法)出现异常或者显式地标记回滚,则整个全局事务将回滚,保证了数据的一致性。

@GlobalLock:当一个方法被 “@GlobalLock” 注解标记时,Seata 会在事务发起时尝试获取全局锁。如果全局锁当前没有被其他事务持有,则当前事务可以继续执行被标记的方法。而如果全局锁已经被其他事务持有,则当前事务会等待,直到全局锁被释放为止。这样就保证了同一时刻只有一个事务能够执行被标记的方法,避免了潜在的并发冲突。