Spring Boot集成Seata利用AT模式分布式事务示例 | Spring Cloud 53

news2025/1/12 1:37:16

一、前言

通过以下系列章节:

docker-compose 实现Seata Server高可用部署 | Spring Cloud 51

Seata AT 模式理论学习、事务隔离及部分源码解析 | Spring Cloud 52

我们对Seata及其AT事务模式有了基础的了解,今天我们通过搭建Spring Boot集成Seata示例,加深对AT事务模式的掌握,避免在后续实战业务中出现脏写及脏读。

二、前置条件

2.1 完成Seata Server安装

具体安装步骤,请见:docker-compose 实现Seata Server高可用部署 | Spring Cloud 51

2.2 创建表 undo_log

Seata AT事务模式需要在每一个微服务对应的数据库中创建表:undo_log(回滚记录表)

  • TMRMClient端)可通过client.undo.logTable属性,配置自定义undo表名,默认undo_log

Mysql建表语句:

-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';

其他数据库建表语句请见官网:https://github.com/seata/seata/tree/1.6.1/script/client/at/db

2.3 依赖说明

<!-- 注意一定要引入对版本,要引入spring-cloud版本seata,而不是springboot版本的seata-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <!-- 排除掉springcloud默认的seata版本,以免版本不一致出现问题-->
    <exclusions>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 上面排除掉了springcloud默认色seata版本,此处引入和seata-server版本对应的seata包-->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.6.1</version>
</dependency>

通过添加spring-cloud-starter-alibaba-seata依赖,来实现利用openfeign传播xid

三、使用示例

3.1 项目总体结构

在这里插入图片描述

目录结构解析:

  • consumer-at:为TM (Transaction Manager) - 事务管理器 角色
    定义全局事务的范围:开始全局事务、提交或回滚全局事务。

  • provider-at:为RM (Resource Manager) - 资源管理器 角色
    管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

3.2 provider-at 搭建

3.2.1 完整依赖

seata/nacos-http-at/provider-at/pom.xml

<?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>nacos-http-at</artifactId>
        <groupId>com.gm</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>provider-at</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</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-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>


        <!-- 注意一定要引入对版本,要引入spring-cloud版本seata,而不是springboot版本的seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <!-- 排除掉springcloud默认的seata版本,以免版本不一致出现问题-->
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-all</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 上面排除掉了springcloud默认色seata版本,此处引入和seata-server版本对应的seata包-->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.6.1</version>
        </dependency>

        <!--<dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </dependency>-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>io.fabric8</groupId>
                <artifactId>docker-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <excludes>
                    <exclude>**/*.xlsx</exclude>
                    <exclude>**/*.xls</exclude>
                </excludes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**/*.xlsx</include>
                    <include>**/*.xls</include>
                </includes>
            </resource>
        </resources>
    </build>


</project>

3.2.2 配置文件

src/main/resources/bootstrap.yml

server:
  port: 4000

spring:
  application:
    name: @artifactId@
  cloud:
    nacos:
      username: @nacos.username@
      password: @nacos.password@
      discovery:
        server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848}
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.0.46:3306/seata-demo-provider?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    username: root
    password: '1qaz@WSX'
seata:
  # 是否开启spring-boot自动装配,seata-spring-boot-starter 专有配置,默认true
  enabled: true
  # 是否开启数据源自动代理,seata-spring-boot-starter专有配置,默认会开启数据源自动代理,可通过该配置项关闭
  enable-auto-data-source-proxy: true
  # 配置自定义事务组名称,需与下方server.vgroupMapping配置一致,程序会通过用户配置的配置中心去寻找service.vgroupMapping
  tx-service-group: mygroup
  config: # 从nacos配置中心获取client端配置
    type: nacos
    nacos:
      server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848}
      group : DEFAULT_GROUP
      namespace: a4c150aa-fd09-4595-9afe-c87084b22105
      dataId: seataServer.properties
      username: @nacos.username@
      password: @nacos.username@
  registry: # 通过服务中心通过服务发现获取seata-server服务地址
    type: nacos
    nacos:
      # 注:客户端注册中心配置的serverAddr和namespace与Server端一致,clusterName与Server端cluster一致
      application: seata-server # 此处与seata-server的application一致,才能通过服务发现获取服务地址
      group : DEFAULT_GROUP
      server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848}
      userName: @nacos.username@
      password: @nacos.username@
      namespace: a4c150aa-fd09-4595-9afe-c87084b22105
  service:
    # 应用程序(客户端)会通过用户配置的配置中心去寻找service.vgroupMapping.[事务分组配置项]
    vgroup-mapping:
      # 事务分组配置项[mygroup]对应的值为TC集群名[default],与Seata-Server中的seata.registry.nacos.cluster配置一致
      mygroup : default
    # 全局事务开关,默认false。false为开启,true为关闭
    disable-global-transaction: false
  client:
    rm:
      report-success-enable: true
