Spring Cloud Alibaba Seata分布式事务组件使用教程

什么是分布式事务

数据库中的事务是数据库写数据时出现错误时保证数据一致性的机制,单体服务中依靠数据库中的事务机制就能保证数据一致性

在分布式服务中,一个业务功能,会涉及多个服务节点,每个服务节点都有需要开启一个事务来保证数据的一致性

在分布式服务的相互调用中,有时会出现某个服务节点出现故障,导致某个环节数据没有成功保存,出现数据不一致的情况

分布式事务就是为解决这种情况导致数据不一致的机制,在一个业务链路中,当某个服务出现故障,则这个链路涉及的数据库操作都要回滚保证数据的一致性

  • 案例

  • 用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持
    1. 仓储服务∶对给定的商品扣除仓库/商品数量
    2. 订单服务;根据采购需求创建订单
    3. 帐户服务∶从用户帐户中扣除余额
  • 单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源
  • 业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证
  • 但是全局的数据—致性问题没法保证
  • 一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题

什么是Seata

Seata官网

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务

Seata提供的分布式事务方案

Seata AT 模式

AT 模式是 Seata 创新的一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,我们使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等

整体机制

两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

Seata TCC 模式

TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,是继 AT 模式后第二种支持的事务模式,最早由蚂蚁金服贡献。其分布式事务模型直接作用于服务层,不依赖底层数据库,可以灵活选择业务资源的锁定粒度,减少资源锁持有时间,可扩展性好,可以说是为独立部署的 SOA 服务而设计的。

整体机制

  • 在两阶段提交协议中,资源管理器(RM, Resource Manager)需要提供“准备”、“提交”和“回滚” 3 个操作;而事务管理器(TM, Transaction Manager)分 2 阶段协调所有资源管理器,在第一阶段询问所有资源管理器“准备”是否成功,如果所有资源均“准备”成功则在第二阶段执行所有资源的“提交”操作,否则在第二阶段执行所有资源的“回滚”操作,保证所有资源的最终状态是一致的,要么全部提交要么全部回滚。
  • 资源管理器有很多实现方式,其中 TCC(Try-Confirm-Cancel)是资源管理器的一种服务化的实现;TCC 是一种比较成熟的分布式事务解决方案,可用于解决跨数据库、跨服务业务操作的数据一致性问题;TCC 其 Try、Confirm、Cancel 3 个方法均由业务编码实现,故 TCC 可以被称为是服务化的资源管理器。
  • TCC 的 Try 操作作为一阶段,负责资源的检查和预留;Confirm 操作作为二阶段提交操作,执行真正的业务;Cancel 是二阶段回滚操作,执行预留资源的取消,使资源回到初始状态。

Seata Saga 模式

Saga 模式是 SEATA 提供的长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。

Seata XA 模式

XA 模式是从 1.2 版本支持的事务模式。XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。Seata XA 模式是利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种事务模式。

整体机制

  • 执行阶段:
    • 可回滚:业务 SQL 操作放在 XA 分支中进行,由资源对 XA 协议的支持来保证 可回滚
    • 持久化:XA 分支完成后,执行 XA prepare,同样,由资源对 XA 协议的支持来保证 持久化(即,之后任何意外都不会造成无法回滚的情况)
  • 完成阶段:
    • 分支提交:执行 XA 分支的 commit
    • 分支回滚:执行 XA 分支的 rollback

