1.前提

已经完成了seata服务端启动的环境部署,并已经启动seata服务端
参考:https://www.yuque.com/u27809381/ca4o9w/avy27g

2.案例

如下,假设当前存在一个存在一个订单确认的功能
image.png
该功能的对外暴露接口存在于订单服务,接口的功能包括3步

  1. 订单状态改为已确认
  2. 物品扣减库存
  3. 账户扣除花费

接下来以上述案例来进行seataAT模式的具体使用

3.创建物品服务

建库建表

  1. -- 创建物品数据库
  2. CREATE DATABASE `item`;
  3. -- 使用物品数据库
  4. USE `item`;
  5. -- 创建物品表
  6. DROP TABLE IF EXISTS `t_item`;
  7. CREATE TABLE `t_item` (
  8. `id` BIGINT NOT NULL AUTO_INCREMENT,
  9. `count` INT DEFAULT NULL COMMENT '库存',
  10. PRIMARY KEY (`id`)
  11. ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
  12. -- 创建回滚日志表
  13. DROP TABLE IF EXISTS `undo_log`;
  14. CREATE TABLE `undo_log`
  15. (
  16. `branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
  17. `xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
  18. `context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
  19. `rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
  20. `log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
  21. `log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
  22. `log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
  23. UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
  24. ) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
  25. -- 插入测试数据
  26. INSERT INTO t_item (id,COUNT)
  27. VALUES
  28. (1,5),
  29. (2,15),
  30. (3,20),
  31. (4,25);

项目引入依赖

  1. <!--web服务依赖-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <!--Nacos 服务发现依赖-->
  7. <dependency>
  8. <groupId>com.alibaba.cloud</groupId>
  9. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  10. </dependency>
  11. <!--引入 seata 依赖-->
  12. <dependency>
  13. <groupId>com.alibaba.cloud</groupId>
  14. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  15. </dependency>
  16. <!--第三方工具类库-->
  17. <dependency>
  18. <groupId>cn.hutool</groupId>
  19. <artifactId>hutool-all</artifactId>
  20. <version>5.7.18</version>
  21. </dependency>
  22. <!--代码生成器依赖-->
  23. <dependency>
  24. <groupId>com.baomidou</groupId>
  25. <artifactId>mybatis-plus-generator</artifactId>
  26. <version>3.5.0</version>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.apache.velocity</groupId>
  30. <artifactId>velocity-engine-core</artifactId>
  31. <version>2.3</version>
  32. </dependency>
  33. <!-- ...................多数据源连接数据库相关依赖 start................... -->
  34. <!--动态数据源-->
  35. <dependency>
  36. <groupId>com.baomidou</groupId>
  37. <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  38. <version>3.5.0</version>
  39. </dependency>
  40. <!--Mybatis连接依赖-->
  41. <dependency>
  42. <groupId>mysql</groupId>
  43. <artifactId>mysql-connector-java</artifactId>
  44. <version>8.0.23</version>
  45. </dependency>
  46. <!--mybatis-plus依赖-->
  47. <dependency>
  48. <groupId>com.baomidou</groupId>
  49. <artifactId>mybatis-plus-boot-starter</artifactId>
  50. <version>3.5.0</version>
  51. </dependency>
  52. <!--druid连接池-->
  53. <dependency>
  54. <groupId>com.alibaba</groupId>
  55. <artifactId>druid-spring-boot-starter</artifactId>
  56. <version>1.2.8</version>
  57. </dependency>
  58. <!--log4j-->
  59. <dependency>
  60. <groupId>log4j</groupId>
  61. <artifactId>log4j</artifactId>
  62. <version>1.2.17</version>
  63. </dependency>
  64. <!-- ...................多数据源连接数据库相关依赖 end................... -->
  65. <!--Lombok依赖-->
  66. <dependency>
  67. <groupId>org.projectlombok</groupId>
  68. <artifactId>lombok</artifactId>
  69. <optional>true</optional>
  70. </dependency>
  71. <!--swagger依赖-->
  72. <dependency>
  73. <groupId>com.spring4all</groupId>
  74. <artifactId>swagger-spring-boot-starter</artifactId>
  75. <version>1.8.0.RELEASE</version>
  76. </dependency>
  77. <!--单元测试-->
  78. <dependency>
  79. <groupId>org.springframework.boot</groupId>
  80. <artifactId>spring-boot-starter-test</artifactId>
  81. <scope>test</scope>
  82. </dependency>

编写bootstrap.yml文件

  1. #端点配置
  2. management:
  3. endpoints:
  4. web:
  5. exposure:
  6. include: '*'
  7. #spring配置
  8. spring:
  9. #服务名称
  10. application:
  11. name: item
  12. cloud:
  13. #注册中心配置
  14. nacos:
  15. discovery:
  16. server-addr: 8.142.132.135:8848 #nacos地址
  17. #事务分组配置
  18. alibaba:
  19. seata:
  20. tx-service-group: service-item-group

编写application.yml文件

  1. #服务器配置
  2. server:
  3. port: 8082
  4. spring:
  5. #配置数据库数据源
  6. datasource:
  7. dynamic:
  8. type: com.alibaba.druid.pool.DruidDataSource
  9. primary: item
  10. #配置连接池
  11. druid:
  12. initialSize: 10
  13. minIdle: 1
  14. maxActive: 20
  15. maxWait: 60000
  16. timeBetweenEvictionRunsMillis: 60000
  17. minEvictableIdleTimeMillis: 300000
  18. validationQuery: select 'x' FROM DUAL
  19. testWhileIdle: true
  20. testOnBorrow: false
  21. testOnReturn: false
  22. poolPreparedStatements: false
  23. maxOpenPreparedStatements: -1
  24. filters: stat,log4j,wall
  25. connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
  26. useGlobalDataSourceStat: true
  27. #配置数据源
  28. datasource:
  29. item:
  30. url: jdbc:mysql://10.128.41.42:3306/item?characterEncoding=UTF-8&autoReconnect=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull&useUnicode=true&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=true&rewriteBatchedStatements=true
  31. username: root
  32. password: sMviwqONsqW&Ag$5
  33. type: com.alibaba.druid.pool.DruidDataSource
  34. driver-class-name: com.mysql.cj.jdbc.Driver
  35. #seata配置
  36. seata:
  37. #配置中心配置
  38. config:
  39. type: nacos
  40. nacos:
  41. server-addr: 8.142.132.135:8848
  42. group: "SEATA_GROUP"
  43. namespace: ""
  44. username: "nacos"
  45. password: "nacos"
  46. #注册中心配置
  47. registry:
  48. type: nacos
  49. nacos:
  50. application: seata-server
  51. server-addr: 8.142.132.135:8848
  52. group: "SEATA_GROUP"
  53. namespace: ""
  54. username: "nacos"
  55. password: "nacos"

启动类添加注解

  1. import org.springframework.boot.SpringApplication;
  2. import org.springframework.boot.autoconfigure.SpringBootApplication;
  3. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  4. @SpringBootApplication
  5. @EnableDiscoveryClient
  6. public class ItemApplication {
  7. public static void main(String[] args) {
  8. SpringApplication.run(ItemApplication.class, args);
  9. }
  10. }

编写扣减库存接口

mapper

  1. import com.example.item.entity.TItemPo;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import org.apache.ibatis.annotations.Mapper;
  4. /**
  5. * <p>
  6. * Mapper 接口
  7. * </p>
  8. *
  9. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  10. * @since 2022-06-27
  11. */
  12. @Mapper
  13. public interface TItemMapper extends BaseMapper<TItemPo> {
  14. }

service

  1. import cn.hutool.json.JSONObject;
  2. import com.baomidou.mybatisplus.extension.service.IService;
  3. import com.example.item.entity.TItemPo;
  4. /**
  5. * <p>
  6. * 服务类
  7. * </p>
  8. *
  9. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  10. * @since 2022-06-27
  11. */
  12. public interface TItemService extends IService<TItemPo> {
  13. /**
  14. * 扣减库存
  15. *
  16. * @param id 主键ID
  17. * @return 扣减后对象
  18. */
  19. JSONObject delCount(Integer id);
  20. }

service.impl

  1. import cn.hutool.core.util.ObjectUtil;
  2. import cn.hutool.json.JSONObject;
  3. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  4. import com.example.item.entity.TItemPo;
  5. import com.example.item.mapper.TItemMapper;
  6. import com.example.item.service.TItemService;
  7. import org.springframework.stereotype.Service;
  8. import org.springframework.transaction.annotation.Transactional;
  9. /**
  10. * <p>
  11. * 服务实现类
  12. * </p>
  13. *
  14. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  15. * @since 2022-06-27
  16. */
  17. @Service
  18. @Transactional(rollbackFor = Exception.class)
  19. public class TItemServiceImpl extends ServiceImpl<TItemMapper, TItemPo> implements TItemService {
  20. @Override
  21. public JSONObject delCount(Integer id) {
  22. //1.id判定
  23. if (id <= 0) {
  24. throw new RuntimeException("id异常");
  25. }
  26. //2.查询库存
  27. TItemPo itemPo = getById(id);
  28. if (ObjectUtil.isNull(itemPo)) {
  29. throw new RuntimeException("未找到资源");
  30. }
  31. //3.修改库存,默认-10,测试使用
  32. itemPo.setCount(itemPo.getCount() - 10);
  33. if (itemPo.getCount() < 0) {
  34. throw new RuntimeException("库存数不足");
  35. }
  36. //4.数据库更新
  37. boolean isSuccess = updateById(itemPo);
  38. if (!isSuccess) {
  39. throw new RuntimeException("数据库更新失败");
  40. }
  41. //5.封装返回结果
  42. JSONObject result = new JSONObject(true);
  43. result.set("status", true);
  44. result.set("data", itemPo);
  45. //6.返回
  46. return result;
  47. }
  48. }

entity

  1. import com.baomidou.mybatisplus.annotation.TableName;
  2. import com.baomidou.mybatisplus.annotation.IdType;
  3. import com.baomidou.mybatisplus.extension.activerecord.Model;
  4. import com.baomidou.mybatisplus.annotation.TableId;
  5. import java.io.Serializable;
  6. import io.swagger.annotations.ApiModel;
  7. import io.swagger.annotations.ApiModelProperty;
  8. import lombok.Data;
  9. import lombok.EqualsAndHashCode;
  10. /**
  11. * <p>
  12. *
  13. * </p>
  14. *
  15. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  16. * @since 2022-06-27
  17. */
  18. @Data
  19. @EqualsAndHashCode(callSuper = false)
  20. @TableName("t_item")
  21. @ApiModel(value="TItemPo对象", description="")
  22. public class TItemPo extends Model<TItemPo> {
  23. private static final long serialVersionUID = 1L;
  24. @TableId(value = "id", type = IdType.AUTO)
  25. private Long id;
  26. @ApiModelProperty(value = "库存")
  27. private Integer count;
  28. @Override
  29. public Serializable pkVal() {
  30. return this.id;
  31. }
  32. }

controller

  1. import cn.hutool.json.JSONObject;
  2. import com.example.item.service.TItemService;
  3. import lombok.RequiredArgsConstructor;
  4. import org.springframework.web.bind.annotation.PathVariable;
  5. import org.springframework.web.bind.annotation.PutMapping;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. /**
  9. * <p>
  10. * 前端控制器
  11. * </p>
  12. *
  13. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  14. * @since 2022-06-27
  15. */
  16. @RestController
  17. @RequestMapping("/api/v1/items")
  18. @RequiredArgsConstructor
  19. public class TItemController {
  20. private final TItemService itemService;
  21. @PutMapping("{id}")
  22. public JSONObject delCount(@PathVariable Integer id) {
  23. return itemService.delCount(id);
  24. }
  25. }

4. 创建账户服务

建库建表

  1. -- 创建账户数据库
  2. CREATE DATABASE `account`;
  3. -- 使用账户数据库
  4. USE `account`;
  5. -- 创建账户表
  6. DROP TABLE IF EXISTS `t_account`;
  7. CREATE TABLE `t_account` (
  8. `id` BIGINT NOT NULL AUTO_INCREMENT,
  9. `count` INT DEFAULT NULL COMMENT '余额',
  10. PRIMARY KEY (`id`)
  11. ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
  12. -- 创建回滚日志表
  13. DROP TABLE IF EXISTS `undo_log`;
  14. CREATE TABLE `undo_log`
  15. (
  16. `branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
  17. `xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
  18. `context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
  19. `rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
  20. `log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
  21. `log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
  22. `log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
  23. UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
  24. ) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
  25. -- 插入测试数据
  26. INSERT INTO t_account (id,COUNT)
  27. VALUES
  28. (1,100),
  29. (2,150),
  30. (3,200),
  31. (4,250);

项目引入依赖

  1. <!--web服务依赖-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <!--Nacos 服务发现依赖-->
  7. <dependency>
  8. <groupId>com.alibaba.cloud</groupId>
  9. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  10. </dependency>
  11. <!--引入 seata 依赖-->
  12. <dependency>
  13. <groupId>com.alibaba.cloud</groupId>
  14. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  15. </dependency>
  16. <!--第三方工具类库-->
  17. <dependency>
  18. <groupId>cn.hutool</groupId>
  19. <artifactId>hutool-all</artifactId>
  20. <version>5.7.18</version>
  21. </dependency>
  22. <!--代码生成器依赖-->
  23. <dependency>
  24. <groupId>com.baomidou</groupId>
  25. <artifactId>mybatis-plus-generator</artifactId>
  26. <version>3.5.0</version>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.apache.velocity</groupId>
  30. <artifactId>velocity-engine-core</artifactId>
  31. <version>2.3</version>
  32. </dependency>
  33. <!-- ...................多数据源连接数据库相关依赖 start................... -->
  34. <!--动态数据源-->
  35. <dependency>
  36. <groupId>com.baomidou</groupId>
  37. <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  38. <version>3.5.0</version>
  39. </dependency>
  40. <!--Mybatis连接依赖-->
  41. <dependency>
  42. <groupId>mysql</groupId>
  43. <artifactId>mysql-connector-java</artifactId>
  44. <version>8.0.23</version>
  45. </dependency>
  46. <!--mybatis-plus依赖-->
  47. <dependency>
  48. <groupId>com.baomidou</groupId>
  49. <artifactId>mybatis-plus-boot-starter</artifactId>
  50. <version>3.5.0</version>
  51. </dependency>
  52. <!--druid连接池-->
  53. <dependency>
  54. <groupId>com.alibaba</groupId>
  55. <artifactId>druid-spring-boot-starter</artifactId>
  56. <version>1.2.8</version>
  57. </dependency>
  58. <!--log4j-->
  59. <dependency>
  60. <groupId>log4j</groupId>
  61. <artifactId>log4j</artifactId>
  62. <version>1.2.17</version>
  63. </dependency>
  64. <!-- ...................多数据源连接数据库相关依赖 end................... -->
  65. <!--Lombok依赖-->
  66. <dependency>
  67. <groupId>org.projectlombok</groupId>
  68. <artifactId>lombok</artifactId>
  69. <optional>true</optional>
  70. </dependency>
  71. <!--swagger依赖-->
  72. <dependency>
  73. <groupId>com.spring4all</groupId>
  74. <artifactId>swagger-spring-boot-starter</artifactId>
  75. <version>1.8.0.RELEASE</version>
  76. </dependency>
  77. <!--单元测试-->
  78. <dependency>
  79. <groupId>org.springframework.boot</groupId>
  80. <artifactId>spring-boot-starter-test</artifactId>
  81. <scope>test</scope>
  82. </dependency>

编写bootstrap.yml文件

  1. #端点配置
  2. management:
  3. endpoints:
  4. web:
  5. exposure:
  6. include: '*'
  7. #spring配置
  8. spring:
  9. #服务名称
  10. application:
  11. name: account
  12. cloud:
  13. #注册中心配置
  14. nacos:
  15. discovery:
  16. server-addr: 8.142.132.135:8848 #nacos地址
  17. #事务分组配置
  18. alibaba:
  19. seata:
  20. tx-service-group: service-account-group

编写application.yml文件

  1. #服务器配置
  2. server:
  3. port: 8083
  4. spring:
  5. #配置数据库数据源
  6. datasource:
  7. dynamic:
  8. type: com.alibaba.druid.pool.DruidDataSource
  9. primary: account
  10. #配置连接池
  11. druid:
  12. initialSize: 10
  13. minIdle: 1
  14. maxActive: 20
  15. maxWait: 60000
  16. timeBetweenEvictionRunsMillis: 60000
  17. minEvictableIdleTimeMillis: 300000
  18. validationQuery: select 'x' FROM DUAL
  19. testWhileIdle: true
  20. testOnBorrow: false
  21. testOnReturn: false
  22. poolPreparedStatements: false
  23. maxOpenPreparedStatements: -1
  24. filters: stat,log4j,wall
  25. connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
  26. useGlobalDataSourceStat: true
  27. #配置数据源
  28. datasource:
  29. account:
  30. url: jdbc:mysql://10.128.41.42:3306/account?characterEncoding=UTF-8&autoReconnect=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull&useUnicode=true&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=true&rewriteBatchedStatements=true
  31. username: root
  32. password: sMviwqONsqW&Ag$5
  33. type: com.alibaba.druid.pool.DruidDataSource
  34. driver-class-name: com.mysql.cj.jdbc.Driver
  35. #seata配置
  36. seata:
  37. #配置中心配置
  38. config:
  39. type: nacos
  40. nacos:
  41. server-addr: 8.142.132.135:8848
  42. group: "SEATA_GROUP"
  43. namespace: ""
  44. username: "nacos"
  45. password: "nacos"
  46. #注册中心配置
  47. registry:
  48. type: nacos
  49. nacos:
  50. application: seata-server
  51. server-addr: 8.142.132.135:8848
  52. group: "SEATA_GROUP"
  53. namespace: ""
  54. username: "nacos"
  55. password: "nacos"

启动类添加注解

  1. import org.springframework.boot.SpringApplication;
  2. import org.springframework.boot.autoconfigure.SpringBootApplication;
  3. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  4. @SpringBootApplication
  5. @EnableDiscoveryClient
  6. public class AccountApplication {
  7. public static void main(String[] args) {
  8. SpringApplication.run(AccountApplication.class, args);
  9. }
  10. }

编写扣减账户余额接口

mapper

  1. import com.example.account.entity.TAccountPo;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import org.apache.ibatis.annotations.Mapper;
  4. /**
  5. * <p>
  6. * Mapper 接口
  7. * </p>
  8. *
  9. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  10. * @since 2022-06-27
  11. */
  12. @Mapper
  13. public interface TAccountMapper extends BaseMapper<TAccountPo> {
  14. }

service

  1. import cn.hutool.json.JSONObject;
  2. import com.baomidou.mybatisplus.extension.service.IService;
  3. import com.example.account.entity.TAccountPo;
  4. /**
  5. * <p>
  6. * 服务类
  7. * </p>
  8. *
  9. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  10. * @since 2022-06-27
  11. */
  12. public interface TAccountService extends IService<TAccountPo> {
  13. /**
  14. * 扣减余额
  15. *
  16. * @param id 主键ID
  17. * @return 扣减后对象
  18. */
  19. JSONObject delCount(Integer id);
  20. }

service.impl

  1. import cn.hutool.core.util.ObjectUtil;
  2. import cn.hutool.json.JSONObject;
  3. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  4. import com.example.account.entity.TAccountPo;
  5. import com.example.account.mapper.TAccountMapper;
  6. import com.example.account.service.TAccountService;
  7. import org.springframework.stereotype.Service;
  8. import org.springframework.transaction.annotation.Transactional;
  9. /**
  10. * <p>
  11. * 服务实现类
  12. * </p>
  13. *
  14. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  15. * @since 2022-06-27
  16. */
  17. @Service
  18. @Transactional(rollbackFor = Exception.class)
  19. public class TAccountServiceImpl extends ServiceImpl<TAccountMapper, TAccountPo> implements TAccountService {
  20. @Override
  21. public JSONObject delCount(Integer id) {
  22. //1.id判定
  23. if (id <= 0) {
  24. throw new RuntimeException("id异常");
  25. }
  26. //2.查询库存
  27. TAccountPo accountPo = getById(id);
  28. if (ObjectUtil.isNull(accountPo)) {
  29. throw new RuntimeException("未找到资源");
  30. }
  31. //3.修改余额,默认-100,测试使用
  32. accountPo.setCount(accountPo.getCount() - 100);
  33. if (accountPo.getCount() < 0) {
  34. throw new RuntimeException("余额不足");
  35. }
  36. //4.数据库更新
  37. boolean isSuccess = updateById(accountPo);
  38. if (!isSuccess) {
  39. throw new RuntimeException("数据库更新失败");
  40. }
  41. //5.封装返回结果
  42. JSONObject result = new JSONObject(true);
  43. result.set("status", true);
  44. result.set("data", accountPo);
  45. //6.返回
  46. return result;
  47. }
  48. }

entity

  1. import com.baomidou.mybatisplus.annotation.IdType;
  2. import com.baomidou.mybatisplus.annotation.TableId;
  3. import com.baomidou.mybatisplus.annotation.TableName;
  4. import com.baomidou.mybatisplus.extension.activerecord.Model;
  5. import io.swagger.annotations.ApiModel;
  6. import io.swagger.annotations.ApiModelProperty;
  7. import lombok.Data;
  8. import lombok.EqualsAndHashCode;
  9. import java.io.Serializable;
  10. /**
  11. * <p>
  12. *
  13. * </p>
  14. *
  15. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  16. * @since 2022-06-27
  17. */
  18. @Data
  19. @EqualsAndHashCode(callSuper = false)
  20. @TableName("t_account")
  21. @ApiModel(value = "TAccountPo对象", description = "")
  22. public class TAccountPo extends Model<TAccountPo> {
  23. private static final long serialVersionUID = 1L;
  24. @TableId(value = "id", type = IdType.AUTO)
  25. private Long id;
  26. @ApiModelProperty(value = "余额")
  27. private Integer count;
  28. @Override
  29. public Serializable pkVal() {
  30. return this.id;
  31. }
  32. }

controller

  1. import cn.hutool.json.JSONObject;
  2. import com.example.account.service.TAccountService;
  3. import lombok.RequiredArgsConstructor;
  4. import org.springframework.web.bind.annotation.PathVariable;
  5. import org.springframework.web.bind.annotation.PutMapping;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. /**
  9. * <p>
  10. * 前端控制器
  11. * </p>
  12. *
  13. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  14. * @since 2022-06-27
  15. */
  16. @RestController
  17. @RequestMapping("/api/v1/accounts")
  18. @RequiredArgsConstructor
  19. public class TAccountController {
  20. private final TAccountService accountService;
  21. @PutMapping("{id}")
  22. public JSONObject delCount(@PathVariable Integer id) {
  23. return accountService.delCount(id);
  24. }
  25. }

5.创建订单服务

建库建表

  1. -- 创建订单数据库
  2. CREATE DATABASE `order`;
  3. -- 使用订单数据库
  4. USE `order`;
  5. -- 创建订单表
  6. DROP TABLE IF EXISTS `t_order`;
  7. CREATE TABLE `t_order` (
  8. `id` BIGINT NOT NULL AUTO_INCREMENT,
  9. `status` INT DEFAULT NULL COMMENT '订单状态:0:未完成;1:已完结',
  10. PRIMARY KEY (`id`)
  11. ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
  12. -- 创建回滚日志表
  13. DROP TABLE IF EXISTS `undo_log`;
  14. CREATE TABLE `undo_log`
  15. (
  16. `branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
  17. `xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
  18. `context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
  19. `rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
  20. `log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
  21. `log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
  22. `log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
  23. UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
  24. ) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
  25. -- 插入测试数据
  26. INSERT INTO t_order (id,STATUS)
  27. VALUES
  28. (1,0),
  29. (2,0),
  30. (3,0),
  31. (4,0);

项目引入依赖

  1. <!--web服务依赖-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <!--Nacos 服务发现依赖-->
  7. <dependency>
  8. <groupId>com.alibaba.cloud</groupId>
  9. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  10. </dependency>
  11. <!--引入 seata 依赖-->
  12. <dependency>
  13. <groupId>com.alibaba.cloud</groupId>
  14. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  15. </dependency>
  16. <!-- openfeign依赖 -->
  17. <dependency>
  18. <groupId>org.springframework.cloud</groupId>
  19. <artifactId>spring-cloud-starter-openfeign</artifactId>
  20. </dependency>
  21. <!-- openfeign优化请求连接池依赖 -->
  22. <dependency>
  23. <groupId>io.github.openfeign</groupId>
  24. <artifactId>feign-httpclient</artifactId>
  25. </dependency>
  26. <!--第三方工具类库-->
  27. <dependency>
  28. <groupId>cn.hutool</groupId>
  29. <artifactId>hutool-all</artifactId>
  30. <version>5.7.18</version>
  31. </dependency>
  32. <!-- ...................多数据源连接数据库相关依赖 start................... -->
  33. <!--动态数据源-->
  34. <dependency>
  35. <groupId>com.baomidou</groupId>
  36. <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  37. <version>3.5.0</version>
  38. </dependency>
  39. <!--Mybatis连接依赖-->
  40. <dependency>
  41. <groupId>mysql</groupId>
  42. <artifactId>mysql-connector-java</artifactId>
  43. <version>8.0.23</version>
  44. </dependency>
  45. <!--mybatis-plus依赖-->
  46. <dependency>
  47. <groupId>com.baomidou</groupId>
  48. <artifactId>mybatis-plus-boot-starter</artifactId>
  49. <version>3.5.0</version>
  50. </dependency>
  51. <!--druid连接池-->
  52. <dependency>
  53. <groupId>com.alibaba</groupId>
  54. <artifactId>druid-spring-boot-starter</artifactId>
  55. <version>1.2.8</version>
  56. </dependency>
  57. <!--log4j-->
  58. <dependency>
  59. <groupId>log4j</groupId>
  60. <artifactId>log4j</artifactId>
  61. <version>1.2.17</version>
  62. </dependency>
  63. <!-- ...................多数据源连接数据库相关依赖 end................... -->
  64. <!--Lombok依赖-->
  65. <dependency>
  66. <groupId>org.projectlombok</groupId>
  67. <artifactId>lombok</artifactId>
  68. <optional>true</optional>
  69. </dependency>
  70. <!--swagger依赖-->
  71. <dependency>
  72. <groupId>com.spring4all</groupId>
  73. <artifactId>swagger-spring-boot-starter</artifactId>
  74. <version>1.8.0.RELEASE</version>
  75. </dependency>
  76. <!--单元测试-->
  77. <dependency>
  78. <groupId>org.springframework.boot</groupId>
  79. <artifactId>spring-boot-starter-test</artifactId>
  80. <scope>test</scope>
  81. </dependency>

编写bootstramp.yml文件

  1. #端点配置
  2. management:
  3. endpoints:
  4. web:
  5. exposure:
  6. include: '*'
  7. #spring配置
  8. spring:
  9. #服务名称
  10. application:
  11. name: order
  12. cloud:
  13. #注册中心配置
  14. nacos:
  15. discovery:
  16. server-addr: 8.142.132.135:8848 #nacos地址
  17. #事务分组配置
  18. alibaba:
  19. seata:
  20. tx-service-group: service-order-group

编写application.yml文件

  1. #服务器配置
  2. server:
  3. port: 8081
  4. spring:
  5. #配置数据库数据源
  6. datasource:
  7. dynamic:
  8. type: com.alibaba.druid.pool.DruidDataSource
  9. primary: order
  10. #配置连接池
  11. druid:
  12. initialSize: 10
  13. minIdle: 1
  14. maxActive: 20
  15. maxWait: 60000
  16. timeBetweenEvictionRunsMillis: 60000
  17. minEvictableIdleTimeMillis: 300000
  18. validationQuery: select 'x' FROM DUAL
  19. testWhileIdle: true
  20. testOnBorrow: false
  21. testOnReturn: false
  22. poolPreparedStatements: false
  23. maxOpenPreparedStatements: -1
  24. filters: stat,log4j,wall
  25. connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
  26. useGlobalDataSourceStat: true
  27. #配置数据源
  28. datasource:
  29. order:
  30. url: jdbc:mysql://10.128.41.42:3306/order?characterEncoding=UTF-8&autoReconnect=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull&useUnicode=true&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=true&rewriteBatchedStatements=true
  31. username: root
  32. password: sMviwqONsqW&Ag$5
  33. type: com.alibaba.druid.pool.DruidDataSource
  34. driver-class-name: com.mysql.cj.jdbc.Driver
  35. #seata配置
  36. seata:
  37. #配置中心配置
  38. config:
  39. type: nacos
  40. nacos:
  41. server-addr: 8.142.132.135:8848
  42. group: "SEATA_GROUP"
  43. namespace: ""
  44. username: "nacos"
  45. password: "nacos"
  46. #注册中心配置
  47. registry:
  48. type: nacos
  49. nacos:
  50. application: seata-server
  51. server-addr: 8.142.132.135:8848
  52. group: "SEATA_GROUP"
  53. namespace: ""
  54. username: "nacos"
  55. password: "nacos"
  56. #ribbon配置
  57. ribbon:
  58. ReadTimeout: 6000
  59. ConnectionTimeout: 6000
  60. #feign配置
  61. feign:
  62. client:
  63. httpclient:
  64. enabled: true
  65. compression:
  66. request:
  67. enabled: true
  68. mime-types: text/xml,application/xml, application/json
  69. min-request-size: 1024
  70. response:
  71. enabled: true

启动类添加注解

  1. import org.springframework.boot.SpringApplication;
  2. import org.springframework.boot.autoconfigure.SpringBootApplication;
  3. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
  4. import org.springframework.cloud.openfeign.EnableFeignClients;
  5. @SpringBootApplication
  6. @EnableDiscoveryClient
  7. @EnableFeignClients
  8. public class OrderApplication {
  9. public static void main(String[] args) {
  10. SpringApplication.run(OrderApplication.class, args);
  11. }
  12. }

编写订单确认接口

mapper

  1. import com.example.order.entity.TOrderPo;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import org.apache.ibatis.annotations.Mapper;
  4. /**
  5. * <p>
  6. * Mapper 接口
  7. * </p>
  8. *
  9. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  10. * @since 2022-06-27
  11. */
  12. @Mapper
  13. public interface TOrderMapper extends BaseMapper<TOrderPo> {
  14. }

service

  1. import cn.hutool.json.JSONObject;
  2. import com.baomidou.mybatisplus.extension.service.IService;
  3. import com.example.order.entity.TOrderPo;
  4. /**
  5. * <p>
  6. * 服务类
  7. * </p>
  8. *
  9. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  10. * @since 2022-06-27
  11. */
  12. public interface TOrderService extends IService<TOrderPo> {
  13. /**
  14. * 修改订单状态
  15. *
  16. * @param id 订单ID
  17. * @return 修改后的订单
  18. */
  19. JSONObject updateOrder(Integer id);
  20. }

service.impl

  1. import cn.hutool.core.util.ObjectUtil;
  2. import cn.hutool.json.JSONObject;
  3. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  4. import com.example.order.entity.TOrderPo;
  5. import com.example.order.mapper.TOrderMapper;
  6. import com.example.order.service.TOrderService;
  7. import org.springframework.stereotype.Service;
  8. import org.springframework.transaction.annotation.Transactional;
  9. /**
  10. * <p>
  11. * 服务实现类
  12. * </p>
  13. *
  14. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  15. * @since 2022-06-27
  16. */
  17. @Service
  18. @Transactional(rollbackFor = Exception.class)
  19. public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrderPo> implements TOrderService {
  20. @Override
  21. public JSONObject updateOrder(Integer id) {
  22. //1.id判定
  23. if (id <= 0) {
  24. throw new RuntimeException("id异常");
  25. }
  26. //2.查询账户
  27. TOrderPo orderPo = getById(id);
  28. if (ObjectUtil.isNull(orderPo)) {
  29. throw new RuntimeException("未找到资源");
  30. }
  31. //3.修改订单状态
  32. orderPo.setStatus(1);
  33. //4.数据库更新
  34. boolean isSuccess = updateById(orderPo);
  35. if (!isSuccess) {
  36. throw new RuntimeException("数据库更新失败");
  37. }
  38. //5.封装返回结果
  39. JSONObject result = new JSONObject(true);
  40. result.set("status", true);
  41. result.set("data", orderPo);
  42. //6.返回
  43. return result;
  44. }
  45. }

entity

  1. import com.baomidou.mybatisplus.annotation.TableName;
  2. import com.baomidou.mybatisplus.annotation.IdType;
  3. import com.baomidou.mybatisplus.extension.activerecord.Model;
  4. import com.baomidou.mybatisplus.annotation.TableId;
  5. import java.io.Serializable;
  6. import io.swagger.annotations.ApiModel;
  7. import io.swagger.annotations.ApiModelProperty;
  8. import lombok.Data;
  9. import lombok.EqualsAndHashCode;
  10. /**
  11. * <p>
  12. *
  13. * </p>
  14. *
  15. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  16. * @since 2022-06-27
  17. */
  18. @Data
  19. @EqualsAndHashCode(callSuper = false)
  20. @TableName("t_order")
  21. @ApiModel(value="TOrderPo对象", description="")
  22. public class TOrderPo extends Model<TOrderPo> {
  23. private static final long serialVersionUID = 1L;
  24. @TableId(value = "id", type = IdType.AUTO)
  25. private Long id;
  26. @ApiModelProperty(value = "订单状态:0:未完成;1:已完结")
  27. private Integer status;
  28. @Override
  29. public Serializable pkVal() {
  30. return this.id;
  31. }
  32. }

ItemFeign

  1. import cn.hutool.json.JSONObject;
  2. import org.springframework.cloud.openfeign.FeignClient;
  3. import org.springframework.stereotype.Component;
  4. import org.springframework.web.bind.annotation.PathVariable;
  5. import org.springframework.web.bind.annotation.PutMapping;
  6. /**
  7. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  8. * @date 2022-06-27 18:24:50
  9. * @describe:
  10. */
  11. @Component
  12. @FeignClient(value = "item")
  13. public interface ItemFeign {
  14. @PutMapping("/api/v1/items/{id}")
  15. JSONObject delCount(@PathVariable Integer id);
  16. }

AccountFeign

  1. import cn.hutool.json.JSONObject;
  2. import org.springframework.cloud.openfeign.FeignClient;
  3. import org.springframework.stereotype.Component;
  4. import org.springframework.web.bind.annotation.PathVariable;
  5. import org.springframework.web.bind.annotation.PutMapping;
  6. /**
  7. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  8. * @date 2022-06-27 18:24:50
  9. * @describe:
  10. */
  11. @Component
  12. @FeignClient(value = "account")
  13. public interface AccountFeign {
  14. @PutMapping("/api/v1/accounts/{id}")
  15. JSONObject delCount(@PathVariable Integer id);
  16. }

controller

  1. import cn.hutool.json.JSONObject;
  2. import cn.hutool.log.StaticLog;
  3. import com.example.order.feign.AccountFeign;
  4. import com.example.order.feign.ItemFeign;
  5. import com.example.order.service.TOrderService;
  6. import lombok.RequiredArgsConstructor;
  7. import org.springframework.web.bind.annotation.PathVariable;
  8. import org.springframework.web.bind.annotation.PutMapping;
  9. import org.springframework.web.bind.annotation.RequestMapping;
  10. import org.springframework.web.bind.annotation.RestController;
  11. /**
  12. * <p>
  13. * 前端控制器
  14. * </p>
  15. *
  16. * @author 冯铁城 [fengtiecheng@cyou-inc.com]
  17. * @since 2022-06-27
  18. */
  19. @RestController
  20. @RequestMapping("/api/v1/orders")
  21. @RequiredArgsConstructor
  22. public class TOrderController {
  23. private final TOrderService orderService;
  24. private final ItemFeign itemFeign;
  25. private final AccountFeign accountFeign;
  26. @PutMapping("{id}")
  27. public JSONObject updateOrder(@PathVariable Integer id) {
  28. //1.修改订单状态
  29. JSONObject result = orderService.updateOrder(id);
  30. //2.调用物品服务扣减库存
  31. JSONObject itemResult = itemFeign.delCount(id);
  32. StaticLog.info("物品服务扣减库存响应:[{}]", itemResult);
  33. //3.调用账户服务扣减余额
  34. JSONObject accountResult = accountFeign.delCount(id);
  35. StaticLog.info("账户服务扣减余额响应:[{}]", accountResult);
  36. //4.返回
  37. return result;
  38. }
  39. }

6.未引入seata触发修改订单状态接口

因为item服务每次会扣除10库存,而id=1的库存=5,当库存不足时,item服务会抛出异常,因此调用item服务的order服务也会异常
如图:响应异常
image.png
异常原因出现在item服务
image.png
但是此时查看order数据库,id为1的订单状态,发现状态已经变更了,此时就出现了数据不一致问题
image.png

7.@GlobalTransactional注解引入

注解说明

seata提供了@GlobalTransactional标签来控制全局事务的开启、管理以及控制
@GlobalTransactional注解最常用的属性

  1. name:声明全局事务的名称
  2. rollbackFor:出现什么异常时全局回滚

@GlobalTransactional注解的作用域

  1. 在方法上使用时,全局事务的范围就是该方法以及它所涉及的所有服务。
  2. 在类上使用时,全局事务的范围就是这个类中的所有方法以及这些方法涉及的服务。

    order服务改造

    ```java import cn.hutool.json.JSONObject; import cn.hutool.log.StaticLog; import com.example.order.feign.AccountFeign; import com.example.order.feign.ItemFeign; import com.example.order.service.TOrderService; import io.seata.spring.annotation.GlobalTransactional; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;

/**

  • 前端控制器
  • *
  • @author 冯铁城 [fengtiecheng@cyou-inc.com]
  • @since 2022-06-27 */ @RestController @RequestMapping(“/api/v1/orders”) @RequiredArgsConstructor public class TOrderController {

    private final TOrderService orderService;

    private final ItemFeign itemFeign;

    private final AccountFeign accountFeign;

    @PutMapping(“{id}”) @GlobalTransactional(name = “order-update-order”, rollbackFor = Exception.class) public JSONObject updateOrder(@PathVariable Integer id) {

    1. //1.修改订单状态
    2. JSONObject result = orderService.updateOrder(id);
    3. //2.调用物品服务扣减库存
    4. JSONObject itemResult = itemFeign.delCount(id);
    5. StaticLog.info("物品服务扣减库存响应:[{}]", itemResult);
    6. //3.调用账户服务扣减余额
    7. JSONObject accountResult = accountFeign.delCount(id);
    8. StaticLog.info("账户服务扣减余额响应:[{}]", accountResult);
    9. //4.返回
    10. return result;

    } } ```

    改造后具体逻辑步骤

  1. order服务作为TM向TC注册一个全局事务,TC返回一个XID
  2. order服务一阶段修改订单状态,之后提交并保存前后镜像回滚日志
  3. TC发送一阶段执行命令给item服务(RM)
  4. item服务一阶段执行过程中出现异常,事务回滚。返回结果给TC
  5. TC将item服务的返回结果给TM
  6. TM决定全局回滚
  7. TC发送全局回滚命令
  8. order服务接收到全局回滚命令,根据前后镜像回滚日志进行订单数据回滚

    验证

    如图:订单id=1的数据状态并没有改变
    image.png
    同时查看订单服务控制台,出现订单回滚字样
    image.png
    全局事务成功!!!