management:
  endpoints:
    web:
      exposure:
        include: '*'

logging:
  level:
    io.seata: debug

# mybatis-plus配置控制台打印完整带参数SQL语句
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

注意事项:

  • 要求客户端配置(seata.tx-service-group: [事务分组配置项]service.vgroupMapping.[事务分组配置项]: [TC集群名])与Seata-Server配置(service.vgroupMapping.[事务分组配置项]: [TC集群名] )在[事务分组配置项][TC集群名]配置一致,且上述[TC集群名]seata.registry中的cluster配置一致
  • seata.config.typeseata.registry.type均为nacos
  • configregistrynacos的配置,其中namespacegroup须与Seata-Server的配置一致

seata更多配置说明,请见官网:http://seata.io/zh-cn/docs/user/configurations.html

3.2.3 建表语句

DROP TABLE IF EXISTS `t_b`;
CREATE TABLE `t_b`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `create_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `create_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1662719643897974787 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

在这里插入图片描述

3.2.4 功能搭建

3.2.4.1 启动类

com/gm/seata/nacos_http_provider/NacosHttpProviderATApplication.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class NacosHttpProviderATApplication {

    public static void main(String[] args) {
        SpringApplication.run(NacosHttpProviderATApplication.class, args);
    }

}

3.2.4.2 实体类

com/gm/seata/nacos_http_provider/entity/B.java

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@TableName("t_b")
public class B {

    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    private String createBy;

    private LocalDateTime createTime;
}

3.2.4.3 Mapper类

com/gm/seata/nacos_http_provider/mapper/BMapper.java

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gm.seata.nacos_http_provider.entity.B;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface BMapper extends BaseMapper<B> {
}

3.2.4.4 Service类

com/gm/seata/nacos_http_provider/service/BService.java

public interface BService {
    void save(String createBy);
}

com/gm/seata/nacos_http_provider/service/impl/BServiceImpl.java

import com.gm.seata.nacos_http_provider.entity.B;
import com.gm.seata.nacos_http_provider.mapper.BMapper;
import com.gm.seata.nacos_http_provider.service.BService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;

@Service
public class BServiceImpl implements BService {

    @Autowired
    BMapper bMapper;

    @Override
    public void save(String createBy) {
        B b = new B();
        b.setCreateBy(createBy);
        b.setCreateTime(LocalDateTime.now());
        bMapper.insert(b);
        if (Math.random() < 0.5) {
            throw new RuntimeException("模拟调用第三方接口异常");
        }
    }
}

通过随机模拟RM (Resource Manager) - 资源管理器 角色调用失败

3.2.4.5 Controller类

com/gm/seata/nacos_http_provider/controller/ProviderController.java

import com.gm.seata.nacos_http_provider.service.BService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProviderController {

    @Autowired
    BService bService;

    @RequestMapping(value = "save", method = RequestMethod.GET)
    public void save(@RequestParam("createBy") String createBy) {
        bService.save(createBy);
    }
}

3.3 consumer-at 搭建

3.3.1 完整依赖

seata/nacos-http-at/consumer-at/pom.xml

<?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>nacos-http-at</artifactId>
        <groupId>com.gm</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>consumer-at</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</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-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>

        <!-- 注意一定要引入对版本,要引入spring-cloud版本seata,而不是springboot版本的seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <!-- 排除掉springcloud默认的seata版本,以免版本不一致出现问题-->
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-all</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 上面排除掉了springcloud默认色seata版本,此处引入和seata-server版本对应的seata包-->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!--<dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </dependency>-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>io.fabric8</groupId>
                <artifactId>docker-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <excludes>
                    <exclude>**/*.xlsx</exclude>
                    <exclude>**/*.xls</exclude>
                </excludes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**/*.xlsx</include>
                    <include>**/*.xls</include>
                </includes>
            </resource>
        </resources>
    </build>