基于Seata AT 模式分布式事务案例

  • 术语解读
    • Transaction ID(XID):全局唯一的事务ID
    • Transaction Coordinator(TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚
    • Transaction Manager(TM):控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
    • Resource Manager(RM):控制分支事务,负责分支注册,状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
  • 流程解读
    1. TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
    2. XID在微服务调用链路的上下文中传播
    3. RM向TC注册分支事务,将其纳入XID对应全局事务的管辖
    4. TM向TC发起针对XID的全局提交或回滚决议
    5. TC调度XID下管辖的全部分支事务完成提交或回滚请求
  1. 下载安装Seata

    Seata2.5.0下载地址

  2. 配置Seata

    • 参考"seata-server/conf/application.example.yml"配置文件的配置,进行"seata-server/conf/application.yml"配置文件的配置
    • 配置配置中心和注册中心使用nacos
    • 数据存储模式使用MySQL数据库
    #
    # Licensed to the Apache Software Foundation (ASF) under one or more
    # contributor license agreements.  See the NOTICE file distributed with
    # this work for additional information regarding copyright ownership.
    # The ASF licenses this file to You 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: 8091
    spring:
      application:
        name: seata-server
      main:
        web-application-type: none
    logging:
      config: classpath:logback-spring.xml
      file:
        path: ${log.home:${user.home}/logs/seata}
      extend:
        logstash-appender:
          # off by default
          enabled: false
          destination: 127.0.0.1:4560
        kafka-appender:
          # off by default
          enabled: false
          bootstrap-servers: 127.0.0.1:9092
          topic: logback_to_logstash
          producer:
            acks: 0
            linger-ms: 1000
            max-block-ms: 0
        metric-appender:
          # off by default
          enabled: false
    
    seata:
      config:
        # support: nacos, consul, apollo, zk, etcd3
        type: nacos
        nacos:
          server-addr: 127.0.0.1:8848
          namespace: public
          group: SEATA_GROUP
          data-id: seataServer.properties
      registry:
        # support: nacos, eureka, redis, zk, consul, etcd3, sofa
        type: nacos
        nacos:
          application: seata-server
          server-addr: 127.0.0.1:8848
          group: SEATA_GROUP
          namespace: public
          cluster: default
      store:
        # support: file 、 db 、 redis 、 raft
        mode: db
        db:
          datasource: druid
          db-type: mysql
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true
          user: root
          password: hsp
          min-conn: 10
          max-conn: 100
          global-table: global_table
          branch-table: branch_table
          lock-table: lock_table
          distributed-lock-table: distributed_lock
          vgroup-table: vgroup_table
          query-limit: 1000
          max-wait: 5000
          druid:
            time-between-eviction-runs-millis: 120000
            min-evictable-idle-time-millis: 300000
            test-while-idle: true
            test-on-borrow: false
            keep-alive: false
      #  server:
      #    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
    
    
    • 在MySQL数据库中创建一个seata数据库

      create database seata;
      use seata;
      
    • 运行"seata-server/script/server/db/mysql.sql"SQL文件,创建Seata需要的数据表

      -- -------------------------------- 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 = InnoDB
        DEFAULT 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 = InnoDB
        DEFAULT 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 = InnoDB
        DEFAULT 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 = InnoDB
        DEFAULT 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);
      
      
      CREATE TABLE IF NOT EXISTS `vgroup_table`
      (
          `vGroup`    VARCHAR(255),
          `namespace` VARCHAR(255),
          `cluster`   VARCHAR(255),
        UNIQUE KEY `idx_vgroup_namespace_cluster` (`vGroup`,`namespace`,`cluster`)
      ) ENGINE = InnoDB
        DEFAULT CHARSET = utf8mb4;
      
  3. 运行Seata

    • 先后运行nacos和seata,验证seata能成功启动

    • 运行nacos

      startup.cmd -m standalone
      

    • 运行seata,使用"seata-server/bin/seata-server.bat"脚本进行启动

      seata-server.bat
      

  4. 查看nacos控制台,验证seata注册到nacos

  5. 创建三个对应的微服务模块

    • 创建三个微服务需要使用的数据库和数据表

      1. 订单微服务的数据库

        -- 订单微服务的数据库
        CREATE DATABASE order_micro_service ;
        USE order_micro_service;
        CREATE TABLE `order`(
            id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
            user_id BIGINT DEFAULT NULL,
            product_id BIGINT DEFAULT NULL,
            nums INT DEFAULT NULL,
            money INT DEFAULT NULL,
            `status` INT DEFAULT NULL COMMENT '0:创建中;1:已完结'
        );
        
        • 创建Seata需要使用的"undo_log"数据表
        -- 注意此处0.3.0+ 增加唯一索引 ux_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;
        
      2. 库存微服务的数据库

        -- 库存微服务的数据库
        CREATE DATABASE storage_micro_service;
        USE storage_micro_service;
        CREATE TABLE `storage`(
            id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
            product_id BIGINT DEFAULT NULL,
            amount INT DEFAULT NULL COMMENT'库存量'
        );
        -- 初始化库存表
        INSERT INTO `storage` VALUES(NULL,1,10);
        
        • 创建Seata需要使用的"undo_log"数据表
        -- 注意此处0.3.0+ 增加唯一索引 ux_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. 账号微服务的数据库

        -- 账号微服务的数据库
        CREATE DATABASE account_micro_service;
        USE account_micro_service;
        CREATE TABLE `account`(
            id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
            user_id BIGINT DEFAULT NULL,
            money INT DEFAULT NULL COMMENT'账户金额'
        );
        -- 初始化账户表
        INSERT INTO `account`VALUES(NULL,666,10000);
        
        • 创建Seata需要使用的"undo_log"数据表
        -- 注意此处0.3.0+ 增加唯一索引 ux_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;
        
    1. 搭建订单微服务模块

      1. 新建一个"seata_order_micro_service"模块

      2. 配置依赖

                <!--导入seata依赖-->
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
                    <exclusions>
                        <exclusion>
                            <groupId>com.alibaba</groupId>
                            <artifactId>druid</artifactId>
                        </exclusion>
                        <exclusion>
                            <groupId>com.alibaba</groupId>
                            <artifactId>fastjson</artifactId>
                        </exclusion>
                        <exclusion>
                            <groupId>com.github.ben-manes.caffeine</groupId>
                            <artifactId>caffeine</artifactId>
                        </exclusion>
                    </exclusions>
                </dependency>
                <!--nacos服务发现的依赖-->
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
                </dependency>
                <!--配合OpenFeign的负载均衡组件-->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
                </dependency>
                <!--远程调用openFeign的依赖-->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-openfeign</artifactId>
                    <version>3.1.5</version>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </dependency>
                <dependency>
                    <groupId>com.mysql</groupId>
                    <artifactId>mysql-connector-j</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                </dependency>
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>druid-spring-boot-starter</artifactId>
                </dependency>
                <dependency>
                    <groupId>anyi.space</groupId>
                    <artifactId>common</artifactId>
                    <version>${project.version}</version>
                </dependency>
        
      3. 配置"application.yaml"

        server:
          port: 10012
        spring:
          datasource:
            type: com.alibaba.druid.pool.DruidDataSource
            druid:
              url: jdbc:mysql://localhost:3306/order_micro_service?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
              username: root
              password: hsp
              driver-class-name: com.mysql.jdbc.Driver
          application:
            name: seata-order-micro-service
          cloud:
            nacos:
              discovery:
                server-addr: localhost:8848
            alibaba:
              seata:
                tx-service-group: seata-server
        mybatis-plus:
          configuration:
            log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        logging:
          level:
            io:
              seata: info
        
        
      4. 编写业务代码

        • domain

          package space.anyi.seataOrderMicroService.domain;
          
          import com.baomidou.mybatisplus.annotation.IdType;
          import com.baomidou.mybatisplus.annotation.TableField;
          import com.baomidou.mybatisplus.annotation.TableId;
          import com.baomidou.mybatisplus.annotation.TableName;
          import java.io.Serializable;
          import lombok.Data;
          
          /**
           *
           * @TableName order
           */
          @TableName(value ="order")
          @Data
          public class Order implements Serializable {
              /**
               *
               */
              @TableId(type = IdType.AUTO)
              private Long id;
          
              /**
               *
               */
              private Long userId;
          
              /**
               *
               */
              private Long productId;
          
              /**
               *
               */
              private Integer nums;
          
              /**
               *
               */
              private Integer money;
          
              /**
               * 0:创建中;1:已完结
               */
              private Integer status;
          
              @TableField(exist = false)
              private static final long serialVersionUID = 1L;
          
              @Override
              public boolean equals(Object that) {
                  if (this == that) {
                      return true;
                  }
                  if (that == null) {
                      return false;
                  }
                  if (getClass() != that.getClass()) {
                      return false;
                  }
                  Order other = (Order) that;
                  return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
                      && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
                      && (this.getProductId() == null ? other.getProductId() == null : this.getProductId().equals(other.getProductId()))
                      && (this.getNums() == null ? other.getNums() == null : this.getNums().equals(other.getNums()))
                      && (this.getMoney() == null ? other.getMoney() == null : this.getMoney().equals(other.getMoney()))
                      && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()));
              }
          
              @Override
              public int hashCode() {
                  final int prime = 31;
                  int result = 1;
                  result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
                  result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
                  result = prime * result + ((getProductId() == null) ? 0 : getProductId().hashCode());
                  result = prime * result + ((getNums() == null) ? 0 : getNums().hashCode());
                  result = prime * result + ((getMoney() == null) ? 0 : getMoney().hashCode());
                  result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
                  return result;
              }
          
              @Override
              public String toString() {
                  StringBuilder sb = new StringBuilder();
                  sb.append(getClass().getSimpleName());
                  sb.append(" [");
                  sb.append("Hash = ").append(hashCode());
                  sb.append(", id=").append(id);
                  sb.append(", userId=").append(userId);
                  sb.append(", productId=").append(productId);
                  sb.append(", nums=").append(nums);
                  sb.append(", money=").append(money);
                  sb.append(", status=").append(status);
                  sb.append(", serialVersionUID=").append(serialVersionUID);
                  sb.append("]");
                  return sb.toString();
              }
          }
          
          
        • mapper

          package space.anyi.seataOrderMicroService.mapper;
          
          import org.apache.ibatis.annotations.Mapper;
          import space.anyi.seataOrderMicroService.domain.Order;
          import com.baomidou.mybatisplus.core.mapper.BaseMapper;
          
          /**
          * @author 杨逸
          * @description 针对表【order】的数据库操作Mapper
          * @createDate 2025-09-21 11:22:55
          * @Entity generator.domain.Order
          */
          @Mapper
          public interface OrderMapper extends BaseMapper<Order> {
          
          }
          
          <?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="space.anyi.seataOrderMicroService.mapper.OrderMapper">
          
              <resultMap id="BaseResultMap" type="space.anyi.seataOrderMicroService.domain.Order">
                      <id property="id" column="id" jdbcType="BIGINT"/>
                      <result property="userId" column="user_id" jdbcType="BIGINT"/>
                      <result property="productId" column="product_id" jdbcType="BIGINT"/>
                      <result property="nums" column="nums" jdbcType="INTEGER"/>
                      <result property="money" column="money" jdbcType="INTEGER"/>
                      <result property="status" column="status" jdbcType="INTEGER"/>
              </resultMap>
          
              <sql id="Base_Column_List">
                  id,user_id,product_id,
                  nums,money,status
              </sql>
          </mapper>
          
          
        • service

          package space.anyi.seataOrderMicroService.service;
          
          import space.anyi.seataOrderMicroService.domain.Order;
          import com.baomidou.mybatisplus.extension.service.IService;
          
          /**
          * @author 杨逸
          * @description 针对表【order】的数据库操作Service
          * @createDate 2025-09-21 11:22:55
          */
          public interface OrderService extends IService<Order> {
          
              void saveOrder(Order order);
          }
          
          
          package space.anyi.seataOrderMicroService.service.impl;
          
          import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
          import lombok.extern.slf4j.Slf4j;
          import org.springframework.beans.factory.annotation.Autowired;
          import space.anyi.seataOrderMicroService.domain.Order;
          import space.anyi.seataOrderMicroService.service.AccountService;
          import space.anyi.seataOrderMicroService.service.OrderService;
          import space.anyi.seataOrderMicroService.mapper.OrderMapper;
          import org.springframework.stereotype.Service;
          import space.anyi.seataOrderMicroService.service.StorageService;
          
          /**
          * @author 杨逸
          * @description 针对表【order】的数据库操作Service实现
          * @createDate 2025-09-21 11:22:55
          */
          @Slf4j
          @Service
          public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
              @Autowired
              private AccountService accountService;
              @Autowired
              private StorageService storageService;
          
              @Override
              public void saveOrder(Order order) {
                  log.info("开始创建订单");
                  save(order);
          
                  log.info("减少金额开始");
                  accountService.reduce(order.getUserId(),order.getMoney());
                  log.info("减少金额结束");
          
                  log.info("减少库存开始");
                  storageService.reduce(order.getProductId(),order.getNums());
                  log.info("减少库存结束");
          
                  order.setStatus(0);
                  updateById(order);
                  log.info("订单创建成功");
              }
          }
          
          package space.anyi.seataOrderMicroService.service;
          
          import anyi.sapce.common.entity.ResponseResult;
          import org.springframework.cloud.openfeign.FeignClient;
          import org.springframework.web.bind.annotation.PostMapping;
          import org.springframework.web.bind.annotation.RequestParam;
          
          /**
           * @ProjectName: distributedSystemLearn
           * @FileName: AccountService
           * @Author: 杨逸
           * @Data:2025/9/21 11:35
           * @Description:
           */
          @FeignClient("seata-account-micro-service")
          public interface AccountService {
              @PostMapping("/account/reduce")
              public ResponseResult reduce(@RequestParam("userId")Long userId, @RequestParam("money")Integer money);
          }
          
          
          package space.anyi.seataOrderMicroService.service;
          
          import anyi.sapce.common.entity.ResponseResult;
          import org.springframework.cloud.openfeign.FeignClient;
          import org.springframework.web.bind.annotation.PostMapping;
          import org.springframework.web.bind.annotation.RequestParam;
          
          /**
           * @ProjectName: distributedSystemLearn
           * @FileName: StorageService
           * @Author: 杨逸
           * @Data:2025/9/21 11:36
           * @Description:
           */
          @FeignClient("seata-storage-micro-service")
          public interface StorageService {
              @PostMapping("/storage/reduce")
              public ResponseResult reduce(@RequestParam("productId") Long productId,@RequestParam("nums") Integer nums);
          }
          
          
        • controller

          package space.anyi.seataOrderMicroService.controller;
          
          import anyi.sapce.common.entity.ResponseResult;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.web.bind.annotation.GetMapping;
          import org.springframework.web.bind.annotation.RestController;
          import space.anyi.seataOrderMicroService.domain.Order;
          import space.anyi.seataOrderMicroService.service.OrderService;
          
          /**
           * @ProjectName: distributedSystemLearn
           * @FileName: OrderController
           * @Author: 杨逸
           * @Data:2025/9/21 11:25
           * @Description:
           */
          @RestController
          public class OrderController {
              @Autowired
              private OrderService orderService;
              @GetMapping("/order/save")
              public ResponseResult save(Order order){
                  orderService.saveOrder(order);
                  return ResponseResult.okResult("订单创建成功",null);
              }
          }
          
          
        • 主启动类

          package space.anyi.seataOrderMicroService;
          
          import org.springframework.boot.SpringApplication;
          import org.springframework.boot.autoconfigure.SpringBootApplication;
          import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
          import org.springframework.cloud.openfeign.EnableFeignClients;
          
          /**
           * @ProjectName: distributedSystemLearn
           * @FileName: SeataOrderMicroServiceApplication
           * @Author: 杨逸
           * @Data:2025/9/21 11:24
           * @Description:
           */
          @SpringBootApplication
          @EnableDiscoveryClient
          @EnableFeignClients
          public class SeataOrderMicroServiceApplication {
              public static void main(String[] args) {
                  SpringApplication.run(SeataOrderMicroServiceApplication.class,args);
              }
          }
          
          
      5. 验证搭建成功

    2. 搭建库存微服务模块

      1. 创建"seata_storage_micro_service"模块

      2. 导入依赖

                <!--导入seata依赖-->
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
                </dependency>
                <!--nacos服务发现的依赖-->
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
                </dependency>
                <!--远程调用openFeign的依赖-->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-openfeign</artifactId>
                    <version>3.1.5</version>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </dependency>
                <dependency>
                    <groupId>com.mysql</groupId>
                    <artifactId>mysql-connector-j</artifactId>
                </dependency>
                <dependency>
                    <groupId>com.baomidou</groupId>
                    <artifactId>mybatis-plus-boot-starter</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                </dependency>
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>druid-spring-boot-starter</artifactId>
                </dependency>
                <dependency>
                    <groupId>anyi.space</groupId>
                    <artifactId>common</artifactId>
                    <version>${project.version}</version>
                </dependency>
        
      3. 配置application.yaml

        server:
          port: 10010
        spring:
          datasource:
            type: com.alibaba.druid.pool.DruidDataSource
            druid:
              url: jdbc:mysql://localhost:3306/storage_micro_service?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
              username: root
              password: hsp
              driver-class-name: com.mysql.jdbc.Driver
          application:
            name: seata-storage-micro-service
          cloud:
            nacos:
              discovery:
                server-addr: localhost:8848
            alibaba:
              seata:
                tx-service-group: seata-server
        mybatis-plus:
          configuration:
            log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        logging:
          level:
            io:
              seata: info
        
        
      4. 编写业务代码

        • domain

          package space.anyi.seataStorageMicroService.domain;
          
          import com.baomidou.mybatisplus.annotation.IdType;
          import com.baomidou.mybatisplus.annotation.TableField;
          import com.baomidou.mybatisplus.annotation.TableId;
          import com.baomidou.mybatisplus.annotation.TableName;
          import java.io.Serializable;
          import lombok.Data;
          
          /**
           *
           * @TableName storage
           */
          @TableName(value ="storage")
          @Data
          public class Storage implements Serializable {
              /**
               *
               */
              @TableId(type = IdType.AUTO)
              private Long id;
          
              /**
               *
               */
              private Long productId;
          
              /**
               * 库存量
               */
              private Integer amount;
          
              @TableField(exist = false)
              private static final long serialVersionUID = 1L;
          
              @Override
              public boolean equals(Object that) {
                  if (this == that) {
                      return true;
                  }
                  if (that == null) {
                      return false;
                  }
                  if (getClass() != that.getClass()) {
                      return false;
                  }
                  Storage other = (Storage) that;
                  return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
                      && (this.getProductId() == null ? other.getProductId() == null : this.getProductId().equals(other.getProductId()))
                      && (this.getAmount() == null ? other.getAmount() == null : this.getAmount().equals(other.getAmount()));
              }
          
              @Override
              public int hashCode() {
                  final int prime = 31;
                  int result = 1;
                  result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
                  result = prime * result + ((getProductId() == null) ? 0 : getProductId().hashCode());
                  result = prime * result + ((getAmount() == null) ? 0 : getAmount().hashCode());
                  return result;
              }
          
              @Override
              public String toString() {
                  StringBuilder sb = new StringBuilder();
                  sb.append(getClass().getSimpleName());
                  sb.append(" [");
                  sb.append("Hash = ").append(hashCode());
                  sb.append(", id=").append(id);
                  sb.append(", productId=").append(productId);
                  sb.append(", amount=").append(amount);
                  sb.append(", serialVersionUID=").append(serialVersionUID);
                  sb.append("]");
                  return sb.toString();
              }
          }
          
          
        • mapper

          package space.anyi.seataStorageMicroService.mapper;
          
          import org.apache.ibatis.annotations.Mapper;
          import space.anyi.seataStorageMicroService.domain.Storage;
          import com.baomidou.mybatisplus.core.mapper.BaseMapper;
          
          /**
          * @author 杨逸
          * @description 针对表【storage】的数据库操作Mapper
          * @createDate 2025-09-20 16:56:26
          * @Entity generator.domain.Storage
          */
          @Mapper
          public interface StorageMapper extends BaseMapper<Storage> {
          
              void reduce(Long productId, Integer nums);
          }
          
          
          <?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="space.anyi.seataStorageMicroService.mapper.StorageMapper">
          
              <resultMap id="BaseResultMap" type="space.anyi.seataStorageMicroService.domain.Storage">
                      <id property="id" column="id" jdbcType="BIGINT"/>
                      <result property="productId" column="product_id" jdbcType="BIGINT"/>
                      <result property="amount" column="amount" jdbcType="INTEGER"/>
              </resultMap>
          
              <sql id="Base_Column_List">
                  id,product_id,amount
              </sql>
              <update id="reduce">
                  UPDATE storage
                  SET amount = amount - #{nums}
                  WHERE product_id = #{productId}
              </update>
          </mapper>
          
          
        • service

          package space.anyi.seataStorageMicroService.service;
          
          import space.anyi.seataStorageMicroService.domain.Storage;
          import com.baomidou.mybatisplus.extension.service.IService;
          
          /**
          * @author 杨逸
          * @description 针对表【storage】的数据库操作Service
          * @createDate 2025-09-20 16:56:26
          */
          public interface StorageService extends IService<Storage> {
          
              void reduce(Long productId, Integer nums);
          }
          
          
          package space.anyi.seataStorageMicroService.service.impl;
          
          import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
          import lombok.extern.slf4j.Slf4j;
          import space.anyi.seataStorageMicroService.domain.Storage;
          import space.anyi.seataStorageMicroService.service.StorageService;
          import space.anyi.seataStorageMicroService.mapper.StorageMapper;
          import org.springframework.stereotype.Service;
          
          /**
          * @author 杨逸
          * @description 针对表【storage】的数据库操作Service实现
          * @createDate 2025-09-20 16:56:26
          */
          @Slf4j
          @Service
          public class StorageServiceImpl extends ServiceImpl<StorageMapper, Storage>
              implements StorageService{
          
              @Override
              public void reduce(Long productId, Integer nums) {
                  log.info("扣减库存开始");
                  getBaseMapper().reduce(productId,nums);
                  log.info("扣减库存结束");
              }
          }
          
        • controller

          package space.anyi.seataStorageMicroService.controller;
          
          import anyi.sapce.common.entity.ResponseResult;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.web.bind.annotation.PostMapping;
          import org.springframework.web.bind.annotation.RestController;
          import space.anyi.seataStorageMicroService.service.StorageService;
          
          /**
           * @ProjectName: distributedSystemLearn
           * @FileName: StorageController
           * @Author: 杨逸
           * @Data:2025/9/20 16:59
           * @Description:
           */
          @RestController
          public class StorageController {
              @Autowired
              private StorageService storageService;
              //扣减库存
              @PostMapping("/storage/reduce")
              public ResponseResult reduce(Long productId, Integer nums){
                  storageService.reduce(productId,nums);
                  return ResponseResult.okResult("扣减库存成功ok",null);
              }
          }
          
        • 主启动类

          package space.anyi.seataStorageMicroService;
          
          import org.mybatis.spring.annotation.MapperScan;
          import org.springframework.boot.SpringApplication;
          import org.springframework.boot.autoconfigure.SpringBootApplication;
          import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
          import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
          import org.springframework.cloud.openfeign.EnableFeignClients;
          
          /**
           * @ProjectName: distributedSystemLearn
           * @FileName: SeataStorageMicroServiceApplication
           * @Author: 杨逸
           * @Data:2025/9/20 16:53
           * @Description:
           */
          @MapperScan("space.anyi.seataStorageMicroService.mapper")
          @EnableFeignClients
          @EnableDiscoveryClient
          @SpringBootApplication()
          public class SeataStorageMicroServiceApplication {
              public static void main(String[] args) {
                  SpringApplication.run(SeataStorageMicroServiceApplication.class,args);
              }
          }
          
          
      5. 验证搭建成功

    3. 搭建账号微服务模块

      1. 创建"account_micro_service"模块

      2. 导入依赖

                <!--导入seata依赖-->
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
                    <exclusions>
                        <exclusion>
                            <groupId>com.alibaba</groupId>
                            <artifactId>druid</artifactId>
                        </exclusion>
                        <exclusion>
                            <groupId>com.alibaba</groupId>
                            <artifactId>fastjson</artifactId>
                        </exclusion>
                        <exclusion>
                            <groupId>com.github.ben-manes.caffeine</groupId>
                            <artifactId>caffeine</artifactId>
                        </exclusion>
                    </exclusions>
                </dependency>
                <!--nacos服务发现的依赖-->
                <dependency>
                    <groupId>com.alibaba.cloud</groupId>
                    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
                </dependency>
                <!--远程调用openFeign的依赖-->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-openfeign</artifactId>
                    <version>3.1.5</version>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </dependency>
                <dependency>
                    <groupId>com.mysql</groupId>
                    <artifactId>mysql-connector-j</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                </dependency>
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>druid-spring-boot-starter</artifactId>
                </dependency>
                <dependency>
                    <groupId>anyi.space</groupId>
                    <artifactId>common</artifactId>
                    <version>${project.version}</version>
                </dependency>
        
      3. 配置aoolication.yaml

        server:
          port: 10011
        spring:
          datasource:
            type: com.alibaba.druid.pool.DruidDataSource
            druid:
              url: jdbc:mysql://localhost:3306/account_micro_service?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
              username: root
              password: hsp
              driver-class-name: com.mysql.jdbc.Driver
          application:
            name: seata-account-micro-service
          cloud:
            nacos:
              discovery:
                server-addr: localhost:8848
            alibaba:
              seata:
                tx-service-group: seata-server
        mybatis-plus:
          configuration:
            log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        logging:
          level:
            io:
              seata: info
        
      4. 编写业务代码

        • domain

          package space.anyi.seataAccountMicroService.domain;
          
          import com.baomidou.mybatisplus.annotation.IdType;
          import com.baomidou.mybatisplus.annotation.TableField;
          import com.baomidou.mybatisplus.annotation.TableId;
          import com.baomidou.mybatisplus.annotation.TableName;
          import java.io.Serializable;
          import lombok.Data;
          
          /**
           *
           * @TableName account
           */
          @TableName(value ="account")
          @Data
          public class Account implements Serializable {
              /**
               *
               */
              @TableId(type = IdType.AUTO)
              private Long id;
          
              /**
               *
               */
              private Long userId;
          
              /**
               * 账户金额
               */
              private Integer money;
          
              @TableField(exist = false)
              private static final long serialVersionUID = 1L;
          
              @Override
              public boolean equals(Object that) {
                  if (this == that) {
                      return true;
                  }
                  if (that == null) {
                      return false;
                  }
                  if (getClass() != that.getClass()) {
                      return false;
                  }
                  Account other = (Account) that;
                  return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
                      && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
                      && (this.getMoney() == null ? other.getMoney() == null : this.getMoney().equals(other.getMoney()));
              }
          
              @Override
              public int hashCode() {
                  final int prime = 31;
                  int result = 1;
                  result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
                  result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
                  result = prime * result + ((getMoney() == null) ? 0 : getMoney().hashCode());
                  return result;
              }
          
              @Override
              public String toString() {
                  StringBuilder sb = new StringBuilder();
                  sb.append(getClass().getSimpleName());
                  sb.append(" [");
                  sb.append("Hash = ").append(hashCode());
                  sb.append(", id=").append(id);
                  sb.append(", userId=").append(userId);
                  sb.append(", money=").append(money);
                  sb.append(", serialVersionUID=").append(serialVersionUID);
                  sb.append("]");
                  return sb.toString();
              }
          }
          
          
        • mapper

          package space.anyi.seataAccountMicroService.mapper;
          
          import space.anyi.seataAccountMicroService.domain.Account;
          import com.baomidou.mybatisplus.core.mapper.BaseMapper;
          
          /**
          * @author 杨逸
          * @description 针对表【account】的数据库操作Mapper
          * @createDate 2025-09-21 10:52:56
          * @Entity generator.domain.Account
          */
          public interface AccountMapper extends BaseMapper<Account> {
          
              void reduce(Long userId, Integer money);
          }
          
          <?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="space.anyi.seataAccountMicroService.mapper.AccountMapper">
          
              <resultMap id="BaseResultMap" type="space.anyi.seataAccountMicroService.domain.Account">
                      <id property="id" column="id" jdbcType="BIGINT"/>
                      <result property="userId" column="user_id" jdbcType="BIGINT"/>
                      <result property="money" column="money" jdbcType="INTEGER"/>
              </resultMap>
          
              <sql id="Base_Column_List">
                  id,user_id,money
              </sql>
              <update id="reduce">
                  UPDATE account
                  SET money = money - #{money}
                  WHERE user_id = #{userId};
              </update>
          </mapper>
          
          
        • service

          package space.anyi.seataAccountMicroService.service;
          
          import space.anyi.seataAccountMicroService.domain.Account;
          import com.baomidou.mybatisplus.extension.service.IService;
          
          /**
          * @author 杨逸
          * @description 针对表【account】的数据库操作Service
          * @createDate 2025-09-21 10:52:56
          */
          public interface AccountService extends IService<Account> {
          
              void reduce(Long userId, Integer money);
          }
          
          
          package space.anyi.seataAccountMicroService.service.impl;
          
          import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
          import lombok.extern.slf4j.Slf4j;
          import space.anyi.seataAccountMicroService.domain.Account;
          import space.anyi.seataAccountMicroService.service.AccountService;
          import space.anyi.seataAccountMicroService.mapper.AccountMapper;
          import org.springframework.stereotype.Service;
          
          /**
          * @author 杨逸
          * @description 针对表【account】的数据库操作Service实现
          * @createDate 2025-09-21 10:52:56
          */
          @Service
          @Slf4j
          public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account>
              implements AccountService{
          
              @Override
              public void reduce(Long userId, Integer money) {
                  log.info("扣款开始");
                  getBaseMapper().reduce(userId, money);
                  log.info("扣款结束");
              }
          }
          
        • controller

          package space.anyi.seataAccountMicroService.controller;
          
          import anyi.sapce.common.entity.ResponseResult;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.web.bind.annotation.PostMapping;
          import org.springframework.web.bind.annotation.RequestParam;
          import org.springframework.web.bind.annotation.RestController;
          import space.anyi.seataAccountMicroService.service.AccountService;
          
          /**
           * @ProjectName: distributedSystemLearn
           * @FileName: AccountController
           * @Author: 杨逸
           * @Data:2025/9/21 10:59
           * @Description:
           */
          @RestController
          public class AccountController {
              @Autowired
              private AccountService accountService;
              @PostMapping("/account/reduce")
              public ResponseResult reduce(@RequestParam("userId")Long userId, @RequestParam("money")Integer money){
                  accountService.reduce(userId,money);
                  return ResponseResult.okResult(200,"扣减账户余额OK");
              }
          }
          
          
        • 主启动类

          package space.anyi.seataAccountMicroService;
          
          import org.mybatis.spring.annotation.MapperScan;
          import org.springframework.boot.SpringApplication;
          import org.springframework.boot.autoconfigure.SpringBootApplication;
          import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
          import org.springframework.cloud.openfeign.EnableFeignClients;
          
          /**
           * @ProjectName: distributedSystemLearn
           * @FileName: SeataAccountMicroServiceApplication
           * @Author: 杨逸
           * @Data:2025/9/21 10:50
           * @Description:
           */
          @MapperScan("space.anyi.seataAccountMicroService.mapper")
          @SpringBootApplication
          @EnableFeignClients
          @EnableDiscoveryClient
          public class SeataAccountMicroServiceApplication {
              public static void main(String[] args) {
                  SpringApplication.run(SeataAccountMicroServiceApplication.class,args);
              }
          }
          
          
      5. 验证搭建成功

  6. 接口测试,验证三个微服务模块之间的调用链路通畅

    1. 接口调用

    1. 数据一致性验证,查看数据表的数据修改是一致的

  7. 模拟一个异常导致链路调用失败,使得数据不一致

    • 在"seata-storage-micro-service"服务的业务中加入一段睡眠代码,模拟调用超时

      package space.anyi.seataStorageMicroService.controller;
      
      import anyi.sapce.common.entity.ResponseResult;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RestController;
      import space.anyi.seataStorageMicroService.service.StorageService;
      
      import java.util.concurrent.TimeUnit;
      
      /**
       * @ProjectName: distributedSystemLearn
       * @FileName: StorageController
       * @Author: 杨逸
       * @Data:2025/9/20 16:59
       * @Description:
       */
      @RestController
      public class StorageController {
          @Autowired
          private StorageService storageService;
          //扣减库存
          @PostMapping("/storage/reduce")
          public ResponseResult reduce(Long productId, Integer nums){
              //模拟一个延迟,使得调用链路超时,导致的调用失败
              try {
                  TimeUnit.SECONDS.sleep(12);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              storageService.reduce(productId,nums);
              return ResponseResult.okResult("扣减库存成功ok",null);
          }
      }
      
      
    • 在调用方配置openfeign的超时时间

      feign:
        client:
          config:
            #配置超时时间
            seata-storage-micro-service:
              #读取资源超时时间
              readTimeout: 6000
              #建立连接超时时间
              connectTimeout: 1000
      
    • 验证出现异常导致的数据不一致问题

  8. 使用seata解决分布式事务

    1. 在"seata-order-micro-service"服务中配置seata

      seata:
        registry:
          # 配置seata客户端的注册中心, 告诉seata client 怎么去访问seata server(TC)
          type: nacos
          nacos:
            # seata server 所在的nacos服务地址
            server-addr: 127.0.0.1:8848
            # seata server 的服务名seata-server ,如果没有修改可以不配
            application: seata-server
            # seata server 所在的组,默认就是SEATA_GROUP,没有改也可以不配
            group: SEATA_GROUP
            cluster: default
        config:
          type: nacos
          nacos:
            server-addr: 127.0.0.1:8848
            group: SEATA_GROUP
        #这里每个服务都是对应不同的映射名,在配置中心可以看到
        tx-service-group: order_tx_group
        service:
          vgroup-mapping:
            order_tx_group: default
      

      在nacos创建对应的配置

    2. 在"seata-storage-micro-service"服务中配置seata

      seata:
        registry:
          # 配置seata客户端的注册中心, 告诉seata client 怎么去访问seata server(TC)
          type: nacos
          nacos:
            # seata server 所在的nacos服务地址
            server-addr: 127.0.0.1:8848
            # seata server 的服务名seata-server ,如果没有修改可以不配
            application: seata-server
            # seata server 所在的组,默认就是SEATA_GROUP,没有改也可以不配
            group: SEATA_GROUP
            cluster: default
        config:
          type: nacos
          nacos:
            server-addr: 127.0.0.1:8848
            group: SEATA_GROUP
        #这里每个服务都是对应不同的映射名,在配置中心可以看到
        tx-service-group: storage_tx_group
        service:
          vgroup-mapping:
            storage_tx_group: default
      
      

      在nacos创建对应的配置

    3. 在"seata-account-micro-service"服务中配置seata

      seata:
        registry:
          # 配置seata客户端的注册中心, 告诉seata client 怎么去访问seata server(TC)
          type: nacos
          nacos:
            # seata server 所在的nacos服务地址
            server-addr: 127.0.0.1:8848
            # seata server 的服务名seata-server ,如果没有修改可以不配
            application: seata-server
            # seata server 所在的组,默认就是SEATA_GROUP,没有改也可以不配
            group: SEATA_GROUP
            cluster: default
        config:
          type: nacos
          nacos:
            server-addr: 127.0.0.1:8848
            group: SEATA_GROUP
        #这里每个服务都是对应不同的映射名,在配置中心可以看到
        tx-service-group: account_tx_group
        service:
          vgroup-mapping:
            account_tx_group: default
      
      

      在nacos上创建对应的配置

    4. 在业务链路调用入口使用"GlobalTransactional"注解开启分布式事务

      package space.anyi.seataOrderMicroService.service.impl;
      
      import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
      import io.seata.spring.annotation.GlobalTransactional;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.annotation.Autowired;
      import space.anyi.seataOrderMicroService.domain.Order;
      import space.anyi.seataOrderMicroService.service.AccountService;
      import space.anyi.seataOrderMicroService.service.OrderService;
      import space.anyi.seataOrderMicroService.mapper.OrderMapper;
      import org.springframework.stereotype.Service;
      import space.anyi.seataOrderMicroService.service.StorageService;
      
      /**
      * @author 杨逸
      * @description 针对表【order】的数据库操作Service实现
      * @createDate 2025-09-21 11:22:55
      */
      @Slf4j
      @Service
      public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
          @Autowired
          private AccountService accountService;
          @Autowired
          private StorageService storageService;
          //使用@GlobalTransactional开启分布式事务
          //name为事务名称,rollbackFor指定回滚的异常类型
          @GlobalTransactional(name = "seata_order_micro_service",rollbackFor = Exception.class)
          @Override
          public void saveOrder(Order order) {
              log.info("开始创建订单");
              save(order);
      
              log.info("减少金额开始");
              accountService.reduce(order.getUserId(),order.getMoney());
              log.info("减少金额结束");
      
              log.info("减少库存开始");
              storageService.reduce(order.getProductId(),order.getNums());
              log.info("减少库存结束");
      
              order.setStatus(0);
              updateById(order);
              log.info("订单创建成功");
          }
      }
      

未验证成功

出现错误

### Error updating database.  Cause: java.sql.SQLException: io.seata.common.loader.EnhancedServiceNotFoundException: not found service provider for : io.seata.rm.datasource.sql.struct.TableMetaCache