文章目錄
- SpringCloud Alibaba:
- 依賴版本補充
- Seata處理分布式事務(AT模式)
- AT模式介紹
- 核心組件介紹
- AT的工作流程:兩階段提交(**2PC**)
- Seata-AT模式使用
- Seata(2.0.0)下載、配置和啟動
- Seata案例實戰
- 前置代碼
- 添加全局注解 @GlobalTransactional
SpringCloud Alibaba:
官方學習文檔(中文): https://spring-cloud-alibaba-group.github.io/github-pages/2022/zh-cn/2022.0.0.0-RC2.html
微服務的中間件介紹與使用
微服務架構體系圖:
依賴版本補充
下面所有代碼中的依賴版本如下:
<properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><hutool.version>5.8.22</hutool.version><lombok.version>1.18.26</lombok.version><druid.version>1.1.20</druid.version><mybatis.springboot.version>3.0.2</mybatis.springboot.version><mysql.version>8.0.11</mysql.version><swagger3.version>2.2.0</swagger3.version><mapper.version>4.2.3</mapper.version><fastjson2.version>2.0.40</fastjson2.version><persistence-api.version>1.0.2</persistence-api.version><spring.boot.test.version>3.1.5</spring.boot.test.version><spring.boot.version>3.2.0</spring.boot.version><spring.cloud.version>2023.0.0</spring.cloud.version><spring.cloud.alibaba.version>2023.0.0.0-RC1</spring.cloud.alibaba.version><knife4j-openapi3.version>4.4.0</knife4j-openapi3.version> </properties>
Seata處理分布式事務(AT模式)
Seata,是一款開源的分布式事務 解決方案,其中有四種模式來去實現分布式事務:
- AT(Auto Transaction):基于兩階段提交(2PC)的自動模式,通過代理數據源自動記錄事務前后的數據快照(UNDO_LOG),實現自動回滾。無代碼侵入,適合大部分業務場景。
- TCC(Try-Confirm-Cancel):需手動編寫 Try(資源預留)、Confirm(提交)、Cancel(回滾)三個接口,適用于對一致性要求極高或業務邏輯復雜的場景(如資金交易)。
- Saga:長事務解決方案,通過狀態機編排服務調用鏈,每個步驟提供正向操作和補償操作,適合跨多個服務的長時間事務(如電商訂單流程)。
- XA:基于數據庫的 XA 協議實現強一致性,依賴數據庫自身的事務能力,適合傳統 XA 兼容場景。
其作用就是在應對一個服務請求中存在多個數據庫的操作時,為了確保數據的 一致性 會對此進行分布式事務處理,
當某一個操作報錯或者超時時就進行回滾,若是程序運行正常便直接提交。避免了對數據庫不一致的問題,這里只以AT模式作為演示,要是想了解其他的模式可以去查看官網
AT模式介紹
在開始介紹之前先聲明一下,seata的AT模式使用起來并不難,甚至只需要添加一個注釋即可,
但是在面試中seata的AT模式會問一些關于概念性的東西所以這里詳細的介紹一下,
若是你只想要知道如何使用直接向下看到Seata-AT模式使用
核心組件介紹
在Seata中四種模式下都會有的三個 核心組件 (TC、TM、RM):
- TC(Transaction Coordinator[事務協調器]):
負責全局事務的 提交 和 回滾,- TM(Transaction Manage[事務管理]):
定義事務的邊界(如@GlobalTransactional
),事務的發起者。(如何實現管理:會對此事務進行生成一個 XID,用來區分全局事務和定義邊界)- RM(Resource Manage[資源管理]):
資源管理器,管理分支事務,與 TC 通信上報狀態并執行指令。(如何實現管理:會對每一個分支生成一個branchId)
架構圖展示來自官網:
AT的工作流程:兩階段提交(2PC)
在 AT模式 下事務的提交/回滾,是由兩個階段完成的:
- 第一階段:
對數據的插入和更新等操作分別記錄數據在 操作前(beforeImage) 和 操作后(afterImage) 的數據,
并將branchId、XID、undoItems{beforeImage{rows、tableName}、afterImage{rows、tableName}}
插入到 UNDO_LOG 表中對下一階段做準備,
同時將業務數據也進行提交。
- 第二階段:
對TC下的分支提供的消息進行判斷是要Commit(提交) 還是 Rollbacked(回滾),
- Commit(提交):提交第一階段的 操作后(afterImage) 的數據進行 Commit(提交),然后在將 UNDO_LOG 中的數據進行刪除
- Rollbacked(回滾):開啟一個事務進行數據(本地數據庫與UNDO_LOG的afterImage)的比較,
- 一致:說明數據未被其他事務修改,允許回滾。
- 不一致:說明數據已被其他事務修改(臟寫),回滾失敗,需人工介入處理。
這一部分是個人參考官網的理解進行概述可能會有一些表達并不到位,大家可以到官網進行參考
Seata-AT模式使用
Seata(2.0.0)下載、配置和啟動
- 下載地址:Seata的官網,找到Seata(2.0.0)的版本,下載資源
seata-server-2.0.0.zip
,即可- Seata的配置:要想使用Seata模式的話還要進行Seata的專屬庫的創建,
-- -------------------------------- The script used when storeMode is 'db' -------------------------------- -- the table to store GlobalSession data CREATE DATABASE seata; USE seata; 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` (`xid`) ) 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庫,
- 修改Seata的
seata/conf/application.yml
配置文件(注意:!!!這里最好先備份一下此配置文件,預防配置出錯!!!)# 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.xmlfile:path: ${log.home:${user.home}/logs/seata}extend:logstash-appender:destination: 127.0.0.1:4560kafka-appender:bootstrap-servers: 127.0.0.1:9092topic: logback_to_logstash console:user:username: seatapassword: seata#------------新增的內容--------------- seata:config:type: nacosnacos: #nacos的配置server-addr: 127.0.0.1:8848 namespace:group: SEATA_GROUP #后續自己在nacos里面新建,不想新建SEATA_GROUP,就寫DEFAULT_GROUPusername: nacospassword: nacosregistry:type: nacosnacos: #服務注冊 application: seata-serverserver-addr: 127.0.0.1:8848group: SEATA_GROUP #后續自己在nacos里面新建,不想新建SEATA_GROUP,就寫DEFAULT_GROUPnamespace:cluster: defaultusername: nacospassword: nacosstore:mode: dbdb:datasource: druiddb-type: mysqldriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueuser: root #寫自己的數據庫的用戶名password: yourPassword #數據庫密碼min-conn: 10max-conn: 100#--------這幾張表就是上面創建的Seata專屬庫中的表-----------global-table: global_table branch-table: branch_table lock-table: lock_table distributed-lock-table: distributed_lock #--------------------------------------------------query-limit: 1000max-wait: 5000#——--------------------# server:# service-port: 8091 #If not configured, the default is '${server.port} + 1000' security:secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017tokenValidityInMilliseconds: 1800000ignore:urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**
- 啟動Seata:到
seata\bin\
目錄下運行seata-server.bat
- 測試:訪問默認網址http://localhost:7091/,賬號密碼默認:Seata
Seata案例實戰
前置代碼
- sql準備:
建seata order庫+建t order表+undo log表CREATE DATABASE seata_order; USE 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; -- for AT mode you must to init this sql for you business database. the seata server not need it. CREATE TABLE IF NOT EXISTS `undo_log` (`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) ) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table'; ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
建seata storage庫+建t storage 表+undo log表
CREATE DATABASE seata_storage; USE 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 t_storage(`id`,`product_id`,`total`,`used`,`residue`)VALUES('1','1','100','0','100'); SELECT * FROM t_storage; -- for AT mode you must to init this sql for you business database. the seata server not need it. CREATE TABLE IF NOT EXISTS `undo_log` (`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table'; ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
自建seata account庫+建t account 表+undo log表
create database seata_account; use 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=2 DEFAULT CHARSET=utf8; INSERT INTO t_account(`id`,`user_id`,`total`,`used`,`residue`)VALUES('1','1','1000','0','1000'); SELECT * FROM t_account; -- for AT mode you must to init this sql for you business database. the seata server not need it. CREATE TABLE IF NOT EXISTS `undo_log` (`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table'; ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
- 添加兩個Openfeign的接口:
賬戶對外暴漏的接口
@FeignClient(value = "seata-account-service") public interface SeataAccountFeignApi {//扣減賬戶余額@PostMapping("/account/decrease")ResultData decrease(@RequestParam("userId") Long userId, @RequestParam("money") Long money); }
庫存對外暴漏的接口
@FeignClient(value = "seata-storage-service") public interface SeataStorageFeignApi { // 扣減庫存@PostMapping(value = "/storage/decrease")ResultData decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count); }
- 訂單服務的創建
pom依賴導入
<dependencies><!-- nacos --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--alibaba-seata--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId></dependency><!--openfeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--loadbalancer--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!--cloud-api-commons--><dependency><groupId>com.chyb.cloud</groupId><artifactId>cloud-api-commons</artifactId><version>1.0-SNAPSHOT</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><!--SpringBoot集成druid連接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency><!-- Swagger3 調用方式 http://你的主機IP地址:5555/swagger-ui/index.html --><dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId></dependency><!--mybatis和springboot整合--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!--MyBatisPlus:這里大家可以不引入我只是為了做一個MyBatisPlus的測試就導入此依賴了--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus</artifactId><version>3.5.11</version></dependency><!--Mysql數據庫驅動8 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--persistence--><dependency><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId></dependency><!--通用Mapper4--><dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency><!-- fastjson2 --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version><scope>provided</scope></dependency><!--test--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
yml配置文件啟動類
server:port: 2001
spring:application:name: seata-order-servicecloud:nacos:discovery:server-addr: localhost:8848 #Nacos服務注冊中心地址# ==========applicationName + druid-mysql8 driver===================datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueusername: rootpassword: 123456
# ========================mybatis===================
mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.chyb.cloud.entitiesconfiguration:map-underscore-to-camel-case: true
# ========================seata===================
seata:registry:type: nacosnacos:server-addr: 127.0.0.1:8848namespace: ""group: SEATA_GROUPapplication: seata-servertx-service-group: default_tx_group # 事務組,由它獲得TC服務的集群名稱service:vgroup-mapping: # 點擊源碼分析default_tx_group: default # 事務組與TC服務集群的映射關系data-source-proxy-mode: AT
logging:level:io:seata: info
@SpringBootApplication
@EnableDiscoveryClient/*服務注冊*/
@EnableFeignClients/*openfeign*/
@MapperScan("com.chyb.cloud.mapper")
public class SeataOrderMainApp2001 {public static void main(String[] args) {SpringApplication.run(SeataOrderMainApp2001.class, args);}}
業務代碼:
@RestController
@RequestMapping("/order")
public class OrderController {@Resourceprivate OrderService orderService;@GetMapping("/create")public ResultData create(Order order){orderService.create(order);return ResultData.success("訂單創建成功!");}
}
@Slf4j
@Service
public class OrderServiceImpl implements OrderService
{@Resourceprivate OrderMapper orderMapper;@Resource//訂單微服務通過OpenFeign去調用庫存微服務private SeataStorageFeignApi storageFeignApi;@Resource//訂單微服務通過OpenFeign去調用賬戶微服務private SeataAccountFeignApi accountFeignApi;@Override
// @GlobalTransactional(name = "chyb-create-order",rollbackFor = Exception.class) //ATpublic void create(Order order) {//xid檢查String xid = RootContext.getXID();//1. 新建訂單log.info("==================>開始新建訂單"+"\t"+"xid_order:" +xid);//訂單狀態status:0:創建中;1:已完結order.setStatus(0);int result = orderMapper.insertSelective(order);//插入訂單成功后獲得插入mysql的實體對象Order orderFromDB = null;if(result > 0){orderFromDB = orderMapper.selectOne(order);//orderFromDB = orderMapper.selectByPrimaryKey(order.getId());log.info("-------> 新建訂單成功,orderFromDB info: "+orderFromDB);System.out.println();//2. 扣減庫存log.info("-------> 訂單微服務開始調用Storage庫存,做扣減count");storageFeignApi.decrease(orderFromDB.getProductId(), orderFromDB.getCount());log.info("-------> 訂單微服務結束調用Storage庫存,做扣減完成");System.out.println();//3. 扣減賬號余額log.info("-------> 訂單微服務開始調用Account賬號,做扣減money");accountFeignApi.decrease(orderFromDB.getUserId(), orderFromDB.getMoney());log.info("-------> 訂單微服務結束調用Account賬號,做扣減完成");System.out.println();//4. 修改訂單狀態//訂單狀態status:0:創建中;1:已完結log.info("-------> 修改訂單狀態");orderFromDB.setStatus(1);Example whereCondition=new Example(Order.class);Example.Criteria criteria=whereCondition.createCriteria();criteria.andEqualTo("userId",orderFromDB.getUserId());criteria.andEqualTo("status",0);int updateResult = orderMapper.updateByExampleSelective(orderFromDB, whereCondition);log.info("-------> 修改訂單狀態完成"+"\t"+updateResult);log.info("-------> orderFromDB info: "+orderFromDB);}System.out.println();log.info("==================>結束新建訂單"+"\t"+"xid_order:" +xid);}
}
import com.chyb.cloud.entities.Order;
import tk.mybatis.mapper.common.Mapper;
public interface OrderMapper extends Mapper<Order> {
}
- 賬戶服務和庫存服務
這兩個就直接寫業務邏輯,其他的如實體類大家就自己創建
/*庫存*/
@Service
@Slf4j
public class StorageServiceImpl implements StorageService {@Autowiredprivate StorageMapper storageMapper;@Overridepublic void decrease(Long productId, Integer count) {log.info("------->storage-service中扣減庫存開始");/*這里為了方便我直接使用了MyBatisPlus*/LambdaUpdateWrapper<Storage> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper/*條件*/.eq(Storage::getProductId, productId)/*字段自增*/.setIncrBy(Storage::getUsed, count)/*字段自減*/.setDecrBy(Storage::getResidue, count);int update = storageMapper.update(updateWrapper);log.info("------->storage-service中扣減庫存結束");}
}
/*賬戶*/
@Service
@Slf4j
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountMapper accountMapper;@Overridepublic void decrease(Long userId, Long money) {log.info("------->account-service中扣減賬戶余額開始");LambdaUpdateWrapper<Account> updateWrapper = new LambdaUpdateWrapper<>();updateWrapper.eq(Account::getUserId, userId).setIncrBy(Account::getUsed, money).setDecrBy(Account::getTotal, money);accountMapper.update(updateWrapper);
// myTimeOut();
// int a = 10 / 0;}/*** 模擬超時異常,全局事務回滾*/private static void myTimeOut() {try {TimeUnit.SECONDS.sleep(65);} catch (InterruptedException e) {e.printStackTrace();}}
}
運行測試是否可以運行若是可以便是若是是在不行可以直接使用gitee將代碼克隆下來網址為:https://gitee.com/banhuayue/springCloud-Alibaba-code.git
對應運行的代碼是:2001、2002、2003,其中cloud-api-commons是Openfeign對外暴漏的API接口的demo
添加全局注解 @GlobalTransactional
訂單service中的方法上添加一個此注解
@GlobalTransactional
,接口
// 屬性解釋name是查看Seata后臺的transactionName,rollbackFor為指定AT模式(默認是AT模式)@GlobalTransactional(name = "chyb-create-order",rollbackFor = Exception.class)
開啟超時方法
myTimeOut
測試:
啟動三個服務訪問訂單的Swagger測試接口order-controller這里就不展示效果了 (。?_?。)ノ懶~~~ ,大家可以在超時之前查看:三個服務的數據庫、還有對應的UNDO_LOG表、Seata的后臺全局信息和事務信息
上述大部分代碼以上傳到gitee:https://gitee.com/banhuayue/springCloud-Alibaba-code.git
筆記參考來自尚硅谷