</project>

3.3.2 配置文件

src/main/resources/bootstrap.yml

server:
  port: 3000

spring:
  application:
    name: @artifactId@
  cloud:
    nacos:
      username: @nacos.username@
      password: @nacos.password@
      discovery:
        server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848},${NACOS_HOST:nacos2kc}:${NACOS_PORT:8848},${NACOS_HOST:nacos3.kc}:${NACOS_PORT:8848}
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.0.35:3306/seata-demo-consumer?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    username: root
    password: '1qaz@WSX'
seata:
  enabled: true
  enable-auto-data-source-proxy: true
  tx-service-group: mygroup
  config:
    type: nacos
    nacos:
      server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848}
      group : DEFAULT_GROUP
      namespace: a4c150aa-fd09-4595-9afe-c87084b22105
      dataId: seataServer.properties
      username: @nacos.username@
      password: @nacos.username@
  registry:
    type: nacos
    nacos:
      application: seata-server
      group : DEFAULT_GROUP
      server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848}
      userName: @nacos.username@
      password: @nacos.username@
      namespace: a4c150aa-fd09-4595-9afe-c87084b22105
  service:
    vgroup-mapping:
      mygroup : default
    disable-global-transaction: false
  client:
    rm:
      # 是否上报一阶段成功,默认false,true用于保持分支事务生命周期记录完整,false可提高不少性能
      report-success-enable: true

management:
  endpoints:
    web:
      exposure:
        include: '*'

# mybatis-plus配置控制台打印完整带参数SQL语句
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.3.3 建表语句

DROP TABLE IF EXISTS `t_a`;
CREATE TABLE `t_a`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `create_by` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `create_time` datetime NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1659824277015949315 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

在这里插入图片描述

3.3.4 功能搭建

3.3.4.1 启动类

com/gm/seata/nacos_http_consumer/NacosHttpConsumerATApplication.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients("com.gm.seata.nacos_http_consumer.service")
public class NacosHttpConsumerATApplication {

    public static void main(String[] args) {
        SpringApplication.run(NacosHttpConsumerATApplication.class, args);
    }
}

3.3.4.2 实体类

com/gm/seata/nacos_http_consumer/entity/A.java

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@TableName("t_a")
public class A {

    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    private String createBy;

    private LocalDateTime createTime;
}

3.3.4.3 Mapper类

com/gm/seata/nacos_http_consumer/mapper/AMapper.java

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gm.seata.nacos_http_consumer.entity.A;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface AMapper extends BaseMapper<A> {
}

3.3.4.4 Service类

com/gm/seata/nacos_http_consumer/service/ProviderServiceFeign.java

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "provider-at")
public interface ProviderServiceFeign {

    @RequestMapping(value = "save", method = RequestMethod.GET)
    void save(@RequestParam("createBy") String createBy);

}

com/gm/seata/nacos_http_consumer/service/AService.java

import com.gm.seata.nacos_http_consumer.entity.A;
import java.util.Map;

public interface AService {
    Long save(String createBy);

    void update(Long id, Map<String, String> map);

    void writeIsolation_GlobalLock(Long id);

    void writeIsolation_GlobalTransactional(Long id);

    Map<String, Object> readIsolation_GlobalLock(Long id);

    Map<String, Object> readIsolation_GlobalTransactional(Long id);

    Map<String, Object> dirtyReadExample(Long id);

    A dirtyWriteExample(Long id);
}

