【Seata】_03 使用

准备

准备父子模块
在这里插入图片描述
分别是订单服务和库存服务,要实现一个功能,增加一个订单成功后删除对应产品的库存,基于以下两个接口实现
订单接口:

@RestController
@RequestMapping("orders")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OrdersController {

    private final OrdersService service;

    @PostMapping("add")
    public Object add() {
        return service.add();
    }
}

// 这是实际业务方法
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders>
        implements OrdersService {

    private final RestTemplate restTemplate;

    @Override
    public Object add() {
        Orders orders = Orders.builder().productId(1L).total(1).status(1).build();
        restTemplate.getForObject("http://localhost:9001/stock/reduct?productId=" + orders.getProductId(), String.class);
        return orders.getId();
    }
}

可以看到就是增加订单时会调用库存微服务的/stock/reduct接口
库存接口:

@RestController
@RequestMapping("stock")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class StockController {

    private final StockService service;

    @GetMapping("reduct")
    public Object reduct(Long productId) {
        return service.reduct(productId);
    }
}

// 这是实际业务方法
@Service
public class StockServiceImpl extends ServiceImpl<StockMapper, Stock>
        implements StockService {

    @Override
    public Boolean reduct(Long productId) {
        return this.baseMapper.reduct(productId);
    }
}

本地事务效果

请不要在意此两个接口是否符合规范,目前的效果是调用订单接口对应的库存会减1,这时我测试一下本地事务效果。
修改一下订单接口:

@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders>
        implements OrdersService {

    private final RestTemplate restTemplate;

    @Override
    @Transactional
    public Object add() {
        Orders orders = Orders.builder().productId(1L).total(1).status(1).build();
        restTemplate.getForObject("http://localhost:9001/stock/reduct?productId=" + orders.getProductId(), String.class);
        int i = 1 / 0;
        return orders.getId();
    }
}

加上本地事务并且让程序出错,手动清空本地订单表,设置产品对应库存是100,如果本地事务成功的话,订单表会增加一条数据,并且库存会减1变成99
结果是:订单表没有生成数据,但是对应产品的库存减1了,说明本地事务只对单一服务生效

Seata分布式事务

将上面两个服务进行改造

依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>oliver</groupId>
            <artifactId>database</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
    </dependencies>

配置

spring:
  application:
    name: order-seata-service
  cloud:
    nacos:
      server-addr: localhost:8848
      # 如果开启了权限,账号密码必须要显式写清
      username: nacos
      password: nacos
spring:
  application:
    name: stock-seata-service
  cloud:
    nacos:
      server-addr: localhost:8848
      # 如果开启了权限,账号密码必须要显式写清
      username: nacos
      password: nacos

集成OpenFeign

@FeignClient(value = "stock-seata-service", path = "/stock")
public interface StockService {

    @GetMapping("reduct")
    Object reduct(@RequestParam("productId") Long productId);
}

Order服务的新建订单方法改为:

@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders>
        implements OrdersService {
    private final StockService stockService;
    @Override
    public Object add() {
        Orders orders = Orders.builder().productId(1L).total(1).status(1).build();
        this.baseMapper.insert(orders);
        stockService.reduct(orders.getProductId());
        //int i = 1 / 0;
        return orders.getId();
    }
}

记得启动类开启OpenFeign

@EnableFeignClients

遇到的问题

弄完之后我调用订单服务的add接口报错:不支持接口类型:【POST】,原因是Feign使用对象作为参数默认会将GET请求转成POST请求,导致调用库存时请求类型不同,在Feign接口填参数时加上@RequestParam,被调用方的参数前也要加上@RequestParam

增加un_log表

在要使用到Seata的每个数据库中都增加一个表

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;

此表用于记录undo信息,用于回滚

seata相关配置

seata:
  enabled: true
  enable-auto-data-source-proxy: true
  # 配置事务组
  tx-service-group: my_test_tx_group
  registry:
    # 用于告诉Client如何访问Seata Server
    type: nacos
    nacos:
      server-addr: localhost:8848
      # seata服务端的服务名
      application: seata-server
      username: nacos
      password: nacos
      # seata服务端的分组名称
      group: SEATA_GROUP
      namespace: seata-server
  # 配置Seata服务端的配置中心,用于读取关于Seata Client的一些配置
  config:
    # 用于告诉Client如何访问Seata Server
    type: nacos
    nacos:
      server-addr: localhost:8848
      username: nacos
      password: nacos
      # seata服务端的分组名称
      group: SEATA_GROUP
      namespace: seata-server
  service:
    vgroup-mapping:
      my_test_tx_group: default
    disable-global-transaction: false
  client:
    rm:
      report-success-enable: true

这里也要配置事务分组,就是服务端配置中的config.txt里的第一条配置

测试

在增加订单接口逻辑上增加@GlobalTransactional并且测试

@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders>
        implements OrdersService {
    private final StockService stockService;

    @Override
    @GlobalTransactional
    public Object add() {
        Orders orders = Orders.builder().productId(1L).total(1).status(1).build();
        this.baseMapper.insert(orders);
        stockService.reduct(orders.getProductId());
        return orders.getId();
    }
}

发现分布式事务已经生效

原理

  1. 事务进入后会生成全局事务ID(XID),保存在global_table中
  2. 每一个服务执行一个写操作都会注册成为一个分支事务,保存在branch_table中,此时对应事务对自己服务表的操作已经写入了,并且写入了每个服务的undo_log表中,元数据存在rollback_info中,可以转成UTF8来查询出来看
  3. 异常之后,会根据undo_log表中记录的数据逆向生成回滚SQL进行回滚

你可能感兴趣的:(Spring,Cloud,Alibaba,java,spring,jvm)