文章目录
🍊 seata 简介
 

-  TC(Transaction Coordinator) - 事务协调者
 维护全局和分支事务的状态,驱动全局事务提交或回滚。
-  TM(Transaction Manager) - 事务管理器
 定义全局事务的范围:开始全局事务、提交或回滚全局事务。
-  RM(Resource Manager) - 资源管理器
 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
四种事务模式:
- AT模式:提供无侵入自动补偿的事务模式。自动补偿的- sql是系统生成的。
- TCC模式: 反向补偿的- sql需要自己手动去写。
- XA模式:支持已实现- XA接口的数据库的- XA模式。
- SAGA模式:为长事务提供有效的解决方案,提供编排式与注解式。
自动补偿(又称反向补偿): 例如有 a,b,c 三个服务,现在 a 分别调用服务 b 和 c,为了确保 b 和 c 的调用同时成功或者同时失败,那么就要使用分布式事务。假设先调用 b 在调用 c, b 调用完成之后,事务就提交了,然后调用 c,c 服务出错,那么现在需要回滚。此时 b 需要回滚,但是回滚并不是我们传统意义上的回滚,而是通过一条 sql 将 b 服务中的数据进行复原,这个过程就是反向补偿。
🍅 安装 seata-server
 
seata 所提供的 seata-server 本质上就是⼀个 SpringBoot。
- 下载地址: https://github.com/seata/seata/releases/tag/v1.5.2
- 启动 bin目录下的seata-server.bat文件
- 本地启动后访问地址:http://localhost:7091/ ,7091是后台管理页面访问的端口,7091是通信端口。
- 配置文件:application.example.yml案例的配置文件

 6. 如果存储是 db 形式,数据库脚本的位置:
 

🥔 案例
🍓 准备
地址:https://seata.io/zh-cn/docs/user/quickstart.html

新建两个数据库,并在每个数据库中创建 UNDO_LOG  表
account 库:
# ************************************************************
# Sequel Pro SQL dump
# Version 5446
#
# https://www.sequelpro.com/
# https://github.com/sequelpro/sequelpro
#
# Host: 127.0.0.1 (MySQL 5.7.26)
# Database: account
# Generation Time: 2022-06-04 04:01:18 +0000
# ************************************************************
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
SET NAMES utf8mb4;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
# Dump of table account_tbl
# ------------------------------------------------------------
DROP TABLE IF EXISTS `account_tbl`;
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;
LOCK TABLES `account_tbl` WRITE;
/*!40000 ALTER TABLE `account_tbl` DISABLE KEYS */;
INSERT INTO `account_tbl` (`id`, `user_id`, `money`)
VALUES
	(1,'javaboy',999000);
/*!40000 ALTER TABLE `account_tbl` ENABLE KEYS */;
UNLOCK TABLES;
# Dump of table undo_log
# ------------------------------------------------------------
DROP TABLE IF EXISTS `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,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
order 库:
# ************************************************************
# Sequel Pro SQL dump
# Version 5446
#
# https://www.sequelpro.com/
# https://github.com/sequelpro/sequelpro
#
# Host: 127.0.0.1 (MySQL 5.7.26)
# Database: order
# Generation Time: 2022-06-04 04:01:35 +0000
# ************************************************************
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
SET NAMES utf8mb4;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
# Dump of table order_tbl
# ------------------------------------------------------------
DROP TABLE IF EXISTS `order_tbl`;
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;
LOCK TABLES `order_tbl` WRITE;
/*!40000 ALTER TABLE `order_tbl` DISABLE KEYS */;
INSERT INTO `order_tbl` (`id`, `user_id`, `commodity_code`, `count`, `money`)
VALUES
	(16,'javaboy','1111',10,1000),
	(17,'javaboy','1111',10,1000),
	(18,'javaboy','1111',10,1000),
	(20,'javaboy','1111',10,1000);
/*!40000 ALTER TABLE `order_tbl` ENABLE KEYS */;
UNLOCK TABLES;
# Dump of table undo_log
# ------------------------------------------------------------
DROP TABLE IF EXISTS `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,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
storage 库:
# ************************************************************
# Sequel Pro SQL dump
# Version 5446
#
# https://www.sequelpro.com/
# https://github.com/sequelpro/sequelpro
#
# Host: 127.0.0.1 (MySQL 5.7.26)
# Database: storage
# Generation Time: 2022-06-04 04:01:47 +0000
# ************************************************************
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
SET NAMES utf8mb4;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
# Dump of table storage_tbl
# ------------------------------------------------------------
DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_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;
LOCK TABLES `storage_tbl` WRITE;
/*!40000 ALTER TABLE `storage_tbl` DISABLE KEYS */;
INSERT INTO `storage_tbl` (`id`, `commodity_code`, `count`)
VALUES
	(2,'1111',60);