com/gm/seata/nacos_http_consumer/service/impl/AServiceImpl.java

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.gm.seata.nacos_http_consumer.service.ProviderServiceFeign;
import io.seata.spring.annotation.GlobalLock;
import io.seata.spring.annotation.GlobalTransactional;
import com.gm.seata.nacos_http_consumer.entity.A;
import com.gm.seata.nacos_http_consumer.mapper.AMapper;
import com.gm.seata.nacos_http_consumer.service.AService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class AServiceImpl implements AService {

    @Autowired
    ProviderServiceFeign providerServiceFeign;

    @Autowired
    AMapper aMapper;

    @Autowired
    JdbcTemplate jdbcTemplate;

    @GlobalTransactional
    @Override
    public Long save(String createBy) {
        providerServiceFeign.save(createBy);
        A a = new A();
        a.setCreateBy(createBy);
        a.setCreateTime(LocalDateTime.now());
        aMapper.insert(a);

        if (Math.random() < 0.5) {
            throw new RuntimeException("模拟调用本地接口异常");
        }
        return a.getId();
    }

    /**
     * 增加本地线程休眠,模式全局事务进行
     *
     * @param id
     */
    @GlobalTransactional
    @Override
    public void update(Long id, Map<String, String> map) {
        String createBy = UUID.randomUUID().toString();
        providerServiceFeign.save(createBy);

        A a = aMapper.selectById(id);
        map.put("修改前", JSON.toJSON(a).toString());

        a.setCreateBy(createBy);
        a.setCreateTime(LocalDateTime.now());
        QueryWrapper<A> queryWrapper = new QueryWrapper();
        queryWrapper.eq("id", id);
        aMapper.update(a, queryWrapper);

        map.put("修改后", JSON.toJSON(a).toString());
        try {
            TimeUnit.SECONDS.sleep(15);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        if (Math.random() < 0.5) {
            throw new RuntimeException("模拟调用本地接口异常");
        }
    }

    /**
     * 写隔离,通过GlobalLock方式,可了解lockRetryInterval和lockRetryTimes属性
     *
     * @param id
     */
    @GlobalLock(lockRetryInterval = 1000, lockRetryTimes = 30)
    @Override
    public void writeIsolation_GlobalLock(Long id) {
        A a = new A();
        a.setId(id);
        a.setCreateBy(UUID.randomUUID().toString());
        a.setCreateTime(LocalDateTime.now());
        QueryWrapper<A> queryWrapper = new QueryWrapper();
        queryWrapper.eq("id", id);
        aMapper.update(a, queryWrapper);
    }

    /**
     * 写隔离,通过GlobalTransactional方式,可了解lockRetryInterval和lockRetryTimes属性
     *
     * @param id
     */
    @GlobalTransactional/*(lockRetryInterval = 1000, lockRetryTimes = 30)*/
    @Override
    public void writeIsolation_GlobalTransactional(Long id) {
        A a = new A();
        a.setId(id);
        a.setCreateBy(UUID.randomUUID().toString());
        a.setCreateTime(LocalDateTime.now());
        QueryWrapper<A> queryWrapper = new QueryWrapper();
        queryWrapper.eq("id", id);
        aMapper.update(a, queryWrapper);
    }

    @GlobalLock(lockRetryInterval = 1000, lockRetryTimes = 30)
    @Override
    public Map<String, Object> readIsolation_GlobalLock(Long id) {
        return jdbcTemplate.queryForMap("select * from t_a where id=? for update ", id);
    }

    @GlobalTransactional(lockRetryInterval = 1000, lockRetryTimes = 30)
    @Override
    public Map<String, Object> readIsolation_GlobalTransactional(Long id) {
        return jdbcTemplate.queryForMap("select * from t_a where id=? for update ", id);
    }

    @Override
    public Map<String, Object> dirtyReadExample(Long id) {
        return jdbcTemplate.queryForMap("select * from t_a where id=?", id);
    }

    @Override
    public A dirtyWriteExample(Long id) {
        A a = new A();
        a.setId(id);
        a.setCreateBy(UUID.randomUUID().toString());
        a.setCreateTime(LocalDateTime.now());
        QueryWrapper<A> queryWrapper = new QueryWrapper();
        queryWrapper.eq("id", id);
        aMapper.update(a, queryWrapper);
        return a;
    }
}

注意事项:

  • TM (Transaction Manager) - 事务管理器 角色 的调用方法上添加@GlobalTransactional注解
  • 以上部分模拟事务隔离避免:脏写与脏读

3.3.4.5 Controller类

com/gm/seata/nacos_http_consumer/controller/ConsumerController.java

import com.alibaba.fastjson.JSON;
import com.gm.seata.nacos_http_consumer.entity.A;
import com.gm.seata.nacos_http_consumer.service.AService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestController
public class ConsumerController {

    @Autowired
    AService aService;

    @RequestMapping(value = "save", method = RequestMethod.GET)
    public String save(@RequestParam("createBy") String createBy) {
        try {
            return "全局事务操作成功,id=" + aService.save(createBy);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return "全局事务操作失败,error=" + e.getMessage();
        }
    }

    /**
     * 模拟长全局事务,随机成功失败
     *
     * @param id
     * @return
     */
    @RequestMapping(value = "update", method = RequestMethod.GET)
    public String update(@RequestParam("id") Long id) {
        Map<String, String> map = new HashMap<>();
        try {
            aService.update(id, map);
            return "全局事务操作成功," + JSON.toJSON(map).toString();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return "全局事务操作失败," + JSON.toJSON(map).toString() + ",error=" + e.getMessage();
        }
    }

    @RequestMapping(value = "dirty/write", method = RequestMethod.GET)
    public String dirtyWrite(@RequestParam("id") Long id) {
        try {
            A a = aService.dirtyWriteExample(id);
            return "本地事务-写操作成功," + JSON.toJSON(a).toString();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return "本地事务-写操作失败,error=" + e.getMessage();
        }
    }

    @RequestMapping(value = "dirty/read", method = RequestMethod.GET)
    public String dirtyRead(@RequestParam("id") Long id) {
        return JSON.toJSON(aService.dirtyReadExample(id)).toString();
    }

    @RequestMapping(value = "write/globallock", method = RequestMethod.GET)
    public String writeIsolation_GlobalLock(@RequestParam("id") Long id) {
        try {
            aService.writeIsolation_GlobalLock(id);
            return "本地事务-写操作成功";
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return "本地事务-写操作失败,error=" + e.getMessage();
        }
    }

    @RequestMapping(value = "write/globaltransactional", method = RequestMethod.GET)
    public String writeIsolation_GlobalTransactional(@RequestParam("id") Long id) {
        try {
            aService.writeIsolation_GlobalTransactional(id);
            return "本地事务-写操作成功";
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return "本地事务-写操作失败,error=" + e.getMessage();
        }
    }

    @RequestMapping(value = "read/globallock", method = RequestMethod.GET)
    public String readIsolation_GlobalLock(@RequestParam("id") Long id) {
        return JSON.toJSON(aService.readIsolation_GlobalLock(id)).toString();
    }

    @RequestMapping(value = "read/globaltransactional", method = RequestMethod.GET)
    public String readIsolation_GlobalTransactional(@RequestParam("id") Long id) {
        return JSON.toJSON(aService.readIsolation_GlobalTransactional(id)).toString();
    }
}

3.4 示例说明

3.4.1 分布式事务演示

访问:http://127.0.0.1:3000/save?createBy=1234567

  • 分布式事务成功

    在这里插入图片描述

    provider-at日志:

    在这里插入图片描述

    consumer-at日志:

    在这里插入图片描述

  • 分布式事务失败 - 模拟provider-at失败

    在这里插入图片描述

    provider-at日志:

    在这里插入图片描述

    consumer-at日志:

    在这里插入图片描述

  • 分布式事务失败 - 模拟consumer-at失败

    在这里插入图片描述

    provider-at日志:

    在这里插入图片描述

    consumer-at日志:

    在这里插入图片描述

3.4.2 事务隔离演示

访问:http://127.0.0.1:3000/update?id=1662729396682399746 模拟长时间分布式事务

Seata AT 模式理论学习、事务隔离及部分源码解析 | Spring Cloud 52

3.4.2.1 脏读隔离

  • 分布式长事务成功

    访问:http://127.0.0.1:3000/read/globallock?id=1662729396682399746

    在这里插入图片描述

    在本地读取上添加注解@GlobalLock,利用其参数:lockRetryInterval = 1000, lockRetryTimes = 30延长全局锁重试周期并使用select for update语句

  • 分布式长事务失败

    访问:http://127.0.0.1:3000/read/globaltransactional?id=1662729396682399746

    在这里插入图片描述

    在本地读取上添加注解@GlobalTransactional,利用其参数:lockRetryInterval = 1000, lockRetryTimes = 30延长全局锁重试周期并使用select for update语句

    分布式长事务执行识别,本地读取方法读取修改前的值

3.4.2.1 脏写隔离

  • 分布式长事务执行成功,本地事务执行失败

    访问:http://127.0.0.1:3000/write/globaltransactional?id=1662729396682399746

    在这里插入图片描述

    在本地读取上添加注解@GlobalTransactional未使用参数对全局锁重试周期进行延长

    出现以下异常:

    在这里插入图片描述

  • 分布式长事务执行失败,本地事务执行成功

    访问:http://127.0.0.1:3000/write/globallock?id=1662729396682399746

    在这里插入图片描述

    在本地读取上添加注解@GlobalLock,利用其参数:lockRetryInterval = 1000, lockRetryTimes = 30延长全局锁重试周期

    分布式长事务执行失败,本地写入方法执行成功

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

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

相关文章

组合总和--纯垃圾内容别看,浪费时间

1题目 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 candidates 中的 同一个 数字可以 无限制重复被选…

[电脑使用技巧]Windows 11安装安卓手机APP

如果你的电脑已经运行Windows 11,经常在电脑和手机来复制内容&#xff0c;那我们为什么不在Windows 上实现安卓APP的应用呢&#xff1f;其实操作真的非常简单&#xff0c;我们接下来给大家分享下如何实现在Windows 11的系统上安装的app。只要按照下面的步骤逐个完成你就可以开启…

ffmpeg编译成wasm

最近在看ffmpeg的源码 https://ffmpeg.xianwaizhiyin.net/ffplay/ https://crifan.github.io/media_process_ffmpeg/website/audio_process/ 做个可运行的例子 代码在找了一堆&#xff0c;可用的版本放在这 https://github.com/killinux/ffmpeg_wasm_demo 先把ffmpeg 编译成 …

50 Projects 50 Days - Scroll Animation 学习记录

项目地址 Scroll Animation 展示效果 Scroll Animation 实现思路 HTML结构比较简单&#xff0c;就是10个盒子元素。当鼠标滚动时&#xff0c;盒子分别从左右移动过来。 思路上最开始想到的是给每一个盒子标记一个序号&#xff0c;滚动屏幕后&#xff0c;计算已经划动屏幕的…

C++ - 非类型模版参数和模版的特化

目录 非类型模版参数 模版的特化 函数模版特化 类模版特化 全特化 半特化(偏特化) 参数更进一步的限制 非类型模版参数 在之前学过的例子来看&#xff0c;我们使用模版&#xff0c;它们的参数都是类型模版&#xff0c;根据类型来决定实例化 而模版其实还有一种参数&…

通过Python的pdfplumber库提取pdf中表格数据

文章目录 前言一、pdfplumber库是什么&#xff1f;二、安装pdfplumber库三、查看pdfplumber库版本四、提取pdf中表格数据1.引入库2.定义pdf文件路径3.打开pdf文件4.获取pdf文件中的页数5.遍历每一页6.获取当前页内容7.提取表格数据8.输出表格数据9.效果 总结 前言 大家好&#…

聊聊美剧202306

刚把《黄石》系列看完&#xff0c;决定写点最近看的美剧&#xff0c;总共十一部&#xff0c;小评一下&#xff0c;写点东西~ 《黄石》 《黄石》已经到第五季了&#xff0c;看样子还得继续拍。刚开始看《黄石》的时候&#xff0c;确实有种陷进去的感觉&#xff0c;很美很宏伟的电…

leetcode 264.丑数

题目描述跳转去leetcode 给你一个整数 n &#xff0c;请你找出并返回第 n 个 丑数 。 丑数 就是只包含质因数 2、3 和/或 5 的正整数。 来源&#xff1a;leecode&#xff1a;https://leetcode.cn/problems/ugly-number-ii/ 解法1&#xff0c; 动态规划 定义一个数组用来记录前…

VMware Workstation 安装 AlmaLinux 9.2

VMware Workstation 安装 AlmaLinux 9.2 AlmaLinux 9.2 镜像下载AlmaLinux 9.2 安装创建新用户配置免密 sudo 到 root 用户配置 ssh key 登录挂载新磁盘 AlmaLinux 9.2 镜像下载 访问 https://mirrors.almalinux.org/isos.html 下载安装镜像&#xff0c; AlmaLinux 9.2 安装 …

K8s进阶7——Sysdig、Falco、审计日志

文章目录 一、分析容器系统调用&#xff1a;Sysdig1.1. 安装1.2 常用参数1.3 采集分析1.4 示例1.4.1 查看某进程系统调用事件1.4.2 查看建立TCP连接事件1.4.3 查看某目录下打开的文件描述符1.4.4 查看容器的系统调用 1.5 Chisels工具1.5.1 网络类1.5.2 硬盘类1.5.3 cpu类1.5.4 …

新型智慧园区解决方案之园区通行管理设计

智慧园区作为现代化城市化建设的重要组成部分&#xff0c;已经成为未来城市的重要发展方向。在智慧园区的建设中&#xff0c;通行管理是一个十分重要的问题。为了解决这一问题&#xff0c;我们需要进行一系列的设计和方案的制定。 一、园区交通规划设计 园区交通规划设计是通…

IIS总线介绍

IIS是飞利浦在1986年定义(1996年修订)的数字音频传输标准&#xff0c;用于数字音频数据在系统内器件之间传输&#xff0c;例如编解码器CODEC、DSP、数字输入/输出接口、ADC、DAC和数字滤波器等。其与IC无关联。 IIS总线的信号&#xff1a; BCLK&#xff0c;串行时钟也叫位时钟…

以太坊 – 部署智能合约到Ganache

目录 1. Ganache本地区块链 1.1 主界面 1.2 设置 2. 开发智能合约 2.1 初始化项目 2.2 添加package.json文件 2.3 添加智能合约源文件 2.4 编译项目 3. 部署智能合约到Ganache 3.1 更新配置文件 3.2 创建迁移脚本 3.3 执行迁移命令 1. Ganache本地区块链 首先启动…

springboot配置Swagger3.0

springboot配置Swagger3.0 1、pom加入依赖 我们创建一个SpringBoot项目&#xff0c;引入 swagger3 依赖 <dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version>…

任正非:ChatGPT对我们的机会是什么,内部讲话实录!

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 我新建了人工智能中文站https://ai.weoknow.com 每天给大家更新可用的国内可用chatGPT资源 为感谢全国火花奖获奖者对于产业界及科学界做出的重大贡献&#xff0c;华为组织了与部分获奖老师与专家的座谈会。座谈会上&…

LeetCode.46. 全排列(回溯法入门)

写在前面&#xff1a; 题目链接&#xff1a;LeetCode.46. 全排列 编程语言&#xff1a;C 题目难度&#xff1a;中等 一、题目描述 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a…

【震撼GPT-4崛起!年薪60万工作被GPT-4取代,成本仅2千多,引发轰动!】

目录 福利&#xff1a;文末纯分享中文版CHAT GPT镜像&#xff0c;不存在魔法&#xff0c;纯分享免费使用 一、前言 1、GPT-4的表现&#xff0c;与一位6年工作经验的人类相当 2、GPT-4完成所有类型的任务都要比人类快得多&#xff01; 二、GPT-4当数据分析师&#xff0c;都…

聊一聊影响LCD屏背光效率的几个重要因素

前阶段&#xff0c;小白的一个朋友参加了一个面试。面试完和小白说到其技术面过程惨不忍睹。被提及原因时&#xff0c;主要还是因为面试者提出的问题&#xff0c;小白的朋友答复的可能不是很让面试官满意。出于好奇&#xff0c;小白问了问都存在哪些问题&#xff0c;其中一道便…

论文笔记:Graph neural networks: A review of methods and applications

1 GNN的设计pipeline 1.1 获取图结构 结构化场景 图结构在应用问题中是已知的 比如分子结构、物理系统非结构化场景 图结构在应用问题中是未知的 需要根据任务人为地建图 1.2 判断图的类型 & 尺寸 图的类型 有向图/无向图//异构图/同构图 图中的点和边类型是不是一样的…

Prometheus 简单介绍,部署

目录 Prometheus 介绍 功能介绍 Prometheus安装 安装介绍 prometheus.yml 文件介绍 prometheus实施安装 Prometheus常用命令参数有哪些 设置Prometheus-server开机自动启动 &#xff08;解释&#xff09; Prometheus简单启动页面介绍 node_exporte 是做什么的 安装n…