SpringCloud整合Seata(AT两阶段--场景: 下单减少库存)

news2024/11/16 2:56:01

文章目录

    • 零:前置操作 --- 搭建Seata服务
    • 一:介绍说明
    • 二:添加undolog表
    • 三:框架整合Seata相关依赖
      • 3.1:引入公共SEATA POM依赖
      • 3.2:业务服务引入SEATA公共组件依赖
      • 3.3:yml文件配置Seata客户端和注册信息
    • 四:框架整合OpenFeign RPC服务调用相关依赖
      • 4.1:OpenFeign RPC POM依赖
      • 4.2:OpenFeign RPC 库存服务接口整合
      • 4.2:订单业务服务整合Opengfeign组件
        • 4.2.1:Opeign组件POM引入
    • 五:启动订单服务和库存服务 ---- (库存服务和订单服务搭建过程一样,只不过库和服务名不一样)
      • 5.1:启动类注解说明 ---- @EnableFeignClients
      • 5.2:启动服务查看输出日志
        • 5.2.1:订单服务打印日志如下:
        • 5.2.2:库存服务打印日志如下:
    • 六:发送订单服务请求,跨服务调用,实现Seata分布式事务
      • 6.1:请求订单服务
        • 6.1.1:OrderController.class
        • 6.1.2:OrderServiceImpl.class
          • 6.1.2.1:查看订单请求对应Seata信息
          • 6.1.2.1.1:订单库里undolog表信息
            • 查看rollback_info字段信息
          • 6.1.2.1.2:seata-config配置库里branch_table,global_table,lock_table表信息
            • branch_table
            • global_table
      • 6.2:订单服务进入到库存服务
        • 6.2.1 StockServiceImpl.class
          • chain-stock.undo_log
          • branch_table
          • lock_table
        • 6.2.2 库存服务抛异常-成功回滚
          • 库存服务回滚日志
          • 订单服务回滚日志

零:前置操作 — 搭建Seata服务

之前博客整合步骤:
https://blog.csdn.net/Abraxs/article/details/128425499?spm=1001.2014.3001.5502

一:介绍说明

两微服务:
一个order下单服务,一个stock库存服务
下单同时调用stock减少库存

二:添加undolog表

在各业务库中添加事务日志表,相当于各服务都有个事务分支管理
在这里插入图片描述

CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `branch_id` bigint NOT NULL,
  `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;

三:框架整合Seata相关依赖

3.1:引入公共SEATA POM依赖

Seata事务相关依赖抽出,作为一公共组件供其他服务引用

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>europa-platform</artifactId>
        <groupId>com.europa</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>commons-global-tx</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
<!--            <version>1.2.0</version>-->
        </dependency>


        <!-- 分布式事务解决方案 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.4.2</version>
        </dependency>

    </dependencies>
</project>

在这里插入图片描述

3.2:业务服务引入SEATA公共组件依赖

在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>modules</artifactId>
        <groupId>com.europa</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>europa-tx</artifactId>

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

    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit-test</artifactId>
        <scope>test</scope>
    </dependency>
<!--    <dependency>-->
<!--        <groupId>org.springframework.boot</groupId>-->
<!--        <artifactId>spring-boot-starter-amqp</artifactId>-->
<!--    </dependency>-->
        <dependency>
            <groupId>com.europa</groupId>
            <artifactId>commons-db</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.europa</groupId>
            <artifactId>commons-support-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.europa</groupId>
            <artifactId>commons-global-tx</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>com.alibaba.cloud</groupId>-->
<!--            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.11</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
            <version>2.7.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.21</version>
        </dependency>
    </dependencies>
</project>

3.3:yml文件配置Seata客户端和注册信息

seata:
  enabled: true
  application-id: ${spring.application.name}
  # 事务组
  tx-service-group: my_test_tx_group
  # 自动数据源代理
  enable-auto-data-source-proxy: true
  # 数据源代理模式(分布式事务方案)
  data-source-proxy-mode: AT
  # 与Nacos配置的vgrouping一直
  service:
    vgroup-mapping:
      my_test_tx_group: default
  #nacos配置
  config:
    type: nacos
    nacos:
      server-addr: 192..101:8848
      group: SEATA_GROUP
      namespace: d4874eb0-1917-45fd-8dc1-34f6f3f5265a
      data-id: seataServer.properties
      username: nacos
      password: nacos
  #nacos注册
  registry:
    type: nacos
    nacos:
      server-addr: 192..101:8848  # seata server 所在的nacoas服务地址
      application: seata-server    # 默认名称:seata-server 没有修改可以不配置
      group: SEATA_GROUP # 默认分组:SEATA_GROUP 没有修改可以不配置
      username: nacos
      password: nacos
      namespace: d4874eb0-1917-45fd-8dc1-34f6f3f5265a

下面是整个YML

server:
  port: 8092
spring:
  application:
    name: order-server  #服务名称
  rabbitmq:
    host: 192.101
    port: 5672
    username: root
    password: 123456
  cloud:
    nacos:
      config:
        server-addr: 101.62:8848
        file-extension: yaml
      discovery:
        server-addr: 101.62:8848  #nacos的服务注册中心地址
#    sentinel:
#      transport:
#        dashboard: 192.168.56.104:8887
  #      config:
#        # 是否开启配置中心 默认true
#        enabled: true
#        server-addr: 101.62:8848  #nacos的服务注册中心地址
#        # 配置文件后缀
#        file-extension: yml
#        # 配置对应的分组
#        group: SEATA_GROUP
#        # Nacos 认证用户
#        username: nacos
#        # Nacos 认证密码
#        password: nacos
#        # 支持多个共享 Data Id 的配置,优先级小于extension-configs,自定义 Data Id 配置 属性是个集合,内部由 Config POJO 组成。Config 有 3 个属性,分别是 dataId, group 以及 refresh
#        shared-configs[0]:
#          data-id: seata-client.yaml # 配置文件名-Data Id
#          group: SEATA_GROUP   # 默认为DEFAULT_GROUP
#          refresh: false   # 是否动态刷新,默认为false
#        shared-configs:
#          - data-id: seata-client.yaml
#            group: SEATA_GROUP
#            refresh: true
  datasource: #链接数据源
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.:3306/chain-order?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8
    username: root
    password: 123456

#分布式事务
seata:
  enabled: true
  application-id: ${spring.application.name}
  # 客户端和服务端在同一个事务组
  tx-service-group: my_test_tx_group
  # 自动数据源代理
  enable-auto-data-source-proxy: true
  # 数据源代理模式(分布式事务方案)
  data-source-proxy-mode: AT
  # 事务群组,配置项值为TC集群名,需要与服务端保持一致
  service:
    vgroup-mapping:
      my_test_tx_group: default
  #整合nacos配置中心
  config:
    type: nacos
    nacos:
      server-addr: 192..101:8848
      group: SEATA_GROUP
      namespace: d4874eb0-1917-45fd-8dc1-34f6f3f5265a
      data-id: seataServer.properties
      #可选
      username: nacos
      #可选
      password: nacos
  #整合nacos注册中心
  registry:
    type: nacos
    nacos:
      server-addr: 192..101:8848  # seata server 所在的nacoas服务地址
      application: seata-server    # 默认名称:seata-server 没有修改可以不配置
      group: SEATA_GROUP # 默认分组:SEATA_GROUP 没有修改可以不配置
      username: nacos
      password: nacos
      namespace: d4874eb0-1917-45fd-8dc1-34f6f3f5265a

四:框架整合OpenFeign RPC服务调用相关依赖

4.1:OpenFeign RPC POM依赖

 openfeign相关配置依赖单独抽出作为公共组件供其他服务引用

在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>commons</artifactId>
        <groupId>com.europa</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>commons-support-api</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
    </dependencies>
</project>

4.2:OpenFeign RPC 库存服务接口整合

场景: 进入订单服务,订单服务会调用库存服务,需要把库存接口地址抽出来到Openfeign公共组件中
在这里插入图片描述

package com.europa.support.provider;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Component
@FeignClient(value = "stock-server", url = "http://localhost:8001/")
public interface IStockProvider {

    @PostMapping("/reduceStock")
    String reduceStock(@RequestParam("productId") Integer productId, @RequestParam("totalAmount") Integer totalAmount);

    @Component
    class IStockProviderFallback implements IStockProvider {


        @Override
        public String reduceStock(Integer productId, Integer totalAmount) {
            return null;
        }
    }

}

4.2:订单业务服务整合Opengfeign组件

4.2.1:Opeign组件POM引入

在这里插入图片描述

<dependency>
    <groupId>com.europa</groupId>
    <artifactId>commons-support-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

五:启动订单服务和库存服务 ---- (库存服务和订单服务搭建过程一样,只不过库和服务名不一样)

5.1:启动类注解说明 ---- @EnableFeignClients

该注解类 : 是为了启动是扫描到OpenFeign的定义的包路径,跨服务接口并没有具体实现类,通过动态代理

package com.europa.tx;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients(basePackages = "com.europa.support.provider")
@MapperScan(basePackages = {"com.europa.tx.mapper"})
public class EurtxApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(EurtxApplication.class);
        app.run(args);
    }

}

5.2:启动服务查看输出日志

可以看到打印信息,资源管理器RM 注入成功,对应的事务组也成功配置打印

5.2.1:订单服务打印日志如下:

2022-12-28 01:48:13.257  INFO 8488 --- [           main] i.s.c.rpc.netty.RmNettyRemotingClient    : RM will register :jdbc:mysql://192..104:3306/chain-order
2022-12-28 01:48:13.260  INFO 8488 --- [           main] i.s.core.rpc.netty.NettyPoolableFactory  : NettyPool create channel to transactionRole:RMROLE,address:192.:8091,msg:< RegisterRMRequest{resourceIds='jdbc:mysql://192..104:3306/chain-order', applicationId='order-server', transactionServiceGroup='my_test_tx_group'} >

2022-12-28 01:49:10.975  INFO 8488 --- [eoutChecker_1_1] i.s.core.rpc.netty.NettyPoolableFactory  : NettyPool create channel to transactionRole:TMROLE,address:192..102:8091,msg:< RegisterTMRequest{applicationId='order-server', transactionServiceGroup='my_test_tx_group'} >
2022-12-28 01:49:10.987  INFO 8488 --- [eoutChecker_1_1] i.s.c.rpc.netty.TmNettyRemotingClient    : register TM success. client version:1.3.0, server version:1.4.2,channel:[id: 0x318597e3, L:/192..1:52260 - R:/192..102:8091]
2022-12-28 01:49:10.987  INFO 8488 --- [eoutChecker_1_1] i.s.core.rpc.netty.NettyPoolableFactory  : register success, cost 5 ms, version:1.4.2,role:TMROLE,channel:[id: 0x318597e3, L:/192.1:52260 - R:/192.102:8091]

在这里插入图片描述

5.2.2:库存服务打印日志如下:

在这里插入图片描述

六:发送订单服务请求,跨服务调用,实现Seata分布式事务

6.1:请求订单服务

http://localhost:8092/syncOrder

在这里插入图片描述

6.1.1:OrderController.class

OrderController.class
package com.europa.tx.controller;

//import org.springframework.amqp.core.Message;
//import org.springframework.amqp.core.MessageProperties;
//import org.springframework.amqp.rabbit.core.RabbitTemplate;
import com.europa.tx.entity.BizOrder;
import com.europa.tx.service.OrderService;
import org.glassfish.jersey.message.MessageProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;


@RestController
public class OrderController {

//    @Autowired
//    RabbitTemplate rabbitTemplate;

    @Autowired
    private OrderService orderService;

    @RequestMapping("/syncOrder")
    public String syncOrder(){
//        System.out.println("seata全局事务id====================>"+ RootContext.getXID());
        BizOrder order = new BizOrder();
        order.setProductId(1);
//        order.setTotalAmount(new BigDecimal("10"));
        order.setStatus("0");
        order.setTotalAmount(100);
        try {
            boolean result = orderService.saveLock(order);
        } catch (Exception e) {
            e.printStackTrace();
        }
//        if (result==false) {
//            return "下单失败";
//        }
        return "下单成功";
    }

    @RequestMapping("/delayedSend")
    public String delayedSend(){

//        Random random = new Random();
//        int i = random.nextInt(10);
//        String millTimes = String.valueOf(i * 1000);
//
//        MessageProperties messageProperties = new MessageProperties();
//
//        String msg = " 我是 plugins - delay";
//        messageProperties.setHeader("x-delay",millTimes);//延迟5秒被删除
//        Message message = new Message(msg.getBytes(), messageProperties);
//        rabbitTemplate.convertAndSend("delayed-exchange","delay",message);
//        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//        System.out.println("消息发送成功【" + sdf.format(new Date()) + "】");
        return "";

    }
}

6.1.2:OrderServiceImpl.class

package com.europa.tx.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.europa.support.provider.IStockProvider;
import com.europa.tx.entity.BizOrder;
import com.europa.tx.mapper.OrderMapper;
import com.europa.tx.service.OrderService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


import javax.annotation.Resource;
import java.util.UUID;


@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, BizOrder> implements OrderService {

    @Resource
    private OrderMapper orderMapper;

    @Autowired
    private IStockProvider iStockProvider;

    @Override
    @GlobalTransactional(name = "my_test_tx_group", rollbackFor = Exception.class)
    // @Transactional(rollbackFor = Exception.class)
    public boolean saveLock(BizOrder order) throws Exception {
        System.out.println("seata全局事务id====================>"+ RootContext.getXID());


        // 模拟有条订单发生改变
        BizOrder existOrder = orderMapper.selectById("048b2d2836b641aa93b16d410f682db0");
        if (ObjectUtils.isNotEmpty(existOrder)) {
            existOrder.setTotalAmount(50);
            baseMapper.updateById(existOrder);
        }
        order.setId(UUID.randomUUID().toString().replaceAll("-", "").toString());
        order.setProductId(1);
        //  order.setTotalAmount(new BigDecimal("90.00"));
        order.setStatus("看到改变了,说明失效了哦");
        //  下单
        int a = orderMapper.insert(order);
        String aa = iStockProvider.reduceStock(order.getProductId(), order.getProductId());



        // throw new Exception();
        //  扣减库存
        // int f = 1/0;
//        if(a == 1) {
            return true;
//        }
    }

}
6.1.2.1:查看订单请求对应Seata信息
6.1.2.1.1:订单库里undolog表信息

注意:订单服务实现类两条对数据库的操作,修改和新增,下面看undo_log信息
baseMapper.updateById(existOrder);
int a = orderMapper.insert(order);
在这里插入图片描述

查看rollback_info字段信息
SELECT CONVERT(t.rollback_info USING utf8mb4),t.* FROM undo_log t

可以看到生成了两条branch_id分支,分别对应数据库操作的回滚信息,有一个update更新类型和一个insert,分别对应一个beforeImage和afterImage镜像信息,包含之前之后的sql变化和值,方便后面抛异常RC事务协调器通知RM资源管理器进行回滚操作。

在这里插入图片描述

{"@class":"io.seata.rm.datasource.undo.BranchUndoLog","xid":"192..102:8091:18358011811149491","branchId":18358011811149494,"sqlUndoLogs":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.undo.SQLUndoLog","sqlType":"UPDATE","tableName":"biz_order","beforeImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords","tableName":"biz_order","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"id","keyType":"PRIMARY_KEY","type":12,"value":"048b2d2836b641aa93b16d410f682db0"},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"product_id","keyType":"NULL","type":-5,"value":["java.lang.Long",1]},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"total_amount","keyType":"NULL","type":4,"value":100},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"status","keyType":"NULL","type":12,"value":"看到改变了,说明失效了哦"}]]}]]},"afterImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords","tableName":"biz_order","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"id","keyType":"PRIMARY_KEY","type":12,"value":"048b2d2836b641aa93b16d410f682db0"},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"product_id","keyType":"NULL","type":-5,"value":["java.lang.Long",1]},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"total_amount","keyType":"NULL","type":4,"value":50},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"status","keyType":"NULL","type":12,"value":"看到改变了,说明失效了哦"}]]}]]}}]]}

BEFOREIMAGE
在这里插入图片描述
AFTERIMAGE
在这里插入图片描述

6.1.2.1.2:seata-config配置库里branch_table,global_table,lock_table表信息
branch_table

在这里插入图片描述

global_table

在这里插入图片描述###### lock_table
在这里插入图片描述

6.2:订单服务进入到库存服务

6.2.1 StockServiceImpl.class

断点打在库存服务异常行,在执行sql语句之后,上面订单服务断点位置在sql语句之前,对undolog和seatta-config做比较

package com.europa.tx.stock.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.europa.tx.stock.entity.Stock;
import com.europa.tx.stock.mapper.StockMapper;
import com.europa.tx.stock.service.StockService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Random;

@Service
public class StockServiceImpl extends ServiceImpl<StockMapper, Stock> implements StockService {

    @GlobalTransactional(name = "my_test_tx_group", rollbackFor = Exception.class)
    @Override
    public void reduceStock(Integer productId, Integer totalAmount) throws Exception {
        System.out.println("seata全局事务id====================>"+ RootContext.getXID());
        LambdaQueryWrapper<Stock> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(Stock::getProductId,productId);
        Stock result = this.getOne(queryWrapper);
        result.setCount(result.getCount() - 1);
        this.updateById(result);
        throw new Exception("");
    }
}

在这里插入图片描述

chain-stock.undo_log

在这里插入图片描述

branch_table

在这里插入图片描述

lock_table

在这里插入图片描述

6.2.2 库存服务抛异常-成功回滚

库存服务回滚日志

在这里插入图片描述

订单服务回滚日志

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/129371.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[C++]模板与STL简介

&#x1f941;作者&#xff1a; 华丞臧 &#x1f4d5;​​​​专栏&#xff1a;【C】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449;LeetCode 文章目录模板初阶…

AspNetCore中的日志组件

介绍 本文写作年代比较久远&#xff0c;最新日志文档请查看&#xff1a; .NET Core 和 ASP.NET Core 中的日志记录 | Microsoft Learn了解如何使用由 Microsoft Extension.Logging NuGet 包提供的日志记录框架。https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/l…

选择-C++选择分支

引言 你们喜欢吃"只因"肉? a > 喜欢 b > 还行 c > 不喜欢 请说出你的答案: 证明 真ikun 和假ikun 关键点 我现在 a 因为我是ikun 我是 唯一玩梗的CSDN技术型博主 哈哈所以夹带点私货 你们dddd(懂的都懂) 关注我,让你看到更多的C/C 的技术点和技术以外的梗…

CTF之MISC题目-简单流量

CTF系列文章 第一篇 CTF之密码学题目-classical && coding 第二篇 CTF之MISC题目-西游记 第三篇 CTF之MISC题目-简单流量 文章目录CTF系列文章前言一、题目是什么&#xff1f;二、解题步骤1.下载文件、解压2.使用wireshark3.解压flag.zip总结前言 这是一道关于网络数…

ElasticSearch-倒排索引

文章目录一、mysql数据库存在的问题1.1 模糊查询索引失效1.2 不能分词查询二、倒排索引一、mysql数据库存在的问题 1.1 模糊查询索引失效 假设要查询上图中title中包含"手机"的信息&#xff0c;那么sql语句是这样的 SELECT * FROM goods WHERE title LIKE %手机%;如…

功能更新 | 身份认证增强安全配置

在开始本文前&#xff0c;先给大家出个解谜题&#xff0c;密码在下一段文字里&#xff0c;由 9 个字组成&#xff0c;开动你的脑筋吧&#xff0c;我们在本文结尾会揭晓答案&#xff1a; 2022 年马上就要结束了&#xff0c;机遇与挑战并存的一年。昨天&#xff0c;北京郊区一些地…

企业电子招标采购系统源码及功能清单

​ ​ 一、立项管理 1、招标立项申请 功能点&#xff1a;招标类项目立项申请入口&#xff0c;用户可以保存为草稿&#xff0c;提交。 2、非招标立项申请 功能点&#xff1a;非招标立项申请入口、用户可以保存为草稿、提交。 3、采购立项列表 功能点&#xff1a;对草稿进行编…

从卖货到广告,跨境电商解锁变现新模式

一、经济全球化背景下&#xff0c;跨境电商作为外贸发展的新模式&#xff0c;可谓势头正盛。而 2022 年&#xff0c;在汇率波动、欧美通胀等不可抗逆因素的影响下&#xff0c;跨境电商大环境也面临着诸多挑战。对于消费者而言&#xff0c;全球通货膨胀持续走高&#xff0c;物价…

List、List<Object>、List<?>

List、List、List<?>ListList<Object>List<?>demoList 1、声明的List集合对其 所指向的集合对象&#xff08;就是赋值的集合对象&#xff09;的限制:无泛型限制&#xff0c;并且无视指向的集合对象的泛型&#xff0c;直接当成List处理&#xff08;泛型擦除…

软件测试难吗?应该怎样学习?

软件测试是一份不错的职业&#xff0c;现在也有许多小伙伴想要学习软件测试技术&#xff0c;成为一名软件测试员。但是零经验的小白又担心不知道软件测试好不好学&#xff0c;应该如何学习软件测试能力&#xff0c;需要做哪些培训呢。下面就给大家推荐一些学习经验与技巧&#…

【JavaEE】JVM(八股文!)

努力经营当下&#xff0c;直至未来明朗&#xff01; 文章目录【JVM内存划分】【JVM类加载】【JVM垃圾回收机制GC】一&#xff09;GC是啥二&#xff09;GC回收哪部分内存三&#xff09;具体怎么回收四&#xff09;怎么找垃圾&#xff08;判定某个对象是否是垃圾&#xff09;五&a…

圆和椭圆外投影

1 圆外一点在缩放到圆上 圆方程: x2y222x^2y^2 2^2 x2y222 直线方程: ykxy kx ykx 圆外一点: A(3,3)A(3,3)A(3,3) 求点B. 方法1-解方程 圆外一点A(3,3)A(3,3)A(3,3),那么:直线k1k1k1,直线方程:yxyxyx 方程联立: x2x24x^2 x^2 4 \\ x2x24 x2y2x \sqrt{2} \\ y \sqrt…

点云 3D 分割 - SqueezeSeg(ICRA 2018)

点云 3D 分割 - SqueezeSeg&#xff08;ICRA 2018&#xff09;摘要1. 引言2. 相关工作A. 三维激光雷达点云的语义分割B. 用于3D点云的CNNC. 图像的语义分割D. 通过模拟收集数据3. 方法描述A. 点云变换B. 网络结构C. 条件随机场D. 数据收集4. 实验A. 评估指标B. 实验设置C. 实验…

2022电商行业重磅年度报告:八大年度关键词盘点

2022年终于过去&#xff0c;当网易云音乐推出“年度报告”霸屏朋友圈&#xff0c;它在试图唤起那些可能被遗忘的情绪和小心思。 这一年&#xff0c;有人悲观&#xff0c;有人积极&#xff0c;有人凭实力搭上了顺风车&#xff0c;也有人放弃了抵抗。这一年&#xff0c;作为电商人…

Mybatis学习笔记 | 动力节点老杜

目录 一、MyBatis概述 历史 MyBatis特点 ORM概述 二、Mybatis入门 1、SqlSessionFactory和SqlSession 2、核心配置文件的加载 3、mybatis事务管理机制 4、第一个mybatis程序 5、mybatis继承日志 三、mybatis增删改查 1、增加 通过Map传值 通过实体类传值 2、删除…

【uniapp】如何实现拖动文件直接上传

做uniapp项目发布H5有个后台管理&#xff0c;用户说上传文件的体验需要改进&#xff0c;那个弹出选择文件对话框然后去填文件路径选择文件上传&#xff0c;感觉操作太麻烦&#xff0c;于是就有了这么一个需求&#xff0c;需要实现拖动文件直接上传的&#xff0c;这样效率和体验…

九、动态组件与插槽

一、动态组件 1.1、什么是动态组件 动态组件指的是动态切换组件的显示与隐藏。 1.2、如何实现动态组件渲染 vue提供了一个内置的<component>组件&#xff0c;专门用来实现动态组件的渲染。示例代码如下&#xff1a; data() {// 1. 当前要渲染的组件名称return {comN…

学习笔记:混沌工程

个人理解&#xff1a; 混沌工程&#xff0c;chaos engineering&#xff0c;找出系统中的脆弱环节的方法学 混沌工程是软件测试和质量保证的一种方法&#xff0c;在黑客入侵之前或系统故障之前使用它来识别漏洞&#xff0c;由于混沌工程测试而做出的改变增加了人们对系统的信心。…

SpringBoot @SessionScope注解和Session的用法解释

参考资料 JSESSIONID是什么SessionScope 解决了不同session下如何生成不同服务实例 目录一. 前期准备二. 被SessionScope作用的类三. 使用被SessionScope作用类的Service四. 效果4.1 用Edge浏览器进入页面4.2 然后用Edge浏览器进入页面4.3 若将CacheHolder类上的SessionScope注…

nginx部署next项目访问日志去重小技巧,next项目资源不计入日志,网站日志统计去除资源请求

next项目访问日志去重小技巧需求提出具体解决方案配置代码需求提出 之前在跟SEO做网站日志分析的时候受到了一部分资源请求数据的影响&#xff0c;统计出来的ip访问次数远远大于实际值&#xff0c;从日志中或者网站控制台看到每个页面都会发送十几个请求&#xff0c;而这些请求…