概念 分布式事务处理过程的-ID+三组件模型
Transaction ID XID 全局唯一的事务ID 
3组件概念
TC (Transaction Coordinator) - 事务协调者 
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器 
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器 
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
1.TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
2.XID在微服务调用链路的上下文中传播
3.RM向TC注册分支事务,将其耐入XID对应的全局事务的管辖
4.TM向TC发起针对XID的全局提交或回滚决议
5.TC调度XID下管辖的全部分支事务完成提交或回滚请求
Seta Server下载配置 下载 http://seata.io/zh-cn/blog/download.htmlj 
选择版本点击source下载即可
我下载的是1.0.0版本
安装 下载完成只后直接解压即可,得到目录
配置 进入到conf文件夹,先备份原始file.conf文件为file - 副本.conf
service 接着修改file.conf中的service模块将vgroup_mapping.my_test_tx_group = "default"中的default修改为kylin_tx_group
1 2 3 4 5 6 7 8 9 service {   #transaction service group mapping   #vgroup_mapping.my_test_tx_group = "default"   vgroup_mapping.my_test_tx_group = "kylin_tx_group"   #only support when registry.type=file, please don't set multiple addresses   default.grouplist = "127.0.0.1:8091"   #disable seata   disableGlobalTransaction = false } 
store 接着修改store模块mode = "file"file改为db,同时配置好自己的mysql用户名和密码
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 service {   #transaction service group mapping   #vgroup_mapping.my_test_tx_group = "default"   vgroup_mapping.my_test_tx_group = "kylin_tx_group"   #only support when registry.type=file, please don't set multiple addresses   default.grouplist = "127.0.0.1:8091"   #disable seata   disableGlobalTransaction = false } ## transaction log store, only used in seata-server store {   ## store mode: file、db   mode = "db"   ## file store property   file {     ## store location dir     dir = "sessionStore"   }   ## database store property   db {     ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.     datasource = "dbcp"     ## mysql/oracle/h2/oceanbase etc.     db-type = "mysql"     driver-class-name = "com.mysql.jdbc.Driver"     url = "jdbc:mysql://127.0.0.1:3306/seata"     user = "root"     password = "kylin"   } } 
数据库 新建seata数据库
运行建表sql
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 drop  table  if exists  `global_table`;create  table  `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`) );   drop  table  if exists  `branch_table`;create  table  `branch_table` (  `branch_id` bigint  not  null ,   `xid` varchar (128 ) not  null ,   `transaction_id` bigint  ,   `resource_group_id` varchar (32 ),   `resource_id` varchar (256 ) ,   `lock_key` varchar (128 ) ,   `branch_type` varchar (8 ) ,   `status` tinyint,   `client_id` varchar (64 ),   `application_data` varchar (2000 ),   `gmt_create` datetime,   `gmt_modified` datetime,   primary  key (`branch_id`),   key `idx_xid` (`xid`) );   drop  table  if exists  `lock_table`;create  table  `lock_table` (  `row_key` varchar (128 ) not  null ,   `xid` varchar (96 ),   `transaction_id` long ,   `branch_id` long,   `resource_id` varchar (256 ) ,   `table_name` varchar (32 ) ,   `pk` varchar (36 ) ,   `gmt_create` datetime ,   `gmt_modified` datetime,   primary  key(`row_key`) );   
registry.conf 将conf目录下的registry.conf备份registry - 副本.conf后修改,目的是:指明注册中心为nacos,及修改nacos连接信息
测试 先启动Nacos,在从bin目录下点击seata-server.bat运行Seta Server
运行成功!
数据库准备 seata_order: 存储订单的数据库
seata_storage:存储库存的数据库
seata_account: 存储账户信息的数据库
1 2 3 4 5 CREATE  DATABASE seata_order;CREATE  DATABASE seata_storage;  CREATE  DATABASE seata_account;
seata_order库下建t_order表
1 2 3 4 5 6 7 8 9 10 CREATE  TABLE  t_order(    `id` BIGINT (11 ) NOT  NULL  AUTO_INCREMENT PRIMARY  KEY,     `user_id` BIGINT (11 ) DEFAULT  NULL  COMMENT '用户id' ,     `product_id` BIGINT (11 ) DEFAULT  NULL  COMMENT '产品id' ,     `count` INT (11 ) DEFAULT  NULL  COMMENT '数量' ,     `money` DECIMAL (11 ,0 ) DEFAULT  NULL  COMMENT '金额' ,     `status` INT (1 ) DEFAULT  NULL  COMMENT '订单状态:0:创建中; 1:已完结'  ) ENGINE= INNODB AUTO_INCREMENT= 7  DEFAULT  CHARSET= utf8;   SELECT  *  FROM  t_order;
seata_storage库下建t_storage表
1 2 3 4 5 6 7 8 9 10 11 12 13 CREATE  TABLE  t_storage(    `id` BIGINT (11 ) NOT  NULL  AUTO_INCREMENT PRIMARY  KEY,     `product_id` BIGINT (11 ) DEFAULT  NULL  COMMENT '产品id' ,    `total` INT (11 ) DEFAULT  NULL  COMMENT '总库存' ,     `used` INT (11 ) DEFAULT  NULL  COMMENT '已用库存' ,     `residue` INT (11 ) DEFAULT  NULL  COMMENT '剩余库存'  ) ENGINE= INNODB AUTO_INCREMENT= 2  DEFAULT  CHARSET= utf8;   INSERT  INTO  seata_storage.t_storage(`id`,`product_id`,`total`,`used`,`residue`)VALUES ('1' ,'1' ,'100' ,'0' ,'100' );    SELECT  *  FROM  t_storage;
seata_account库下建t_account表
1 2 3 4 5 6 7 8 9 10 11 CREATE  TABLE  t_account(    `id` BIGINT (11 ) NOT  NULL  AUTO_INCREMENT PRIMARY  KEY COMMENT 'id' ,     `user_id` BIGINT (11 ) DEFAULT  NULL  COMMENT '用户id' ,     `total` DECIMAL (10 ,0 ) DEFAULT  NULL  COMMENT '总额度' ,     `used` DECIMAL (10 ,0 ) DEFAULT  NULL  COMMENT '已用余额' ,     `residue` DECIMAL (10 ,0 ) DEFAULT  '0'  COMMENT '剩余可用额度'  ) ENGINE= INNODB AUTO_INCREMENT= 2  DEFAULT  CHARSET= utf8;   INSERT  INTO  seata_account.t_account(`id`,`user_id`,`total`,`used`,`residue`) VALUES ('1' ,'1' ,'1000' ,'0' ,'1000' );SELECT  *  FROM  t_account;
按照上述3库分别建对应的回滚日志表db_undo_log.sql
1 2 3 4 5 6 7 8 9 10 11 12 13 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; 
环境搭建 下订单->减库存->扣余额->改(订单)状态
新建订单Order-Module 创建模块seata-order-service2001
pom 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 <dependencies >          <dependency >          <groupId > com.alibaba.cloud</groupId >          <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId >      </dependency >           <dependency >          <groupId > com.alibaba.cloud</groupId >          <artifactId > spring-cloud-starter-alibaba-seata</artifactId >          <exclusions >              <exclusion >                  <artifactId > seata-all</artifactId >                  <groupId > io.seata</groupId >              </exclusion >          </exclusions >      </dependency >      <dependency >          <groupId > io.seata</groupId >          <artifactId > seata-all</artifactId >          <version > 1.0.0</version >      </dependency >           <dependency >          <groupId > org.springframework.cloud</groupId >          <artifactId > spring-cloud-starter-openfeign</artifactId >      </dependency >           <dependency >          <groupId > org.springframework.boot</groupId >          <artifactId > spring-boot-starter-web</artifactId >      </dependency >      <dependency >          <groupId > org.springframework.boot</groupId >          <artifactId > spring-boot-starter-actuator</artifactId >      </dependency >           <dependency >          <groupId > mysql</groupId >          <artifactId > mysql-connector-java</artifactId >          <version > 5.1.37</version >      </dependency >      <dependency >          <groupId > com.alibaba</groupId >          <artifactId > druid-spring-boot-starter</artifactId >          <version > 1.1.10</version >      </dependency >      <dependency >          <groupId > org.mybatis.spring.boot</groupId >          <artifactId > mybatis-spring-boot-starter</artifactId >          <version > 2.0.0</version >      </dependency >      <dependency >          <groupId > org.springframework.boot</groupId >          <artifactId > spring-boot-starter-test</artifactId >          <scope > test</scope >      </dependency >      <dependency >          <groupId > org.projectlombok</groupId >          <artifactId > lombok</artifactId >          <optional > true</optional >      </dependency >  </dependencies > 
yml 
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 server:   port:  2001  spring:   application:      name:  seata-order-service    cloud:      alibaba:        seata:                   tx-service-group:  kylin_tx_group      nacos:        discovery:          server-addr:  localhost:8848    datasource:      driver-class-name:  com.mysql.jdbc.Driver      url:  jdbc:mysql://localhost:3306/seata_order      username:  root      password:  kylin  feign:   hystrix:      enabled:  false  logging:   level:      io:        seata:  info  mybatis:   mapperLocations:  classpath:mapper/*.xml  
file.conf 
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 transport {   # tcp udt unix-domain-socket   type = "TCP"   #NIO NATIVE   server = "NIO"   #enable heartbeat   heartbeat = true   #thread factory for netty   thread-factory {     boss-thread-prefix = "NettyBoss"     worker-thread-prefix = "NettyServerNIOWorker"     server-executor-thread-prefix = "NettyServerBizHandler"     share-boss-worker = false     client-selector-thread-prefix = "NettyClientSelector"     client-selector-thread-size = 1     client-worker-thread-prefix = "NettyClientWorkerThread"     # netty boss thread size,will not be used for UDT     boss-thread-size = 1     #auto default pin or 8     worker-thread-size = 8   }   shutdown {     # when destroy server, wait seconds     wait = 3   }   serialization = "seata"   compressor = "none" } service {   vgroup_mapping.kylin_tx_group = "default" #修改自定义事务组名称   default.grouplist = "127.0.0.1:8091"   enableDegrade = false   disable = false   max.commit.retry.timeout = "-1"   max.rollback.retry.timeout = "-1"   disableGlobalTransaction = false } client {   async.commit.buffer.limit = 10000   lock {     retry.internal = 10     retry.times = 30   }   report.retry.count = 5   tm.commit.retry.count = 1   tm.rollback.retry.count = 1 } ## transaction log store store {   ## store mode: file、db   mode = "db"   ## file store   file {     dir = "sessionStore"     # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions     max-branch-session-size = 16384     # globe session size , if exceeded throws exceptions     max-global-session-size = 512     # file buffer size , if exceeded allocate new buffer     file-write-buffer-cache-size = 16384     # when recover batch read size     session.reload.read_size = 100     # async, sync     flush-disk-mode = async   }   ## database store   db {     ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.     datasource = "dbcp"     ## mysql/oracle/h2/oceanbase etc.     db-type = "mysql"     driver-class-name = "com.mysql.jdbc.Driver"     url = "jdbc:mysql://127.0.0.1:3306/seata"     user = "root"     password = "kylin"     min-conn = 1     max-conn = 3     global.table = "global_table"     branch.table = "branch_table"     lock-table = "lock_table"     query-limit = 100   } } lock {   ## the lock store mode: local、remote   mode = "remote"   local {     ## store locks in user's database   }   remote {     ## store locks in the seata's server   } } recovery {   #schedule committing retry period in milliseconds   committing-retry-period = 1000   #schedule asyn committing retry period in milliseconds   asyn-committing-retry-period = 1000   #schedule rollbacking retry period in milliseconds   rollbacking-retry-period = 1000   #schedule timeout retry period in milliseconds   timeout-retry-period = 1000 } transaction {   undo.data.validation = true   undo.log.serialization = "jackson"   undo.log.save.days = 7   #schedule delete expired undo_log in milliseconds   undo.log.delete.period = 86400000   undo.log.table = "undo_log" } ## metrics settings metrics {   enabled = false   registry-type = "compact"   # multi exporters use comma divided   exporter-list = "prometheus"   exporter-prometheus-port = 9898 } support {   ## spring   spring {     # auto proxy the DataSource bean     datasource.autoproxy = false   } } 
registry.conf 
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 registry {   # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa   type = "nacos"   nacos {     serverAddr = "localhost:8848"     namespace = ""     cluster = "default"   }   eureka {     serviceUrl = "http://localhost:8761/eureka"     application = "default"     weight = "1"   }   redis {     serverAddr = "localhost:6379"     db = "0"   }   zk {     cluster = "default"     serverAddr = "127.0.0.1:2181"     session.timeout = 6000     connect.timeout = 2000   }   consul {     cluster = "default"     serverAddr = "127.0.0.1:8500"   }   etcd3 {     cluster = "default"     serverAddr = "http://localhost:2379"   }   sofa {     serverAddr = "127.0.0.1:9603"     application = "default"     region = "DEFAULT_ZONE"     datacenter = "DefaultDataCenter"     cluster = "default"     group = "SEATA_GROUP"     addressWaitTime = "3000"   }   file {     name = "file.conf"   } } config {   # file、nacos 、apollo、zk、consul、etcd3   type = "file"   nacos {     serverAddr = "localhost"     namespace = ""   }   consul {     serverAddr = "127.0.0.1:8500"   }   apollo {     app.id = "seata-server"     apollo.meta = "http://192.168.1.204:8801"   }   zk {     serverAddr = "127.0.0.1:2181"     session.timeout = 6000     connect.timeout = 2000   }   etcd3 {     serverAddr = "http://localhost:2379"   }   file {     name = "file.conf"   } } 
domain CommonResult
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Data @AllArgsConstructor @NoArgsConstructor public  class  CommonResult <T >    private  Integer code;     private  String  message;     private  T       data;     public  CommonResult (Integer code, String message)       {        this (code,message,null );     } } 
Order
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Data @AllArgsConstructor @NoArgsConstructor public  class  Order     private  Long id;     private  Long userId;     private  Long productId;     private  Integer count;     private  BigDecimal money;     private  Integer status;  } 
Dao OrderDao
1 2 3 4 5 6 7 8 @Mapper public  interface  OrderDao           void  create (Order order)           void  update (@Param("userId")  Long userId, @Param("status")  Integer status)  } 
resources文件夹下新建mapper文件夹后添加OrderMapper.xml
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 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper  PUBLIC  "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd"  > <mapper  namespace ="com.kylin.dao.OrderDao" >     <resultMap  id ="BaseResultMap"  type ="com.kylin.domain.Order" >          <id  column ="id"  property ="id"  jdbcType ="BIGINT" />          <result  column ="user_id"  property ="userId"  jdbcType ="BIGINT" />          <result  column ="product_id"  property ="productId"  jdbcType ="BIGINT" />          <result  column ="count"  property ="count"  jdbcType ="INTEGER" />          <result  column ="money"  property ="money"  jdbcType ="DECIMAL" />          <result  column ="status"  property ="status"  jdbcType ="INTEGER" />      </resultMap >      <insert  id ="create" >          insert into t_order (id,user_id,product_id,count,money,status)         values (null,#{userId},#{productId},#{count},#{money},0);     </insert >      <update  id ="update" >          update t_order set status = 1         where user_id=#{userId} and status = #{status};     </update >  </mapper > 
Service OrderService
1 2 3 public  interface  OrderService      void  create (Order order)  } 
OrderServiceImpl实现接口
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 @Service @Slf4j public  class  OrderServiceImpl  implements  OrderService      @Resource      private  OrderDao orderDao;     @Resource      private  StorageService storageService;     @Resource      private  AccountService accountService;          @Override      @GlobalTransactional(name = "fsp-create-order", rollbackFor = Exception.class)      public  void  create (Order order)           log.info("----->开始新建订单" );                  orderDao.create(order);                  log.info("----->订单微服务开始调用库存,做扣减Count" );         storageService.decrease(order.getProductId(), order.getCount());         log.info("----->订单微服务开始调用库存,做扣减end" );                  log.info("----->订单微服务开始调用账户,做扣减Money" );         accountService.decrease(order.getUserId(), order.getMoney());         log.info("----->订单微服务开始调用账户,做扣减end" );                  log.info("----->修改订单状态开始" );         orderDao.update(order.getUserId(), 0 );         log.info("----->修改订单状态结束" );         log.info("----->下订单结束了,O(∩_∩)O哈哈~" );     } } 
StorageService
1 2 3 4 5 @FeignClient(value = "seata-storage-service") public  interface  StorageService      @PostMapping(value = "/storage/decrease")      CommonResult decrease (@RequestParam("productId")  Long productId, @RequestParam("count")  Integer count)  ; } 
AccountService
1 2 3 4 5 @FeignClient(value = "seata-account-service") public  interface  AccountService      @PostMapping(value = "/account/decrease")      CommonResult decrease (@RequestParam("userId")  Long userId, @RequestParam("money")  BigDecimal money)  ; } 
Controller 
1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController public  class  OrderController      @Resource      private  OrderService orderService;     @GetMapping("/order/create")      public  CommonResult create (Order order)           orderService.create(order);         return  new  CommonResult(200 , "订单创建成功" );     } }   
Config MyBatisConfig
1 2 3 4 @Configuration @MapperScan({"com.kylin.dao"}) public  class  MyBatisConfig  } 
DataSourceProxyConfig
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 import  com.alibaba.druid.pool.DruidDataSource;import  io.seata.rm.datasource.DataSourceProxy;import  org.apache.ibatis.session.SqlSessionFactory;import  org.mybatis.spring.SqlSessionFactoryBean;import  org.mybatis.spring.transaction.SpringManagedTransactionFactory;import  org.springframework.beans.factory.annotation.Value;import  org.springframework.boot.context.properties.ConfigurationProperties;import  org.springframework.context.annotation.Bean;import  org.springframework.context.annotation.Configuration;import  org.springframework.core.io.support.PathMatchingResourcePatternResolver;import  javax.sql.DataSource;@Configuration public  class  DataSourceProxyConfig      @Value("${mybatis.mapperLocations}")      private  String mapperLocations;     @Bean      @ConfigurationProperties(prefix = "spring.datasource")      public  DataSource druidDataSource ()          return  new  DruidDataSource();     }     @Bean      public  DataSourceProxy dataSourceProxy (DataSource dataSource)           return  new  DataSourceProxy(dataSource);     }     @Bean      public  SqlSessionFactory sqlSessionFactoryBean (DataSourceProxy dataSourceProxy)  throws  Exception          SqlSessionFactoryBean sqlSessionFactoryBean = new  SqlSessionFactoryBean();         sqlSessionFactoryBean.setDataSource(dataSourceProxy);         sqlSessionFactoryBean.setMapperLocations(new  PathMatchingResourcePatternResolver().getResources(mapperLocations));         sqlSessionFactoryBean.setTransactionFactory(new  SpringManagedTransactionFactory());         return  sqlSessionFactoryBean.getObject();     } } 
主启动类 
1 2 3 4 5 6 7 8 9 @EnableDiscoveryClient @EnableFeignClients @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public  class  SeataOrderMainApp2001      public  static  void  main (String[] args)           SpringApplication.run(SeataOrderMainApp2001.class, args);     } } 
新建库存Storage-Module 创建模块seata-order-service2002
pom 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 <dependencies >          <dependency >          <groupId > com.alibaba.cloud</groupId >          <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId >      </dependency >           <dependency >          <groupId > com.alibaba.cloud</groupId >          <artifactId > spring-cloud-starter-alibaba-seata</artifactId >          <exclusions >              <exclusion >                  <artifactId > seata-all</artifactId >                  <groupId > io.seata</groupId >              </exclusion >          </exclusions >      </dependency >      <dependency >          <groupId > io.seata</groupId >          <artifactId > seata-all</artifactId >          <version > 1.0.0</version >      </dependency >           <dependency >          <groupId > org.springframework.cloud</groupId >          <artifactId > spring-cloud-starter-openfeign</artifactId >      </dependency >      <dependency >          <groupId > org.springframework.boot</groupId >          <artifactId > spring-boot-starter-web</artifactId >      </dependency >      <dependency >          <groupId > org.springframework.boot</groupId >          <artifactId > spring-boot-starter-test</artifactId >          <scope > test</scope >      </dependency >      <dependency >          <groupId > org.mybatis.spring.boot</groupId >          <artifactId > mybatis-spring-boot-starter</artifactId >          <version > 2.0.0</version >      </dependency >      <dependency >          <groupId > mysql</groupId >          <artifactId > mysql-connector-java</artifactId >          <version > 5.1.37</version >      </dependency >      <dependency >          <groupId > com.alibaba</groupId >          <artifactId > druid-spring-boot-starter</artifactId >          <version > 1.1.10</version >      </dependency >      <dependency >          <groupId > org.projectlombok</groupId >          <artifactId > lombok</artifactId >          <optional > true</optional >      </dependency >  </dependencies > 
yml 
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 server:   port:  2002  spring:   application:      name:  seata-storage-service    cloud:      alibaba:        seata:          tx-service-group:  kylin_tx_group      nacos:        discovery:          server-addr:  localhost:8848    datasource:      driver-class-name:  com.mysql.jdbc.Driver      url:  jdbc:mysql://localhost:3306/seata_storage      username:  root      password:  kylin  logging:   level:      io:        seata:  info  mybatis:   mapperLocations:  classpath:mapper/*.xml  
file.conf 保持一致
registry.conf 保持一致
domain CommonResult一致
Storage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Data public  class  Storage      private  Long id;          private  Long productId;          private  Integer total;          private  Integer used;          private  Integer residue; } 
Dao StorageDao
1 2 3 4 5 6 @Mapper public  interface  StorageDao                void  decrease (@Param("productId")  Long productId, @Param("count")  Integer count)  } 
resources文件夹下新建mapper文件夹后添加StorageMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?xml version="1.0"  encoding="UTF-8"  ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd"  > <mapper namespace="com.kylin.dao.StorageDao" >     <resultMap id="BaseResultMap"  type="com.kylin.domain.Storage" >         <id column="id"  property="id"  jdbcType="BIGINT" />         <result column="product_id"  property="productId"  jdbcType="BIGINT" />         <result column="total"  property="total"  jdbcType="INTEGER" />         <result column="used"  property="used"  jdbcType="INTEGER" />         <result column="residue"  property="residue"  jdbcType="INTEGER" />     </resultMap>     <update id="decrease" >         UPDATE             t_storage         SET             used = used + #{count},residue = residue - #{count}         WHERE             product_id = #{productId}     </update> </mapper> 
Service StorageServicej接口
1 2 3 4 5 6 public  interface  StorageService           void  decrease (Long productId, Integer count)  }   
实现类StorageServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import  com.kylin.dao.StorageDao;import  com.kylin.service.StorageService;import  org.slf4j.Logger;import  org.slf4j.LoggerFactory;import  org.springframework.stereotype.Service;import  javax.annotation.Resource;@Service public  class  StorageServiceImpl  implements  StorageService      private  static  final  Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);     @Resource      private  StorageDao storageDao;          @Override      public  void  decrease (Long productId, Integer count)           LOGGER.info("------->storage-service中扣减库存开始" );         storageDao.decrease(productId,count);         LOGGER.info("------->storage-service中扣减库存结束" );     } } 
Controller StorageController 
Config 保持一致
主启动类 
1 2 3 4 5 6 7 8 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @EnableDiscoveryClient @EnableFeignClients public  class  SeataStorageServiceApplication2002      public  static  void  main (String[] args)           SpringApplication.run(SeataStorageServiceApplication2002.class, args);     } } 
新建账户Account-Module 创建seata-order-service2003
pom 保持一致
yml 
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 server:   port:  2003  spring:   application:      name:  seata-account-service    cloud:      alibaba:        seata:          tx-service-group:  kylin_tx_group      nacos:        discovery:          server-addr:  localhost:8848    datasource:      driver-class-name:  com.mysql.jdbc.Driver      url:  jdbc:mysql://localhost:3306/seata_account      username:  root      password:  kylin  feign:   hystrix:      enabled:  false  logging:   level:      io:        seata:  info  mybatis:   mapperLocations:  classpath:mapper/*.xml  
file.conf 保持一致
registry.conf 保持一致
domain CommonResult一致
Account
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 @Data @AllArgsConstructor @NoArgsConstructor public class Account {     private Long id;     /**      * 用户id      */     private Long userId;     /**      * 总额度      */     private BigDecimal total;     /**      * 已用额度      */     private BigDecimal used;     /**      * 剩余额度      */     private BigDecimal residue; }   
Dao AccountDao
1 2 3 4 5 6 7 8 @Mapper public  interface  AccountDao           void  decrease (@Param("userId")  Long userId, @Param("money")  BigDecimal money)  } 
resources文件夹下新建mapper文件夹后添加AccountMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper  PUBLIC  "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd"  > <mapper  namespace ="com.kylin.dao.AccountDao" >     <resultMap  id ="BaseResultMap"  type ="com.kylin.domain.Account" >          <id  column ="id"  property ="id"  jdbcType ="BIGINT" />          <result  column ="user_id"  property ="userId"  jdbcType ="BIGINT" />          <result  column ="total"  property ="total"  jdbcType ="DECIMAL" />          <result  column ="used"  property ="used"  jdbcType ="DECIMAL" />          <result  column ="residue"  property ="residue"  jdbcType ="DECIMAL" />      </resultMap >      <update  id ="decrease" >          UPDATE t_account         SET           residue = residue - #{money},used = used + #{money}         WHERE           user_id = #{userId};     </update >  </mapper > 
Service AccountService
1 2 3 4 5 6 7 8 public  interface  AccountService           void  decrease (@RequestParam("userId")  Long userId, @RequestParam("money")  BigDecimal money)  }   
AccountServiceImpl实现类
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 @Service public  class  AccountServiceImpl  implements  AccountService      private  static  final  Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);     @Resource      AccountDao accountDao;          @Override      public  void  decrease (Long userId, BigDecimal money)           LOGGER.info("------->account-service中扣减账户余额开始" );         try  {             TimeUnit.SECONDS.sleep(20 );         } catch  (InterruptedException e) {             e.printStackTrace();         }         accountDao.decrease(userId, money);         LOGGER.info("------->account-service中扣减账户余额结束" );     } } 
Controller AccountController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @RestController public  class  AccountController      @Resource      AccountService accountService;          @RequestMapping("/account/decrease")      public  CommonResult decrease (@RequestParam("userId")  Long userId, @RequestParam("money")  BigDecimal money)          accountService.decrease(userId,money);         return  new  CommonResult(200 ,"扣减账户余额成功!" );     } } 
Config 保持一致
主启动类 SeataAccountMainApp2003
1 2 3 4 5 6 7 8 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @EnableDiscoveryClient @EnableFeignClients public  class  SeataAccountMainApp2003      public  static  void  main (String[] args)           SpringApplication.run(SeataAccountMainApp2003.class, args);     } } 
测试 访问http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
AccountServiceImpl添加超时
重新启动访问http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
当库存和账户余额扣减后,订单状态并没有设置为已经完成,没有从零改为1,而且由于feign的重试机制,账户余额还有可能被多次扣减。
使用@GlobalTransactional
再次测试http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
下单后数据库数据并没有任何改变。记录都添加不进来