/*!40000 ALTER TABLE `storage_tbl` ENABLE KEYS */;
UNLOCK TABLES;
# Dump of table undo_log
# ------------------------------------------------------------
DROP TABLE IF EXISTS `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,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
seata AT 模式需要 UNDO_LOG 表
**-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  -- 分支事务的 id
  `branch_id` bigint(20) NOT NULL,
  -- 全局事务的 id
  `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;**

新建 seata-ax 的 maven 项目,并在该项目下创建子模块(spring boot项目):

创建 eureka 的 spirng boot 项目,并添加如下依赖:
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
添加配置:
# 应用名称
spring.application.name=eureka
# 应用服务 WEB 访问端口
server.port=8761
# 不获取其他的服务,也不将自己注册到其他的 eureka 上面去
eureka.client.fetch-registry=false
eureka.client.register-with-eureka=false
启动类上添加开启 eureka 的注解:
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}
创建 maven 项目,common 模块:
创建同一的响应处理类:
public class RespBean {
    private Integer status;
    private  String msg;
    private Object data;
    public static RespBean ok(String msg,Object data){
        return  new RespBean(200,msg,data);
    }
    public static RespBean ok(String msg){
        return  new RespBean(200,msg,null);
    }
    public static RespBean error(String msg,Object data){
        return  new RespBean(400,msg,data);
    }
    public static RespBean error(String msg){
        return  new RespBean(200,msg,null);
    }
    public RespBean() {
    }
    public RespBean(Integer status, String msg, Object data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }
    public Integer getStatus() {
        return status;
    }
    public void setStatus(Integer status) {
        this.status = status;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }
}
创建全局异常处理类:
@RestControllerAdvice
public class GlobalException {
    @ExceptionHandler(RuntimeException.class)
    // 给一个错误的状态码,让事务回滚
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public RespBean runtimeException(RuntimeException e){
        return RespBean.error(e.getMessage());
    }
}
🍓 创建 account 模块
 
添加项目依赖:

<!--        common -->
        <dependency>
            <groupId>org.javaboy</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
<!--        web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
<!--        mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
<!--        eureka client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
<!--mysql 驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
添加配置:
# 应用名称
spring.application.name=account
# 应用服务 WEB 访问端口
server.port=1111
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mappers/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=org.javaboy.account.mybatis.entity
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://192.168.10.10:3306/account?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
# 注册到 eureka 上
eureka.client.service-url.defalutZone=http://localhost:8761/eureka
# seata 事务分组信息
spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
在 resource 文件目录下添加 file.conf 和 registry.conf 文件,将 account 项目注册到 seata TC(全局事务协调者)中
file.conf 文件:
transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  # the client batch send request enable
  enableClientBatchSendRequest = true
  #thread factory for netty
  threadFactory {
    bossThreadPrefix = "NettyBoss"
    workerThreadPrefix = "NettyServerNIOWorker"
    serverExecutorThread-prefix = "NettyServerBizHandler"
    shareBossWorker = false
    clientSelectorThreadPrefix = "NettyClientSelector"
    clientSelectorThreadSize = 1
    clientWorkerThreadPrefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    bossThreadSize = 1
    #auto default pin or 8
    workerThreadSize = "default"
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}
service {
  #transaction service group mapping
  vgroupMapping.my_test_tx_group = "default"
  #only support when registry.type=file, please don't set multiple addresses
  default.grouplist = "127.0.0.1:8091"
  #degrade, current not support
  enableDegrade = false
  #disable seata
  disableGlobalTransaction = false
}
client {
  rm {
    asyncCommitBufferLimit = 10000
    lock {
      retryInterval = 10
      retryTimes = 30
      retryPolicyBranchRollbackOnConflict = true
    }
    reportRetryCount = 5
    tableMetaCheckEnable = false
    reportSuccessEnable = false
  }
  tm {
    commitRetryCount = 5
    rollbackRetryCount = 5
  }
  undo {
    dataValidation = true
    logSerialization = "jackson"
    logTable = "undo_log"
  }
  log {
    exceptionRate = 100
  }
}
registry.conf 文件:
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "eureka"
  nacos {
    application = "seata-server"
    serverAddr = "localhost"
    namespace = ""
    username = ""
    password = ""
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
    password = ""
    timeout = "0"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}
