【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进行回滚
在您提供的引用内容中,没有直接回答到您问题的信息。关于您的问题,"Error opening log file 'D:\quanjiatong\Seata140_yl\seata-server-1.4.0\seata\bin\\../logs/seata_gc.log': No such file or directory"这个错误可能是由于找不到seata_gc.log文件或目录不存在导致的。请确保您的目录结构和文件路径是正确的,并且seata_gc.log文件已经被正确地创建。如果文件或目录确实不存在,您可以尝试手动创建该文件或目录,并确保程序具有相应的访问权限。如果问题仍然存在,您可以尝试参考相关文档或搜索引擎寻找更多解决办法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [seata-server-1.4.0.zip](https://download.csdn.net/download/dingsai88/14293339)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [seata异常:io.seata.common.exception.FrameworkException: No available service](https://blog.csdn.net/wdquan19851029/article/details/116751027)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值