• 【微服务 32】你为Spring Cloud整合Seata、Nacos实现分布式事务案例跑不起来苦恼过吗(巨细排坑版)【云原生】


    一、前言

    至此,微服务系列正式开启分布式事务篇;

    捎带一提,seata官方给的案例是真的******,版本之间的差异并未说明,据悉官方案例属于政治任务!在开启案例之前,博主和网友们踩过一些坑,具体见文章:

    1. can not get cluster name in registry config ‘service.vgroupMapping.xx‘, please make sure registry问题解决
    2. Seata Failed to get available servers: endpoint format should like ip:port 报错原因/解决方案汇总版(看完本文必解决问题)
    3. Seata json decode exception, Cannot construct instance of java.time.LocalDateTime报错原因/解决方案最全汇总版
    4. 【微服务 31】超细的Spring Cloud 整合Seata实现分布式事务(排坑版)

    本文基于AT模式 +搭建SpringCloud 集成 Seata + Nacos 实现分布式事务的案例;

    版本信息如下:

    <properties>
        <spring-boot.version>2.4.2spring-boot.version>
        <spring-cloud.version>2020.0.1spring-cloud.version>
        <spring-cloud-alibaba.version>2021.1spring-cloud-alibaba.version>
        <mysql.version>8.0.22mysql.version>
    properties>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    二、Seata简介

    在这里插入图片描述

    Seata 是一款开源的分布式事务解决方案,全称:Simple extensiable autonomous transaction architecture;意思是:简单的、可扩展的、自治的事务架构。Seata致力于提供高性能和简单易用的分布式事务服务;Seata 为用户提供了 AT、TCC、SAGA 和 XA 四种分布式事务模式;

    1> AT模式:

    • 提供无侵入自动补偿的事务模式,目前已支持MySQL、Oracle、PostgreSQL、TiDB 和 MariaDB;

    2> TCC 模式:

    • 支持 TCC 模式并可与 AT 混用,灵活度更高;

    3> SAGA 模式:

    • 为长事务提供有效的解决方案,提供编排式与注解式(开发中);

    4> XA 模式:

    • 支持已实现 XA 接口的数据库的 XA 模式,目前已支持MySQL、Oracle、TiDB和MariaDB;

    官方文档https://seata.io/zh-cn/docs/overview/what-is-seata.html

    三个角色

    1> TC (Transaction Coordinator) - 事务协调者

    • 维护全局和分支事务的状态,驱动全局事务提交或回滚。

    2> TM (Transaction Manager) - 事务管理器

    • 定义全局事务的范围:开始全局事务、提交或回滚全局事务。

    3> RM (Resource Manager) - 资源管理器

    • 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

    案例中三个角色的交互

    在这里插入图片描述

    三、SpringCloud 集成Seata(注册和配置均采用Nacos)

    本文基于AT模式 +搭建SpringCloud 集成 Seata + Nacos 实现分布式事务的案例;

    整体项目目录包括四个Module,分别为:trade-center、stock-service、order-service、account-service。
    在这里插入图片描述

    用例为用户购买商品的业务逻辑,整个业务逻辑由3个微服务提供支持,其中:

    • 仓储服务(stock-service):对给定的商品扣除仓储数量。
    • 订单服务(order-service):根据采购需求创建订单。
    • 帐户服务(account-service):从用户帐户中扣除余额。

    此外,trade-center为交易中心,是处理用户请求的入口;

    0、业务架构图

    在这里插入图片描述

    1、MySQL数据库信息

    必须要使用具有InnoDB引擎的MySQL;也就是说数据库的引擎要支持事务,因为AT模式底层是依赖数据库事务实现的分布式事务。

    在案例中,仓储服务(stock-service)、订单服务(order-service)、帐户服务(account-service) 这三个服务对应三个数据库,为了方便测试,我们只创建一个数据库并配置3个数据源。

    0)一键执行所有SQL

    1> 案例中seata-client相关的所有业务库、业务表、undo_log表创建SQL;
    2> seata-server保存数据的表;

    #Account
    DROP SCHEMA IF EXISTS seata_account;
    CREATE SCHEMA seata_account;
    USE seata_account;
    
    CREATE TABLE `account_tbl`
    (
        `id`      INT(11) NOT NULL AUTO_INCREMENT,
        `user_id` VARCHAR(255) DEFAULT NULL,
        `money`   INT(11) DEFAULT 0,
        PRIMARY KEY (`id`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8;
    
    INSERT INTO account_tbl (id, user_id, money)
    VALUES (1, '1001', 10000);
    INSERT INTO account_tbl (id, user_id, money)
    VALUES (2, '1002', 10000);
    
    CREATE TABLE `undo_log`
    (
        `id`            bigint(20) NOT NULL AUTO_INCREMENT,
        `branch_id`     bigint(20) NOT NULL,
        `xid`           varchar(100) NOT NULL,
        `context`       varchar(128) NOT NULL,
        `rollback_info` longblob     NOT NULL,
        `log_status`    int(11) NOT NULL,
        `log_created`   datetime     NOT NULL,
        `log_modified`  datetime     NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
    #Order
    DROP SCHEMA IF EXISTS seata_order;
    CREATE SCHEMA seata_order;
    USE seata_order;
    
    CREATE TABLE `order_tbl`
    (
        `id`             INT(11) NOT NULL AUTO_INCREMENT,
        `user_id`        VARCHAR(255) DEFAULT NULL,
        `commodity_code` VARCHAR(255) DEFAULT NULL,
        `count`          INT(11) DEFAULT '0',
        `money`          INT(11) DEFAULT '0',
        PRIMARY KEY (`id`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8;
    
    CREATE TABLE `undo_log`
    (
        `id`            bigint(20) NOT NULL AUTO_INCREMENT,
        `branch_id`     bigint(20) NOT NULL,
        `xid`           varchar(100) NOT NULL,
        `context`       varchar(128) NOT NULL,
        `rollback_info` longblob     NOT NULL,
        `log_status`    int(11) NOT NULL,
        `log_created`   datetime     NOT NULL,
        `log_modified`  datetime     NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
    #Stock
    DROP SCHEMA IF EXISTS seata_stock;
    CREATE SCHEMA seata_stock;
    USE seata_stock;
    
    CREATE TABLE `stock_tbl`
    (
        `id`             INT(11) NOT NULL AUTO_INCREMENT,
        `commodity_code` VARCHAR(255) DEFAULT NULL,
        `count`          INT(11) DEFAULT '0',
        PRIMARY KEY (`id`),
        UNIQUE KEY `commodity_code` (`commodity_code`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8;
    
    
    INSERT INTO stock_tbl (id, commodity_code, count)
    VALUES (1, '2001', 1000);
    
    CREATE TABLE `undo_log`
    (
        `id`            bigint(20) NOT NULL AUTO_INCREMENT,
        `branch_id`     bigint(20) NOT NULL,
        `xid`           varchar(100) NOT NULL,
        `context`       varchar(128) NOT NULL,
        `rollback_info` longblob     NOT NULL,
        `log_status`    int(11) NOT NULL,
        `log_created`   datetime     NOT NULL,
        `log_modified`  datetime     NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
    -- the table to store GlobalSession data
    DROP SCHEMA IF EXISTS seata_server;
    CREATE SCHEMA seata_server;
    USE seata_server;
    
    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_gmt_modified_status` (`gmt_modified`, `status`),
        KEY `idx_transaction_id` (`transaction_id`)
        ) ENGINE = InnoDB
        DEFAULT CHARSET = utf8;
    
    -- the table to store BranchSession data
    CREATE TABLE IF NOT EXISTS `branch_table`
    (
        `branch_id`         BIGINT       NOT NULL,
        `xid`               VARCHAR(128) NOT NULL,
        `transaction_id`    BIGINT,
        `resource_group_id` VARCHAR(32),
        `resource_id`       VARCHAR(256),
        `branch_type`       VARCHAR(8),
        `status`            TINYINT,
        `client_id`         VARCHAR(64),
        `application_data`  VARCHAR(2000),
        `gmt_create`        DATETIME(6),
        `gmt_modified`      DATETIME(6),
        PRIMARY KEY (`branch_id`),
        KEY `idx_xid` (`xid`)
        ) ENGINE = InnoDB
        DEFAULT CHARSET = utf8;
    
    -- 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),
        `gmt_create`     DATETIME,
        `gmt_modified`   DATETIME,
        PRIMARY KEY (`row_key`),
        KEY `idx_branch_id` (`branch_id`)
        ) ENGINE = InnoDB
        DEFAULT CHARSET = utf8;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155

    1)undo_log 事务回滚日志表

    SEATA AT 模式需要 undo_log 表,用于事务回滚使用。所以上面三个服务每个服务都要有一个undo_log表。表结构如下:
    在这里插入图片描述
    建表SQL:

    CREATE TABLE `undo_log`
    (
        `id`            bigint(20) NOT NULL AUTO_INCREMENT,
        `branch_id`     bigint(20) NOT NULL,
        `xid`           varchar(100) NOT NULL,
        `context`       varchar(128) NOT NULL,
        `rollback_info` longblob     NOT NULL,
        `log_status`    int(11) NOT NULL,
        `log_created`   datetime     NOT NULL,
        `log_modified`  datetime     NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    **undo_log表的结构是从哪里找的?为什么它是这个?**看了一些文章并没有说这个,本文简要说明一下;

    undo_log表结构从哪里找?

    1> 在GitHub中找到seata的源码,选择响应版本的代码分支:

    在这里插入图片描述

    注意源码最上层目录结构下有一个script文件夹,其中记录了所有我们可能需要的SQL、配置…。比如:集成Nacos时,配置的内容、上传配置的shell脚本都在其中。

    2> 进入目录/script/client/at/db,找到mysql.sql文件,其就是我们需要的创建undo_log表结构的SQL:

    在这里插入图片描述

    2)仓储服务(stock-service)业务表

    1> 表结构:

    在这里插入图片描述

    2> 建表SQL:

    DROP SCHEMA IF EXISTS seata_stock;
    CREATE SCHEMA seata_stock;
    USE seata_stock;
    
    CREATE TABLE `stock_tbl`
    (
        `id`             INT(11) NOT NULL AUTO_INCREMENT,
        `commodity_code` VARCHAR(255) DEFAULT NULL,
        `count`          INT(11) DEFAULT '0',
        PRIMARY KEY (`id`),
        UNIQUE KEY `commodity_code` (`commodity_code`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8;
    
    INSERT INTO stock_tbl (id, commodity_code, count)
    VALUES (1, '2001', 1000);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3)订单服务(order-service)业务表

    1> 表结构:

    在这里插入图片描述

    2> 建表SQL:

    DROP SCHEMA IF EXISTS seata_order;
    CREATE SCHEMA seata_order;
    USE seata_order;
    
    CREATE TABLE `order_tbl`
    (
        `id`             INT(11) NOT NULL AUTO_INCREMENT,
        `user_id`        VARCHAR(255) DEFAULT NULL,
        `commodity_code` VARCHAR(255) DEFAULT NULL,
        `count`          INT(11) DEFAULT '0',
        `money`          INT(11) DEFAULT '0',
        PRIMARY KEY (`id`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4)账户服务(account)业务表

    1> 表结构:

    在这里插入图片描述

    2> 建表SQL:

    DROP SCHEMA IF EXISTS seata_account;
    CREATE SCHEMA seata_account;
    USE seata_account;
    
    CREATE TABLE `account_tbl`
    (
        `id`      INT(11) NOT NULL AUTO_INCREMENT,
        `user_id` VARCHAR(255) DEFAULT NULL,
        `money`   INT(11) DEFAULT 0,
        PRIMARY KEY (`id`)
    ) ENGINE = InnoDB
      DEFAULT CHARSET = utf8;
    
    INSERT INTO account_tbl (id, user_id, money)
    VALUES (1, '1001', 10000);
    INSERT INTO account_tbl (id, user_id, money)
    VALUES (2, '1002', 10000);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5)seata-server表结构

    当seata-server配置信息中 store配置的是db时,
    在这里插入图片描述

    需要使用到三张表:global_table(记录全局事务)、branch_table(记录分支事务)、lock_table(记录全局锁);

    当然数据库表和表名是可以改变的,只需要在store配置中对应上即可。

    1> global_table

    1> 表结构:

    在这里插入图片描述

    2> 建表SQL:

    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_gmt_modified_status` (`gmt_modified`, `status`),
        KEY `idx_transaction_id` (`transaction_id`)
        ) ENGINE = InnoDB
        DEFAULT CHARSET = utf8;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2> branch_table

    1> 表结构:

    在这里插入图片描述

    2> 建表SQL:

    CREATE TABLE IF NOT EXISTS `branch_table`
    (
        `branch_id`         BIGINT       NOT NULL,
        `xid`               VARCHAR(128) NOT NULL,
        `transaction_id`    BIGINT,
        `resource_group_id` VARCHAR(32),
        `resource_id`       VARCHAR(256),
        `branch_type`       VARCHAR(8),
        `status`            TINYINT,
        `client_id`         VARCHAR(64),
        `application_data`  VARCHAR(2000),
        `gmt_create`        DATETIME(6),
        `gmt_modified`      DATETIME(6),
        PRIMARY KEY (`branch_id`),
        KEY `idx_xid` (`xid`)
        ) ENGINE = InnoDB
        DEFAULT CHARSET = utf8;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3> lock_table

    1> 表结构:

    在这里插入图片描述

    2> 建表SQL:

    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),
        `gmt_create`     DATETIME,
        `gmt_modified`   DATETIME,
        PRIMARY KEY (`row_key`),
        KEY `idx_branch_id` (`branch_id`)
        ) ENGINE = InnoDB
        DEFAULT CHARSET = utf8;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    seata-server相关表结构从哪里找?

    1> 在GitHub中找到seata的源码,选择响应版本的代码分支:

    在这里插入图片描述

    注意源码最上层目录结构下有一个script文件夹,其中记录了所有我们可能需要的SQL、配置…。比如:集成Nacos时,配置的内容、上传配置的shell脚本都在其中。

    2> 进入目录/script/server/db,找到mysql.sql文件,其就是我们需要的创建seata-server相关的表结构的SQL:

    在这里插入图片描述

    数据库表结构处理完之后,看一下seata-server需要如何下载、配置、启动?

    2、seata-server

    seata-server下载地址:https://seata.io/zh-cn/blog/download.html,其中binary选项为seata-server可执行程序,source为相应版本源码。

    1)seata-server配置

    将下载下来的seata-server-1.3.0.tar.gz压缩包解压,解压后的文件目录为:seata-server-1.3.0

    # 进入seata-server主目录
    cd seata-server-1.3.0
    # 进入seata-server配置目录
    cd conf
    
    • 1
    • 2
    • 3
    • 4

    修改registry.conffile.conf配置文件,内容如下:

    1> registry.conf

    seata-server的配置中心和注册中心均采用nacos的方式,

    设置type为nacos、设置serverAddr为nacos节点地址,serverAddr不能带‘http://’前缀

    特别注意:registry.nacos.cluster(注册到的nacos集群名称)要和事务分组名称一致,即:下面config.txt文件中service.vgroupMapping.saint-trade-tx-group对应的值seata-server-sh

    registry {
      # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
      type = "nacos"
    
      nacos {
        application = "seata-server"
        serverAddr = "127.0.0.1:8848"
        group = "SEATA_GROUP"
        namespace = ""
        cluster = "seata-server-sh"
      }
    }
    
    config {
      # file、nacos 、apollo、zk、consul、etcd3
      type = "nacos"
    
      nacos {
        serverAddr = "127.0.0.1:8848"
        namespace = ""
        group = "SEATA_GROUP"
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    为什么要用service.vgroupMapping.saint-trade-tx-group对应的值作为seata-server注册到nacos注册中心的集群名?

    • 因为采用nacos作为注册中心时,seata-client是通过服务名(seata-server) + 集群名(seata-server-sh)+ 分组(SEATA_GROUP)去nacos注册中心中找到所有的seata-server实例地址;
      在这里插入图片描述

    • 如果不设置相应的集群名,则在从Nacos获取服务实例列表时会报错:

      i.s.c.r.netty.NettyClientChannelManager : no available service ‘seata-server-sh’ found, please make sure registry config correct
      在这里插入图片描述

    这里涉及到Nacos的源码,感兴趣的可以看博主的另一篇文章:图文详述Nacos服务发现源码分析

    2> seata-server相关配置上传到Nacos配置中心

    方法一:
    (1) 在GitHub中找到seata的源码,选择响应版本的代码分支:

    在这里插入图片描述

    注意源码最上层目录结构下有一个script文件夹,其中记录了所有我们可能需要的SQL、配置…。比如:集成Nacos时,配置的内容、上传配置的shell脚本都在其中。

    (2)进入/script/config-center目录,其中config.txt是上传到配置中心的配置所在的文件,nacos目录中是将nacos上传到Nacos配置中心的脚本(nacos-config.shnacos-config.py):

    在这里插入图片描述
    在这里插入图片描述

    (3)将上面的config.txt文件复制到seata-server的根目录,将nacos-config.sh复制到seat-server的bin目录下:
    注意:config.txt文件必须要在nacos-config.sh的上一级目录(../

    • 因为在nacos-config.sh脚本中,是通过cat $(dirname "$PWD")的方式去查找config.txt所在是目录;而cat $(dirname "$PWD")返回的正是nacos-config.sh脚本所在目录的上级目录;
      在这里插入图片描述

    (4)修改config.txt文件:
    重点关注以下配置:

    • 事务分组名称的配置、事务分组名称对应的Seata Server服务实例列表;
      注意,高版本的vgroupMapping后面的如: saint-trade-tx-group 不能定义为 saint_trade_tx_group
    • Seata Server数据存放的模式,如果是模式db,配置db信息;
      在这里插入图片描述
    transport.type=TCP
    transport.server=NIO
    transport.heartbeat=true
    transport.enableClientBatchSendRequest=false
    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
    service.vgroupMapping.saint-trade-tx-group=seata-server-sh
    service.seata-server-sh.grouplist=127.0.0.1:8091
    service.enableDegrade=false
    service.disableGlobalTransaction=false
    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=false
    client.rm.sqlParserType=druid
    client.rm.reportSuccessEnable=false
    client.rm.sagaBranchRegisterEnable=false
    client.tm.commitRetryCount=5
    client.tm.rollbackRetryCount=5
    client.tm.degradeCheck=false
    client.tm.degradeCheckAllowTimes=10
    client.tm.degradeCheckPeriod=2000
    store.mode=db
    store.db.datasource=druid
    store.db.dbType=mysql
    store.db.driverClassName=com.mysql.cj.jdbc.Driver
    store.db.url=jdbc:mysql://127.0.0.1:3306/seata_server?useUnicode=true
    store.db.user=root
    store.db.password=123456
    store.db.minConn=5
    store.db.maxConn=30
    store.db.globalTable=global_table
    store.db.branchTable=branch_table
    store.db.queryLimit=100
    store.db.lockTable=lock_table
    store.db.maxWait=5000
    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
    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.log.exceptionRate=100
    transport.serialization=seata
    transport.compressor=none
    metrics.enabled=false
    metrics.registryType=compact
    metrics.exporterList=prometheus
    metrics.exporterPrometheusPort=9898
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    (5)执行nacos-config.sh脚本将配置上传到nacos配置中心:

    方法二:通过dataId配置

    1. 从seata的v1.4.2版本开始,已支持从一个Nacos dataId中获取所有配置信息,只需要额外添加一个dataId配置项。

    2. 首先在nacos新建配置,此处dataId为seataServer.properties,配置内容参考https://github.com/seata/seata/tree/develop/script/config-center 的config.txt并按需修改(见方法一的修改)保存;

    3. 在client参考如下配置进行修改:

      seata:
        config:
          type: nacos
          nacos:
            server-addr: 127.0.0.1:8848
            group : "SEATA_GROUP"
            namespace: ""
            dataId: "seataServer.properties"
            username: "nacos"
            password: "nacos"
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    2)启动seata-server

    进入seata-server-1.3.0/bin目录,然后运行seata-server.sh shell脚本;

    cd ../bin
    sh seata-server.sh
    
    • 1
    • 2

    seata-server.sh脚本中的参数

    具体参考官方文档:https://github.com/seata/seata/tree/develop/script/config-center

    v1.5.0开始支持的 Nacos相关启动脚本参数介绍:

    在这里插入图片描述

    3、seata-client

    一般Spring Cloud集成seata大致会分为5步:

    1> 第一步:

    • 添加Spring Cloud Alibaba 依赖管理工具和 Seata 依赖;
      <dependencyManagement>
          <dependencies>
              <dependency>
                  <groupId>com.alibaba.cloudgroupId>
                  <artifactId>spring-cloud-alibaba-dependenciesartifactId>
                  <version>2021.1version>
                  <type>pomtype>
                  <scope>importscope>
              dependency>
          dependencies>
      dependencyManagement>
      
      <dependency>
      	<groupId>com.alibaba.cloudgroupId>
      	<artifactId>spring-cloud-starter-alibaba-seataartifactId>
      dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16

    另外,spring-cloud-starter-alibaba-seata依赖中seata相关的只依赖了spring-cloud-alibaba-seata,所以在项目中添加spring-cloud-starter-alibaba-seataspring-cloud-alibaba-seata是一样的;

    2> 第二步:

    • 在application.yml配置文件中设置Seata相关配置,(不再需要file.conf文件,registry.conf也不需要)

    3> 第三步:

    • 注入数据源;Seata 通过代理数据源的方式实现分支事务;MyBatis 和 JPA 都需要注入 io.seata.rm.datasource.DataSourceProxy, 不同的是,MyBatis 还需要额外注入 org.apache.ibatis.session.SqlSessionFactory

    4> 第四步:

    • 在业务相关的数据库中添加 undo_log 表,用于保存需要回滚的数据;

    5> 第五步:

    • 在业务的发起方的方法上使用@GlobalTransactional开启全局事务,Seata 会将事务的 xid 通过拦截器添加到调用其他服务的请求中,实现分布式事务;

    上面提到整体项目目录包括四个Module,分别为:trade-center、stock-service、order-service、account-service。
    在这里插入图片描述

    下面从这四个Module以具体的代码来看,这五步是如何体现在代码中的;

    0)最上层父项目spring-cloud-center的pom.xml文件

    
    <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.0modelVersion>
    
        <parent>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-parentartifactId>
            <version>2.4.2version>
            <relativePath/> 
        parent>
    
        <modules>
            <module>trade-centermodule>
            <module>stock-servicemodule>
            <module>order-servicemodule>
            <module>account-servicemodule>
        modules>
    
        <groupId>com.saintgroupId>
        <artifactId>transaction-seataartifactId>
        <version>0.0.1-SNAPSHOTversion>
        <name>transaction-seataname>
        <description>transaction-seatadescription>
        <packaging>pompackaging>
    
        <properties>
            <java.version>1.8java.version>
            <spring-boot.version>2.4.2spring-boot.version>
            <spring-cloud.version>2020.0.1spring-cloud.version>
            <spring-cloud-alibaba.version>2021.1spring-cloud-alibaba.version>
            <druid.version>1.2.8druid.version>
            <mysql.version>8.0.22mysql.version>
        properties>
    
    
        <dependencies>
    
            <dependency>
                <groupId>org.apache.commonsgroupId>
                <artifactId>commons-lang3artifactId>
                <version>3.12.0version>
            dependency>
    
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
    
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>fastjsonartifactId>
                <version>2.0.10version>
            dependency>
        dependencies>
    
        <dependencyManagement>
    
            <dependencies>
                <dependency>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-dependenciesartifactId>
                    <version>${spring-boot.version}version>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
                
                <dependency>
                    <groupId>org.springframework.cloudgroupId>
                    <artifactId>spring-cloud-dependenciesartifactId>
                    <version>${spring-cloud.version}version>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
                
                <dependency>
                    <groupId>com.alibaba.cloudgroupId>
                    <artifactId>spring-cloud-alibaba-dependenciesartifactId>
                    <version>${spring-cloud-alibaba.version}version>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
    
                <dependency>
                    <groupId>com.alibabagroupId>
                    <artifactId>druid-spring-boot-starterartifactId>
                    <version>${druid.version}version>
                dependency>
    
                <dependency>
                    <groupId>mysqlgroupId>
                    <artifactId>mysql-connector-javaartifactId>
                    <version>${mysql.version}version>
                dependency>
            dependencies>
        dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-maven-pluginartifactId>
                    <version>${spring-boot.version}version>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombokgroupId>
                                <artifactId>lombokartifactId>
                            exclude>
                        excludes>
                    configuration>
                plugin>
            plugins>
        build>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117

    关于Spring-cloud和SpringBoot的版本对应关系,参考博文:SpringBoot、SpringCloud、SpringCloudAlibaba的版本对应关系

    1)account-service

    account-service整体代码结构目录如下:
    在这里插入图片描述

    整体包括:pom.xml、JPA数据源配置、一个Controller、一个entity、一个Dao、一个Service、一个启动类;application.yml配置文件;

    1> pom.xml

    
    <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">
        <parent>
            <artifactId>transaction-seataartifactId>
            <groupId>com.saintgroupId>
            <version>0.0.1-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
        <version>0.0.1-SNAPSHOTversion>
        <groupId>com.saintgroupId>
        <artifactId>account-serviceartifactId>
        <name>account-servicename>
    
        <dependencies>
    
            
            <dependency>
                <groupId>io.protostuffgroupId>
                <artifactId>protostuff-runtimeartifactId>
                <version>1.8.0version>
            dependency>
            <dependency>
                <groupId>io.protostuffgroupId>
                <artifactId>protostuff-coreartifactId>
                <version>1.8.0version>
            dependency>
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <scope>runtimescope>
            dependency>
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>druid-spring-boot-starterartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-jpaartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-openfeignartifactId>
            dependency>
    
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-seataartifactId>
            dependency>
        dependencies>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    2> DataSourceConfig

    package com.saint.account.config;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import io.seata.rm.datasource.DataSourceProxy;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    
    import javax.sql.DataSource;
    
    /**
     * 数据源配置
     *
     * @author Saint
     */
    @Configuration
    public class DataSourceConfig {
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource")
        public DruidDataSource druidDataSource() {
            return new DruidDataSource();
        }
    
        /**
         * 需要将 DataSourceProxy 设置为主数据源,否则事务无法回滚
         *
         * @param druidDataSource The DruidDataSource
         * @return The default datasource
         */
        @Primary
        @Bean("dataSource")
        public DataSource dataSource(DruidDataSource druidDataSource) {
            return new DataSourceProxy(druidDataSource);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    3> AccountController

    package com.saint.account.controller;
    
    import com.saint.account.service.AccountService;
    import lombok.RequiredArgsConstructor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.math.BigDecimal;
    
    /**
     * @author Saint
     */
    @RestController
    @RequiredArgsConstructor(onConstructor = @_(@Autowired))
    public class AccountController {
    
        private final AccountService accountService;
    
        @RequestMapping("/debit")
        public Boolean debit(String userId, BigDecimal money) {
            accountService.debit(userId, money);
    
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    4> Account

    package com.saint.account.entity;
    
    import lombok.Data;
    import org.hibernate.annotations.DynamicInsert;
    import org.hibernate.annotations.DynamicUpdate;
    
    import javax.persistence.Entity;
    import javax.persistence.Id;
    import javax.persistence.Table;
    import java.math.BigDecimal;
    
    /**
     * @author Saint
     */
    @Entity
    @Table(name = "account_tbl")
    @DynamicUpdate
    @DynamicInsert
    @Data
    public class Account {
        @Id
        private Long id;
        private String userId;
        private BigDecimal money;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    5> AccountDAO

    package com.saint.account.repository;
    
    import com.saint.account.entity.Account;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    /**
     * @author Saint
     */
    public interface AccountDAO extends JpaRepository<Account, Long> {
    
        Account findByUserId(String userId);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    6> AccountService

    package com.saint.account.service;
    
    import com.saint.account.entity.Account;
    import com.saint.account.repository.AccountDAO;
    import lombok.RequiredArgsConstructor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.math.BigDecimal;
    
    /**
     * @author Saint
     */
    @Service
    @RequiredArgsConstructor(onConstructor = @_(@Autowired))
    public class AccountService {
    
        private final AccountDAO accountDAO;
    
        private static final String ERROR_USER_ID = "1002";
    
        @Transactional(rollbackFor = Exception.class)
        public void debit(String userId, BigDecimal num) {
            Account account = accountDAO.findByUserId(userId);
            account.setMoney(account.getMoney().subtract(num));
            accountDAO.save(account);
    
            if (ERROR_USER_ID.equals(userId)) {
                throw new RuntimeException("account branch exception");
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    7> AccountApplication

    package com.saint.account;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
    
    /**
     * @author Saint
     */
    @SpringBootApplication
    @EnableFeignClients
    @EnableJpaRepositories
    public class AccountApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(AccountApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    8> application.yml

    server:
      port: 9031
    spring:
      application:
        name: account-service
      cloud:
        nacos:
          # 这里配置的是当前服务所要注册到的Nacos地址
          discovery:
            server-addr: 127.0.0.1:8848
            group: TRADE_GROUP
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/seata_account?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      jpa:
        show-sql: true
    
    seata:
      # 属性需要seata-server端 config.txt文件中的service.vgroupMapping后的值保持一致
      # 即,nacos配置中心中要存在dataId为 service.vgroupMapping.saint-trade-tx-group 的配置
      tx-service-group: saint-trade-tx-group
      # 采用nacos作为配置中心
      config:
        type: nacos
        nacos:
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
      # 当seata-server采用nacos做为注册中心时(通过以下信息获取seata-server实例列表)
      registry:
        type: nacos
        nacos:
          # seata-server的应用名称
          application: seata-server
          # seata-server注册到的nacos服务地址
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    2)order-service

    order-service整体代码结构目录如下:
    在这里插入图片描述

    整体包括:pom.xml、JPA数据源配置、一个Controller、一个entity、一个FeignClient、一个Dao、一个Service、一个启动类;application.yml配置文件;

    1> pom.xml

    
    <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">
        <parent>
            <artifactId>transaction-seataartifactId>
            <groupId>com.saintgroupId>
            <version>0.0.1-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
        <version>0.0.1-SNAPSHOTversion>
        <groupId>com.saintgroupId>
        <artifactId>order-serviceartifactId>
        <name>order-servicename>
    
        <dependencies>
    
            
            <dependency>
                <groupId>io.protostuffgroupId>
                <artifactId>protostuff-runtimeartifactId>
                <version>1.8.0version>
            dependency>
            <dependency>
                <groupId>io.protostuffgroupId>
                <artifactId>protostuff-coreartifactId>
                <version>1.8.0version>
            dependency>
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
            dependency>
    
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-loadbalancerartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <scope>runtimescope>
            dependency>
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>druid-spring-boot-starterartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-jpaartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-openfeignartifactId>
            dependency>
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-seataartifactId>
            dependency>
    
        dependencies>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    2> DataSourceConfig

    JPA的数据源配置和account-service中的一样;

    3> OrderController

    package com.saint.order.controller;
    
    import com.saint.order.service.OrderService;
    import lombok.RequiredArgsConstructor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author Saint
     */
    @RestController
    @RequiredArgsConstructor(onConstructor = @_(@Autowired))
    public class OrderController {
    
        private final OrderService orderService;
    
        @GetMapping("/create")
        public Boolean create(String userId, String commodityCode, Integer count) {
    
            orderService.create(userId, commodityCode, count);
            return true;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    4> Order

    package com.saint.order.entity;
    
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.hibernate.annotations.DynamicInsert;
    import org.hibernate.annotations.DynamicUpdate;
    
    import javax.persistence.*;
    import java.math.BigDecimal;
    
    /**
     * @author Saint
     */
    @Entity
    @Table(name = "order_tbl")
    @DynamicUpdate
    @DynamicInsert
    @NoArgsConstructor
    @Data
    public class Order {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(name = "user_id")
        private String userId;
    
        @Column(name = "commodity_code")
        private String commodityCode;
    
        @Column(name = "money")
        private BigDecimal money;
    
        @Column(name = "count")
        private Integer count;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    5> AccountFeignClient

    AccountFeignClient用于通过OpenFeign调用account-service;

    package com.saint.order.feign;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    import java.math.BigDecimal;
    
    /**
     * @author Saint
     */
    @FeignClient(name = "account-service")
    public interface AccountFeignClient {
    
        @GetMapping("/debit")
        Boolean debit(@RequestParam("userId") String userId, @RequestParam("money") BigDecimal money);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    6> OrderDAO

    package com.saint.order.repository;
    
    import com.saint.order.entity.Order;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    /**
     * @author Saint
     */
    public interface OrderDAO extends JpaRepository<Order, Long> {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    7> OrderService

    package com.saint.order.service;
    
    import com.saint.order.entity.Order;
    import com.saint.order.feign.AccountFeignClient;
    import com.saint.order.repository.OrderDAO;
    import lombok.RequiredArgsConstructor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.math.BigDecimal;
    
    /**
     * @author Saint
     */
    @Service
    @RequiredArgsConstructor(onConstructor = @_(@Autowired))
    public class OrderService {
    
        private final AccountFeignClient accountFeignClient;
        private final OrderDAO orderDAO;
    
        @Transactional
        public void create(String userId, String commodityCode, Integer count) {
    
            BigDecimal orderMoney = new BigDecimal(count).multiply(new BigDecimal(5));
            Order order = new Order();
            order.setUserId(userId);
            order.setCommodityCode(commodityCode);
            order.setCount(count);
            order.setMoney(orderMoney);
    
            orderDAO.save(order);
            accountFeignClient.debit(userId, orderMoney);
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    8> OrderApplication

    package com.saint.order;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
    
    /**
     * @author Saint
     */
    @SpringBootApplication
    @EnableFeignClients
    @EnableJpaRepositories
    public class OrderApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    9> application.yml

    server:
      port: 9021
    spring:
      application:
        name: order-service
      cloud:
        nacos:
          # 这里配置的是当前服务所要注册到的Nacos地址
          discovery:
            server-addr: 127.0.0.1:8848
            group: TRADE_GROUP
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/seata_order?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      jpa:
        show-sql: true
    
    seata:
      # 属性需要seata-server端 config.txt文件中的service.vgroupMapping后的值保持一致
      # 即,nacos配置中心中要存在dataId为 service.vgroupMapping.saint-trade-tx-group 的配置
      tx-service-group: saint-trade-tx-group
      # 采用nacos作为配置中心
      config:
        type: nacos
        nacos:
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
      # 当seata-server采用nacos做为注册中心时(通过以下信息获取seata-server实例列表)
      registry:
        type: nacos
        nacos:
          # seata-server的应用名称
          application: seata-server
          # seata-server注册到的nacos服务地址
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    3)stock-service

    stck-service整体代码结构目录如下:
    在这里插入图片描述

    整体包括:pom.xml、JPA数据源配置、一个Controller、一个entity、一个Dao、一个Service、一个启动类;application.yml配置文件;

    1> pom.xml

    
    <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">
        <parent>
            <artifactId>transaction-seataartifactId>
            <groupId>com.saintgroupId>
            <version>0.0.1-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
        <version>0.0.1-SNAPSHOTversion>
        <groupId>com.saintgroupId>
        <artifactId>stock-serviceartifactId>
        <name>stock-servicename>
    
        <dependencies>
    
            
            <dependency>
                <groupId>io.protostuffgroupId>
                <artifactId>protostuff-runtimeartifactId>
                <version>1.8.0version>
            dependency>
            <dependency>
                <groupId>io.protostuffgroupId>
                <artifactId>protostuff-coreartifactId>
                <version>1.8.0version>
            dependency>
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <scope>runtimescope>
            dependency>
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>druid-spring-boot-starterartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-jpaartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-openfeignartifactId>
            dependency>
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-seataartifactId>
            dependency>
        dependencies>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    2> DataSourceConfig

    JPA的数据源配置和account-service中的一样;

    3> StockController

    package com.saint.stock.controller;
    
    import com.saint.stock.service.StockService;
    import lombok.RequiredArgsConstructor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author Saint
     */
    @RestController
    @RequiredArgsConstructor(onConstructor = @_(@Autowired))
    public class StockController {
    
        private final StockService stockService;
    
        @GetMapping(path = "/deduct")
        public Boolean deduct(String commodityCode, Integer count) {
            stockService.deduct(commodityCode, count);
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    4> Stock

    package com.saint.stock.entity;
    
    import lombok.Data;
    import org.hibernate.annotations.DynamicInsert;
    import org.hibernate.annotations.DynamicUpdate;
    
    import javax.persistence.Entity;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    /**
     * @author Saint
     */
    @Entity
    @Table(name = "stock_tbl")
    @DynamicUpdate
    @DynamicInsert
    @Data
    public class Stock {
    
        @Id
        private Long id;
        private String commodityCode;
        private Integer count;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    5> StockDAO

    package com.saint.stock.repository;
    
    import com.saint.stock.entity.Stock;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    /**
     * @author Saint
     */
    public interface StockDAO extends JpaRepository<Stock, String> {
    
        Stock findByCommodityCode(String commodityCode);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    6> StockService

    package com.saint.stock.service;
    
    import com.saint.stock.entity.Stock;
    import com.saint.stock.repository.StockDAO;
    import lombok.RequiredArgsConstructor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    /**
     * @author Saint
     */
    @Service
    @RequiredArgsConstructor(onConstructor = @_(@Autowired))
    public class StockService {
    
        private final StockDAO stockDAO;
    
        @Transactional
        public void deduct(String commodityCode, int count) {
            Stock stock = stockDAO.findByCommodityCode(commodityCode);
            stock.setCount(stock.getCount() - count);
    
            stockDAO.save(stock);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    7> StockApplication

    package com.saint.stock;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
    
    /**
     * @author Saint
     */
    @SpringBootApplication
    @EnableJpaRepositories
    public class StockApplication {
        public static void main(String[] args) {
            SpringApplication.run(StockApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    8> application.yml

    server:
      port: 9011
    spring:
      application:
        name: stock-service
      cloud:
        nacos:
          # 这里配置的是当前服务所要注册到的Nacos地址
          discovery:
            server-addr: 127.0.0.1:8848
            group: TRADE_GROUP
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/seata_stock?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
      jpa:
        show-sql: true
    
    seata:
      # 属性需要seata-server端 config.txt文件中的service.vgroupMapping后的值保持一致
      # 即,nacos配置中心中要存在dataId为 service.vgroupMapping.saint-trade-tx-group 的配置
      tx-service-group: saint-trade-tx-group
      # 采用nacos作为配置中心
      config:
        type: nacos
        nacos:
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
      # 当seata-server采用nacos做为注册中心时(通过以下信息获取seata-server实例列表)
      registry:
        type: nacos
        nacos:
          # seata-server的应用名称
          application: seata-server
          # seata-server注册到的nacos服务地址
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    4)trade-center

    trade-center整体代码结构目录如下:
    在这里插入图片描述

    整体包括:pom.xml、一个Controller、一个entity、两个FeignClient、一个Service、一个启动类;application.yml配置文件;

    1> pom.xml

    
    <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">
        <parent>
            <artifactId>transaction-seataartifactId>
            <groupId>com.saintgroupId>
            <version>0.0.1-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
        <artifactId>trade-centerartifactId>
        <version>0.0.1-SNAPSHOTversion>
        <groupId>com.saintgroupId>
        <name>trade-centername>
    
        <dependencies>
            
            <dependency>
                <groupId>io.protostuffgroupId>
                <artifactId>protostuff-runtimeartifactId>
                <version>1.8.0version>
            dependency>
            <dependency>
                <groupId>io.protostuffgroupId>
                <artifactId>protostuff-coreartifactId>
                <version>1.8.0version>
            dependency>
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
            dependency>
    
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-loadbalancerartifactId>
            dependency>
    
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-openfeignartifactId>
            dependency>
    
            
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-seataartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    
        dependencies>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    2> TradeController

    package com.saint.trade.controller;
    
    import com.saint.trade.service.TradeService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @author Saint
     */
    @RestController
    public class TradeController {
    
        @Autowired
        private TradeService businessService;
    
        /**
         * 购买下单,模拟全局事务提交
         *
         * @return
         */
        @RequestMapping("/purchase/commit")
        public Boolean purchaseCommit() {
            businessService.purchase("1001", "2001", 1);
            return true;
        }
    
        /**
         * 购买下单,模拟全局事务回滚
         *
         * @return
         */
        @RequestMapping("/purchase/rollback")
        public Boolean purchaseRollback() {
            try {
                businessService.purchase("1002", "2001", 1);
    //            businessService.failToPurchase("1001", "2001", 1);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
    
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    3> OrderFeignClient

    trade-center通过OpenFeign调用order-service;

    package com.saint.trade.feign;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    /**
     * @author Saint
     */
    @FeignClient(name = "order-service")
    public interface OrderFeignClient {
    
        @GetMapping("/create")
        void create(@RequestParam("userId") String userId, @RequestParam("commodityCode") String commodityCode,
                    @RequestParam("count") Integer count);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4> StockFeignClient

    trade-center通过OpenFeign调用stock-service;

    package com.saint.trade.feign;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    /**
     * @author Saint
     */
    @FeignClient(name = "stock-service")
    public interface StockFeignClient {
    
        @GetMapping("/deduct")
        void deduct(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    5> TradeService

    TradeService中通过@GlobalTransactional开启分布式事务;

    package com.saint.trade.service;
    
    import com.saint.trade.feign.OrderFeignClient;
    import com.saint.trade.feign.StockFeignClient;
    import io.seata.spring.annotation.GlobalTransactional;
    import lombok.RequiredArgsConstructor;
    import org.springframework.stereotype.Service;
    
    /**
     * @author Saint
     */
    @Service
    @RequiredArgsConstructor
    public class TradeService {
    
        private final StockFeignClient stockFeignClient;
        private final OrderFeignClient orderFeignClient;
    
        /**
         * 减库存,下订单
         *
         * @param userId
         * @param commodityCode
         * @param orderCount
         */
        @GlobalTransactional
        public void purchase(String userId, String commodityCode, int orderCount) {
            stockFeignClient.deduct(commodityCode, orderCount);
    
            orderFeignClient.create(userId, commodityCode, orderCount);
        }
    
        /**
         * 减库存,下订单(有异常)
         *
         * @param userId
         * @param commodityCode
         * @param orderCount
         */
        @GlobalTransactional
        public void failToPurchase(String userId, String commodityCode, int orderCount) {
            stockFeignClient.deduct(commodityCode, orderCount);
            orderFeignClient.create(userId, commodityCode, orderCount);
            throw new RuntimeException("Error!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    6> TradeApplication

    package com.saint.trade;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    /**
     * @author Saint
     */
    @SpringBootApplication
    @EnableFeignClients
    public class TradeApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(TradeApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    7> application.yml

    server:
      port: 9001
    spring:
      application:
        name: trade-center
      cloud:
        nacos:
          # 这里配置的是当前服务所要注册到的Nacos地址
          discovery:
            server-addr: 127.0.0.1:8848
            group: TRADE_GROUP
    
    #  cloud:
    #    alibaba:
    #      seata:
    #        tx-service-group: saint-trade-tx-group
    
    seata:
      # 属性需要seata-server端 config.txt文件中的service.vgroupMapping后的值保持一致
      # 即,nacos配置中心中要存在dataId为 service.vgroupMapping.saint-trade-tx-group 的配置
      tx-service-group: saint-trade-tx-group
      # 采用nacos作为配置中心
      config:
        type: nacos
        nacos:
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
      # 当seata-server采用nacos做为注册中心时(通过以下信息获取seata-server实例列表)
      registry:
        type: nacos
        nacos:
          # seata-server的应用名称
          application: seata-server
          # seata-server注册到的nacos服务地址
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    4、AT模式分布式事务效果演示

    分别启动trade-center、stock-service、order-service、account-service;都启动成功之后,Nacos服务信息如下:

    在这里插入图片描述

    四个服务的分组名称均为我们通过spring.cloud.nacos.discovery.group属性配置的;

    1)请求正常

    分布式事务成功,模拟正常下单、扣库存

    请求访问:http://127.0.0.1:9001/purchase/commit

    2)请求异常

    分布式事务失败,模拟下单成功、扣库存失败,最终同时回滚

    请求访问:http://127.0.0.1:9001/purchase/rollback

    四、总结和后续

    当前文章讲述了Spring Cloud + Nacos + Seata + OpenFeign + JPA实现分布式事务的案例。

    如果你运行不起来,私信我。

    官方案例文档https://github.com/Saint9768/seata-samples/tree/master/springcloud-nacos-seata;明人不说暗话,里面全是坑,基本是搭不起来的;

    下一篇文章正式开启Seata源码分析,期间会穿插聊一下Seata的TCC模式实现案例;

  • 相关阅读:
    大模型从入门到应用——LangChain:代理(Agents)-[工具包(Toolkit)]
    练习题37:命名空间练习
    C专家编程 第8章 为什么程序员无法分清万圣节和圣诞节 8.10 轻松一下---国际C语言混乱代码大赛
    算法·每日一题(详解+多解)-- day15
    运行jar时提示缺少依赖的类
    SpringBoot 配置文件加载优先级
    vue http页面新开窗口跳转https页面
    在学校拿奖学金,找工作无人问津?跟着学校学编程,面试难过得靠拼
    Redis 6.0 新功能
    vue-cli vue3
  • 原文地址:https://blog.csdn.net/Saintmm/article/details/126218557