config {
  # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig
  type = "file"
  nacos {
    serverAddr = "localhost"
    namespace = ""
    group = "SEATA_GROUP"
    username = ""
    password = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    appId = "seata-server"
    apolloMeta = "http://192.168.1.204:8801"
    namespace = "application"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    sessionTimeout = 6000
    connectTimeout = 2000
    username = ""
    password = ""
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}
添加 AccountMapper 接口:
@Mapper
@Repository
public interface AccountMapper {
   @Update("update account_tbl set money=money-#{money} where user_id=#{account} ")
   int  updateAccount(@Param("account") String account,@Param("money") double money);
   @Select("select money from account_tbl where user_id =#{account} ")
   double getMoneyByAccount(String account);
}
添加 AccountService :
@Service
public class AccountService {
    @Autowired
    private AccountMapper accountMapper;
    // 扣款接口
    public boolean deducAccount(String account,Double money){
        accountMapper.updateAccount(account,money);
        double m = accountMapper.getMoneyByAccount(account);
        if (m>=0){
            return true;
        }
        throw new RuntimeException("账户余额不足扣款失败!");
    }
}
添加 AccountController 接口:
@RestController
public class AccountController {
    @Autowired
    private AccountService accountService;
    @PostMapping("/deductAccount")
    public RespBean deduct(String account,Double money){
        if (accountService.deducAccount(account,money)){
            return RespBean.ok("扣款成功!");
        }
        return RespBean.error("口款失败!");
    }
}
🍓 创建 order 模块
 
添加依赖:

<!--        common -->
        <dependency>
            <groupId>org.javaboy</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
<!--        web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        seata -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
<!--        mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
<!--        eureka-client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
<!--        openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
<!-- mysql-driver -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
添加 配置:
# 应用名称
spring.application.name=order
# 应用服务 WEB 访问端口
server.port=1113
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mappers/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=org.javaboy.order.mybatis.entity
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://192.168.10.10:3306/order?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
# 注册到 eureka 上
eureka.client.service-url.defalutZone=http://localhost:8761/eureka
# seata 事务分组信息
spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
在 resource 文件目录下添加 file.conf 和 registry.conf 文件。(文件和上面模块一样此处省略…)
启动类上添加 @EnableEurekaClient  和 @EnableFeignClients 注解。
创建 AccountFeign 调用 account 模块中的接口:
@FeignClient("ACCOUNT")
public interface AccountFeign {
    @PostMapping("/deductAccount")
    RespBean deduct(@RequestParam("account") String account, @RequestParam("money") Double money);
}
创建 OrderMapper 接口:
@Mapper
@Repository
public interface OrderMapper {
    @Insert("insert into order_tbl(user_id,commodity_code,count,money) values(#{userId},#{commodityCode},#{count},#{money})")
    int addOrder(@Param("userId") String userId, @Param("commodityCode") String commodityCode,
                 @Param("count") Integer count,@Param("money") Double money);
}
创建 Orderservice 类:
@Service
public class Orderservice {
    @Autowired
    private AccountFeign accountFeign;
    @Autowired
    private OrderMapper orderMapper;
    /**
     *  创建订单
     * @param account 账号
     * @param productId 商品 id
     * @param count 购买数量
     * @return
     */
    public  boolean createOrder(String account,String productId,Integer count){
        // 先去扣款,假设每件商品都是 100 元
        RespBean respBean = accountFeign.deduct(account, count * 100.0);
        int i = orderMapper.addOrder(account, productId, count, count * 100.0);
        return  i==1 && respBean.getStatus() == 200;
    }
}
创建 OrderController 类:
@RestController
public class OrderController {
    @Autowired
    private Orderservice orderservice;
    @PostMapping("createOrder")
    public RespBean creatOrder(String account,String productId,Integer count){
        if (orderservice.createOrder(account,productId,count)){
            return RespBean.ok("下单成功!~");
        }
        return RespBean.error("下单失败!");
    }
}
🍓 创建 storage 模块
 
添加依赖:

<!--        common -->
        <dependency>
            <groupId>org.javaboy</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
<!--        web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        seata -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
<!--        mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
<!--        eureka-client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
<!--mysql-driver-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
添加配置:
# 应用名称
spring.application.name=storage
# 应用服务 WEB 访问端口
server.port=1114
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mappers/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=org.javaboy.storage.mybatis.entity
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://192.168.10.10:3306/storage?serverTimezone=UTC
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=root
# 注册到 eureka 上
eureka.client.service-url.defalutZone=http://localhost:8761/eureka
# seata 事务分组信息
spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
在 resource 文件目录下添加 file.conf 和 registry.conf 文件。(文件和上面模块一样此处省略…)
创建 StorageMapper 接口:
@Mapper
@Repository
public interface StorageMapper {
    @Update("update storage_tbl set count=count-#{count} where commodity_code=#{productId}")
    int deductStorage(@Param("productId") String productId, @Param("count") Integer count);
    @Select("select count from storage_tbl where commodity_code=#{commodityCode}")
    int getCountByCommodityCode(String commodityCode);
}
创建 StorageService 业务类:
@Service
public class StorageService {
    @Autowired
    private StorageMapper storageMapper;
    public boolean deduct(String productId,Integer count){
        int i = storageMapper.deductStorage(productId, count);
        int resut = storageMapper.getCountByCommodityCode(productId);
        if (resut>=0){
            return true;
        }
        throw new RuntimeException("库存不足,扣库存失败!");
    }
}
创建接口类:
@RestController
public class StorageController {
    @Autowired
    private StorageService storageService;
    @PostMapping("deduct")
    public RespBean deduct(String prodectId,Integer count){
        if (storageService.deduct(prodectId,count)){
            return  RespBean.ok("扣库存成功!");
        }
        return RespBean.error("扣库存失败!");
    }
}
🍓 创建 business 模块
 
添加项目依赖:
<!--        common -->
        <dependency>
            <groupId>org.javaboy</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
<!--        web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        seata  -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
<!--        eureka-client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
<!--        openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
添加配置:
# 应用名称
spring.application.name=business
# 应用服务 WEB 访问端口
server.port=1112
# 注册到 eureka 上
eureka.client.service-url.defalutZone=http://localhost:8761/eureka
# seata 事务分组信息
spring.cloud.alibaba.seata.tx-service-group=my_test_tx_group
启动类添加注解:
@EnableFeignClients
@EnableDiscoveryClient
添加 StorageFeign 和 OrderFeign 接口:
@FeignClient("STORAGE")
public interface StorageFeign {
    @PostMapping("/deduct")
    RespBean deduct(@RequestParam("prodectId") String prodectId, @RequestParam("count") Integer count);
}
@FeignClient("ORDER")
public interface OrderFeign {
    @PostMapping("/createOrder")
    RespBean creatOrder(@RequestParam("account") String account, @RequestParam("productId")String productId,@RequestParam("count") Integer count);
}
添加业务类,并添加全局异常处理的注解:
@Service
public class BusinessService {
    @Autowired
    private StorageFeign storageFeign;
    @Autowired
    private OrderFeign orderFeign;
    @GlobalTransactional
    public void purchase(String account,String productId,Integer count){
        orderFeign.creatOrder(account,productId,count);
        storageFeign.deduct(productId,count);
    }
}
创建 BusinessController 接口:
@RestController
public class BusinessController {
    @Autowired
    private BusinessService businessService;
    @PostMapping("order")
    public RespBean order(String account, String productId, Integer count) {
        try {
            businessService.purchase(account, productId, count);
            return RespBean.ok("下单成功!");
        } catch (Exception e) {
            e.printStackTrace();
            return RespBean.error("下单失败!");
        }
    }
}
测试:











