SpringCloud学习笔记 - 分布式系统全局事务管理 - Seata1.5.2+Nacos+OpenFeign
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 = InnoDBDEFAULT CHARSET = utf8mb4;-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(`branch_id` BIGINT NOT NULL,`xid` VARCHAR(128) NOT NULL,`transaction_id` BIGINT,`resource_group_id` VARCHAR(32),`resource_id` VARCHAR(256),`branch_type` VARCHAR(8),`status` TINYINT,`client_id` VARCHAR(64),`application_data` VARCHAR(2000),`gmt_create` DATETIME(6),`gmt_modified` DATETIME(6),PRIMARY KEY (`branch_id`),KEY `idx_xid` (`xid`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(`row_key` VARCHAR(128) NOT NULL,`xid` VARCHAR(128),`transaction_id` BIGINT,`branch_id` BIGINT NOT NULL,`resource_id` VARCHAR(256),`table_name` VARCHAR(32),`pk` VARCHAR(36),`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',`gmt_create` DATETIME,`gmt_modified` DATETIME,PRIMARY KEY (`row_key`),KEY `idx_status` (`status`),KEY `idx_branch_id` (`branch_id`),KEY `idx_xid_and_branch_id` (`xid` , `branch_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;CREATE TABLE IF NOT EXISTS `distributed_lock`
(`lock_key` CHAR(20) NOT NULL,`lock_value` VARCHAR(20) NOT NULL,`expire` BIGINT,primary key (`lock_key`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
- 修改seata - config下的application.yaml配置文件,使用nacos作为服务注册发现中心和配置中心,其他详细配置可以参照seata提供的模板application.example.yml
3.1. 在nacos中新建一个命名空间,并记录下空间ID
3.2. 修改application.yml:主要修改项seata 下的 config、registry、store
server:port: 7091spring:application:name: seata-serverlogging:config: classpath:logback-spring.xmlfile:path: ${user.home}/logs/seataextend:logstash-appender:destination: 127.0.0.1:4560kafka-appender:bootstrap-servers: 127.0.0.1:9092topic: logback_to_logstashconsole:user:username: seatapassword: seataseata:config:# support: nacos, consul, apollo, zk, etcd3type: nacosnacos:server-addr: localhost:8848namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50dgroup: SEATA_GROUPusername: nacospassword: nacos#data-id: seataServer.properties registry:# support: nacos, eureka, redis, zk, consul, etcd3, sofatype: nacosnacos:application: seata-serverserver-addr: localhost:8848group: SEATA_GROUPnamespace: 704a579f-f1ef-45fd-818e-4e5c113ca50dcluster: defaultusername: nacospassword: nacos##if use MSE Nacos with auth, mutex with username/password attribute#access-key: ""#secret-key: ""store:# support: file 、 db 、 redismode: dbdb:datasource: druiddb-type: mysqldriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaiuser: rootpassword: 123456min-conn: 1max-conn: 20global-table: global_tablebranch-table: branch_tablelock-table: lock_tabledistributed-lock-table: distributed_lockquery-limit: 100max-wait: 10000server:service-port: 8091 #If not configured, the default is '${server.port} + 1000'max-commit-retry-timeout: -1max-rollback-retry-timeout: -1rollback-retry-timeout-unlock-enable: falseenable-check-auth: trueenable-parallel-request-handle: trueretry-dead-threshold: 130000xaer-nota-retry-timeout: 60000undo:log-save-days: 7log-delete-period: 86400000session:branch-async-queue-size: 5000 #branch async remove queue sizeenable-branch-async-remove: false #enable to asynchronous remove branchSessionsecurity:secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017tokenValidityInMilliseconds: 1800000ignore: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: 2002spring:application:name: seata-storage-servicecloud:nacos:discovery:server-addr: localhost:8848datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata_storage?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456mybatis:mapper-locations: classpath:mapper/*.xml# seata 配置
seata:# seata配置中心tx-service-group: default_tx_groupconfig:type: nacosnacos:namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50d# nacos配置中心地址server-addr: localhost:8848# 分组group: SEATA_GROUP# nacos的账号和密码userName: nacospassword: nacos# seata的注册中心registry:type: nacosnacos:application: seata-serverserver-addr: localhost:8848namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50duserName: nacospassword: nacosservice: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; //产品IDprivate 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">updatet_storagesetused = used + #{count}, residue = residue - #{count}whereproduct_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);@Resourceprivate StorageDao storageDao;@Overridepublic 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 {@Resourceprivate 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: 2003spring:application:name: seata-account-servicecloud:nacos:discovery:server-addr: localhost:8848datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata_account?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456mybatis:mapper-locations: classpath:mapper/*.xml# seata 配置
seata:# seata配置中心tx-service-group: default_tx_groupconfig:type: nacosnacos:namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50d# nacos配置中心地址server-addr: localhost:8848# 分组group: SEATA_GROUP# nacos的账号和密码userName: nacospassword: nacos# seata的注册中心registry:type: nacosnacos:application: seata-serverserver-addr: localhost:8848namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50duserName: nacospassword: nacosservice: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; //用户IDprivate 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">updatet_accountsetresidue = residue - #{money}, used = used + #{money}whereuser_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);@Resourceprivate AccountDao accountDao;@Overridepublic 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 {@Resourceprivate 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: 2001spring:application:name: seata-order-servicecloud:nacos:discovery:server-addr: localhost:8848datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456mybatis:mapper-locations: classpath:mapper/*.xml# seata 配置
seata:# seata配置中心tx-service-group: default_tx_groupconfig:type: nacosnacos:namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50d# nacos配置中心地址server-addr: localhost:8848# 分组group: SEATA_GROUP# nacos的账号和密码userName: nacospassword: nacos# seata的注册中心registry:type: nacosnacos:application: seata-serverserver-addr: localhost:8848namespace: 704a579f-f1ef-45fd-818e-4e5c113ca50duserName: nacospassword: nacosservice: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 修改订单状态,从零改为1public 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 = 1where 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 {@Resourceprivate OrderDao orderDao;@Resourceprivate StorageService storageService;@Resourceprivate AccountService accountService;@Override@GlobalTransactionalpublic 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 {@Resourceprivate 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 {@Resourceprivate OrderDao orderDao;@Resourceprivate StorageService storageService;@Resourceprivate AccountService accountService;@Override@GlobalTransactionalpublic 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账户服务的情况下
发送请求:这时数据库应该不会创建任何数据加粗样式
相关文章:
SpringCloud学习笔记 - 分布式系统全局事务管理 - Seata1.5.2+Nacos+OpenFeign
1. Seata 是什么? 由于业务和技术的发展,单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证, 但是全…...
LeetCode190_190. 颠倒二进制位
LeetCode190_190. 颠倒二进制位 一、描述 颠倒给定的 32 位无符号整数的二进制位。 提示: 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型&…...
atomic 原子操作
atomic 原子操作前言atomic_t定义内核中的实现armv7的实现armv8的实现Exclusive monitor实现所处的位置External exclusive monitorAtomic指令的支持QA前言 修改一个变量会经过读、修改、写的操作序列。但有时该操作序列在执行完毕前会被其他任务或事件打断。 比如在多CPU体系…...
DataGear 制作基于Vue前端框架渲染的数据可视化看板
DataGear 在4.3.0版本新增了dg-dashboard-code特性,并在4.4.0版本进行了改进和增强,结合看板API,可以很方便地制作完全由Vue、React等前端框架渲染的数据可视化看板。 本文基于Vue2、Element UI前端框架的<el-container>、<el-head…...
JavaFX Scene Builder 下载安装
JavaFX Scene Builder 下载安装1. Scene Builder官网下载2. 安装3. Oracle官网下载Scene Builder 是创建漂亮的用户界面,并将您的设计转化为交互式原型。Scene Builder通过创建可以直接在JavaFX应用程序中使用的用户界面,缩小了设计人员和开发人员之间的…...
dva( 轻量级的应用框架 )
dva核心知识与实战运用 dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架! 介绍 | DvaJS 易学易用,仅有 6 个 api&…...
数据结构:堆的实现与建堆时间复杂度分析
目录 前言 一.堆的介绍 1.堆的本质 2.堆的分类 二.堆的实现(以小根堆为例) 1.关于二叉树的两组重要结论: 2.堆的物理存储结构框架(动态数组的简单构建) 3. 堆元素插入接口(以小根堆为例) 堆尾元素向上调整的算法接口: 4.堆元素插入接口测试 5.堆元素插入…...
对“车辆销售配置器”的认识与理解
概述 中国汽车市场转为存量阶段后,各车企开始从”以产品为中心“转型到”以客户为中心“,产品的个性化配置需求日益丰富。随着竞争的加剧,车企们不仅要提供出色的产品,而且需要提供更加个性化的产品配置和服务,例如&am…...
Linux编译器——gcc/g++(预处理、编译、汇编、链接)
目录 0.程序实现的两大环境 1.gcc如何完成 预处理 编译 汇编 链接 2.动态库与静态库 对比二者生成的文件大小 3. gcc常用选项 0.程序实现的两大环境 任何一个C程序的实现都要经过翻译环境与执行环境。 在翻译环境中又分为4个部分,预编译、编译、汇编与链…...
Java 操作图片进行缩放旋转翻转加水印
1 纯原生手写图片操作工具类 import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; import java.awt.image.BufferedImage; public class RotateImageUtil {public static BufferedImage rotateImage(BufferedImage bu…...
不能去演唱会现场就多听听耳机里的他们,教你用python来实现一个音乐播放器
前言 最近可以说大麦网很知名了,哈哈还有好多想要用Python来搞抢票脚本的 怎么说呢也不是不行,但是咱今天可不是来搞这个的,我可不抢票,抢了都去不了,上班搞钱啊铁铁们 咱就是说去不了现场,就多听听手机…...
CLion Debug 调试 Makefile 构建的 C 语言程序断点不起作用
最近在研究 jattach,打算在本地调试项目,发现 CLion 可以正常编译运行代码,却无法断点 Debug。由于笔者对 C/C 项目不熟悉,在此记录研究过程中遇到的一些基本问题与解决方法。 文章目录解决方式尝试过的手段【未解决】找 Native D…...
·神经网络
目录11神经网络demo112神经网络demo213神经网络demo320tensorflow2.0 安装教程,所有安装工具(神经网络)21神经网络-线性回归- demo122神经网络-线性回归- demo228神经网络-多层感知- demo1目录11神经网络demo1 package com.example.xxx; import java.ut…...
【Java 多线程学习】
多线程学习多线程1. 并行与并发2.进程和线程3. *****多线程的实现方式3.1 继承Thread类的方式进行实现3.2 实现Runnable接口方式进行实现3.3 利用Callable和Future接口方式实现3.4 设置获取线程名字4.获得线程对象5.线程休眠6.线程调度[线程的优先级]7.后台线程/守护线程多线程…...
【计算机考研408】快速排序的趟数问题 + PAT 甲级 7-2 The Second Run of Quicksort
前言 该题还未加入PAT甲级题库中,可以通过购买2022年秋季甲级考试进行答题,纯考研题改编 快速排序 常考的知识点 快速排序是基于分治法快速排序是所有内部排序算法中平均性能最优的排序算法快速排序是一种不稳定的排序算法快速排序算法中,…...
CSS-Grid(网格)布局
前言 之前HTML 页面的布局基本上都是通过 Flexbox 来实现的,能轻松的解决复杂的 Web 布局。 现在又出现了一个构建 HTML 最佳布局体系的新竞争者。就是强大的CSS Grid 布局。 grid和flex区别是什么?适用什么场景? Flexbox 是一维布局系统&am…...
软件测试4
一 form表单标签 1.form表单标签里面就是所有用户填写的表单数据; action“xxx.py”把表单数据提交给哪一个后台程序去处理 method“post” 传递数据时候的方式方法,post代表隐式提交数据、get明文传送数据 2.input标签的type类型 type“text” 普通的输…...
996的压力下,程序员还有时间做副业吗?
996怎么搞副业? 这个问题其实蛮奇怪的:996的压力下,怎么会还想着搞副业呢? 996还想搞副业的原因有哪些? 大家对于996应该都不陌生,总结就是一个字:忙。 996的工作性质就是加班,就…...
每日学术速递3.1
CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Directed Diffusion: Direct Control of Object Placement through Attention Guidance 标题:定向扩散:通过注意力引导直接控制物体放置 作者:…...
金融行业数据模型
一、Teradata FS-LDM Teradata 公司基于金融业务发布的FS-LDM(Financial Servies Logical Data Model) 十大主题:当事人、产品、协议、事件、资产、财务、机构、地域、营销、渠道。 1、当事人(Party) 银行所服务的任…...
【面试题】2023前端vue面试题及答案
Vue3.0 为什么要用 proxy?在 Vue2 中, 0bject.defineProperty 会改变原始数据,而 Proxy 是创建对象的虚拟表示,并提供 set 、get 和 deleteProperty 等处理器,这些处理器可在访问或修改原始对象上的属性时进行拦截&…...
(哈希查找)leetcode128. 最长连续序列
文章目录一、题目1、题目描述2、基础框架3、原题链接二、解题报告1、思路分析2、时间复杂度3、代码详解三、本题小知识一、题目 1、题目描述 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。…...
js中splice方法和slice方法
splice方法用来操作数组splice(startIndex,deleteNum,item1,....,)此操作会改变原数组。删除数组中元素参数解释:startIndex为起始index索引。deleteNum为从startIndex索引位置开始需要删除的个数。分三种情况:没有传第三个参数的情况下,dele…...
c++ argparse
需求 c程序传参数,像python中argparse一样方便。 方法1 用gflags 参考https://heroacool.blog.csdn.net/?typeblog git clone https://github.com/gflags/gflags cd gflags # 进入项目文件夹 cmake . # 使用 cmake 编译生成 Makefile 文件 make -j 24 # make 编…...
内大892复试真题16年
内大892复试真题16年 1. 输出三个数中较大数2. 求两个数最大公约数与最小公倍数3. 统计字符串中得字符个数4. 输出菱形5. 迭代法求平方根6. 处理字符串(逆序、进制转换)7. 寻找中位数8. 输入十进制输出n进制1. 输出三个数中较大数 问题 代码 #include <iostream>usin…...
面试题 05.02. 二进制数转字符串
二进制数转字符串。给定一个介于0和1之间的实数(如0.72),类型为double,打印它的二进制表达式。如果该数字无法精确地用32位以内的二进制表示,则打印“ERROR”。 示例1: 输入:0.625输出:"0…...
MySQL数据更新操作
文章目录前言添加数据插入数据删除数据修改数据前言 提示:这里可以添加本文要记录的大概内容: 数据更新有两种办法: 1:使用数据可视化工具操作 2:SQL语句 添加数据 前面的添加数据命令一次只能插入一条记录。如果想…...
C# 封装
修正bug之前总是要考虑是什么导致了这个bug,并花些时间了解发生了什么。增加打印输出行的语句可能是一个很有效的调试工具。增加语句来打印诊断信息时,要使用Debug.WriteLine。构造器是CLR第一次创建一个新对象实例时调用的方法。字符串插值会让字符串拼…...
每日学术速递3.2
CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Interactive Segmentation as Gaussian Process Classification(CVPR 2023) 标题:作为高斯过程分类的交互式分割 作者:Minghao Zhou, Hong Wang, Qian Zha…...
PCBA方案设计——LCD体重电子秤方案
体重秤,一种测量体重的电子秤,与最近很火的体脂秤来比来说,他是的功能能就有点单一了,只能测量体重,而体脂秤可以精准抓取测量体脂体重等一系列的数据,功能更为多样,但相比之下体重秤的功能简单…...
做的比较好的b2b网站/磁力链 ciliba
在本视图中,每一个连接到数据库实例中的 session都拥有一条记录。包括用户 session及后台进程如 DBWR, LGWR, arcchiver等等。 V$SESSION中的常用列 V$SESSION是基础信息视图,用于找寻用户 SID或 SADDR。不过,它也有一…...
北京网站建设学校/百度关键词推广公司哪家好
结合 category 工作原理分析 OC2.0 中的 runtime 绝大多数 iOS 开发者在学习 runtime 时都阅读过 runtime.h 文件中的这段代码: struct objc_class {Class isa OBJC_ISA_AVAILABILITY;#if !__OBJC2__Class super_class OBJC2_UNAVAILA…...
平面设计接单的网站/友情链接英语
QVector与QList 数据量比较小时(小于1000项),没什么区别。 如果你需要开辟连续的内存空间存储,或者你的元素远比一个指针大,可以用QVector 按照索引获取元素时,QList比较快 追加插入元素时QVector大概快个百分之5 查找元素时QLis…...
企业网站的公司和产品信息的介绍与网络营销关系/深圳网站制作推广
题目:原题链接(中等) 标签:广度优先搜索、深度优先搜索 解法时间复杂度空间复杂度执行用时Ans 1 (Python)O(N84)O(N8^4)O(N84)O(N)O(N)O(N)36ms (79.65%)Ans 2 (Python)Ans 3 (Python) 解法一: class Solution:_CHA…...
网站怎么才能被百度收录/爱站关键词挖掘old
在android中的文件放在不同位置,它们的读取方式也有一些不同。 一、资源文件的读取: 1) 从resource的raw中读取文件数据: ?1234567891011121314151617181920212223String res "";try{//得到资源中的Raw数据流InputStream in get…...
电子商务网站管理系统完美版/网站外链平台
1、获取体素在全局坐标系下的坐标(x,y,z),根据ICP配准得到的变换矩阵,将体素的坐标从全局坐标系转换到相机坐标系; 2、根据相机的内参矩阵,转换到图像坐标系,得到体素所在的图像坐标(u,v&#x…...