Seata 2.x 系列【11】多数据源分布式事务

本文介绍了如何在SpringBoot3.2.0项目中集成Seata2.0.0处理分布式事务,包括在多数据源环境中搭建、动态数据源配置、undo_log表的创建、依赖引入、事务配置和分布式事务注解的使用。通过测试验证了Seata在AT模式下的正确工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有道无术,术尚可求,有术无道,止于术。

本系列Seata 版本 2.0.0

本系列Spring Boot 版本 3.2.0

本系列Spring Cloud 版本 2023.0.0

源码地址:https://gitee.com/pearl-organization/study-seata-demo

1. 概述

在之前的案例中,我们在Spring Cloud微服务场景下使用Seata解决了分布式事务问题,在单体架构中,单个服务也可能存在跨库导致的分布式事务问题,例如下图中,因为分库导致下单请求需要连接多个数据库进行操作:
在这里插入图片描述

2. 多数据源环境搭建

这里使用MyBatis-Plus开发团队提供的多数据源框架dynamic-datasource,相关简介可参考官网

2.1 数据库

使用的是Mysql 8.0.29,首先创建三个数据库:

  • seata_account:账户
  • seata_order:订单
  • seata_stock:库存

seata_account库插入t_account表:

CREATE TABLE `t_account` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `amount` double(14,2) DEFAULT '0.00',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO t_account
(user_id, amount)
VALUES(1, 10000.00);

seata_order库插入t_order表:

CREATE TABLE `t_order` (
  `id` int NOT NULL AUTO_INCREMENT,
  `order_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `user_id` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `commodity_code` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `count` int DEFAULT '0',
  `amount` double(14,2) DEFAULT '0.00',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=64 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

seata_stock库插入t_stock表:

CREATE TABLE `t_stock` (
  `id` int NOT NULL AUTO_INCREMENT,
  `commodity_code` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `count` int DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO t_stock
(commodity_code, name, count)
VALUES('IPHONE', '苹果手机', 10000);

2.2 项目搭建

注意:这里省略了一些简单代码,完整代码请参考案例源码

项目技术栈:

  • JDK 17
  • Maven 3.6.3
  • Seata 2.0
  • Mybatis Plus 3.5.5
  • Dynamic Datasource 4.3.0
  • Spring Boot 3.2.0

使用Spring Initializr创建一个Spring Boot服务项目,引入相关依赖:

    <dependencies>
        <!--Spring Boot Web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--工具-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.21</version>
        </dependency>
        <!--Mybatis Plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.5</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.baomidou/dynamic-datasource-spring-boot-starter -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
            <version>4.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

添加多数据源配置:

server:
  port: 9000
spring:
  application:
    name: dynamic-datasource-seata-demo
  datasource:
    dynamic:
      hikari:
        minimum-idle: 5
        idle-timeout: 30000
        maximum-pool-size: 20
        max-lifetime: 1800000
        connection-timeout: 50000
      primary: d_account
      datasource:
        d_account:
          type: com.zaxxer.hikari.HikariDataSource
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/seata_account?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true
          username: root
          password: 123456
        d_order:
          type: com.zaxxer.hikari.HikariDataSource
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/seata_order?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true
          username: root
          password: 123456
        d_stock:
          type: com.zaxxer.hikari.HikariDataSource
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/seata_stock?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true
          username: root
          password: 123456

各个Mapper接口添加DS注解:

@DS("d_account")
@DS("d_order")
@DS("d_stock")

BusinessService服务添加下单功能:

    public Object handleBusiness() {
        // 1. 业务请求数据
        BusinessDTO businessDTO =new BusinessDTO();
        businessDTO.setUserId("1"); // 下单用户
        businessDTO.setCount(1); // 数量
        businessDTO.setCommodityCode("IPHONE"); // 商品编号
        businessDTO.setAmount(new BigDecimal(1)); // 订单金额
        log.info("业务请求数据:"+ businessDTO);

        // 2. 扣减库存 
        CommodityDTO commodityDTO = new CommodityDTO();
        commodityDTO.setCommodityCode(businessDTO.getCommodityCode()); // 商品编号
        commodityDTO.setCount(businessDTO.getCount()); // 数量
        Object stock = stockService.decreaseStock(commodityDTO);
        log.info("调用库存服务:"+ stock);

        // 3. 创建订单
        OrderDTO orderDTO = new OrderDTO();
        orderDTO.setUserId(businessDTO.getUserId());
        orderDTO.setCommodityCode(businessDTO.getCommodityCode());
        orderDTO.setOrderCount(businessDTO.getCount());
        orderDTO.setOrderAmount(businessDTO.getAmount());
        OrderDTO response = orderService.createOrder(orderDTO);
        log.info("调用订单服务:"+ response);

        return orderDTO;
    }

3. Seata 集成

3.1 undo_log 表

AT模式中,需要在参与全局事务的数据库中添加undo_log表:

-- seata_account.undo_log definition

CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `branch_id` bigint NOT NULL COMMENT '分支事务ID',
  `xid` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8_general_ci NOT NULL COMMENT '全局事务唯一标识',
  `context` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8_general_ci NOT NULL COMMENT '上下文',
  `rollback_info` longblob NOT NULL COMMENT '回滚信息',
  `log_status` int NOT NULL COMMENT '状态,0正常,1全局已完成(防悬挂)',
  `log_created` datetime NOT NULL COMMENT '创建时间',
  `log_modified` datetime NOT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='AT模式回滚日志表';

seata_accountseata_orderseata_stock库中,都新建undo_log表。

3.2 引入依赖

这里演示的是单机模式,所以不需要注册中心、配置中心,引入seata提供的Spring Boot启动包:

        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

3.3 配置

application.yml中配置使用file作为注册中心、配置中心:

seata:
  # 配置中心
  config:
    type: file
    file:
      name: file.conf
  # 注册中心
  registry:
    type: file

多数据源配置开启Seata,并设置事务模式:

  datasource:
    dynamic:
      seata: true # 启用Seata
      seata-mode: at # 事务模式,支持AT、XA
      hikari:
        minimum-idle: 5

resources目录下添加file.conf配置文件,内容如下:

transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  # the client batch send request enable
  enableClientBatchSendRequest = true
  #thread factory for netty
  threadFactory {
    bossThreadPrefix = "NettyBoss"
    workerThreadPrefix = "NettyServerNIOWorker"
    serverExecutorThread-prefix = "NettyServerBizHandler"
    shareBossWorker = false
    clientSelectorThreadPrefix = "NettyClientSelector"
    clientSelectorThreadSize = 1
    clientWorkerThreadPrefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    bossThreadSize = 1
    #auto default pin or 8
    workerThreadSize = "default"
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}
service {
  #transaction service group mapping
  vgroupMapping.default_tx_group = "default"
  #only support when registry.type=file, please don't set multiple addresses
  default.grouplist = "127.0.0.1:8091"
  #degrade, current not support
  enableDegrade = false
  #disable seata
  disableGlobalTransaction = false
}

client {
  rm {
    asyncCommitBufferLimit = 10000
    lock {
      retryInterval = 10
      retryTimes = 30
      retryPolicyBranchRollbackOnConflict = true
    }
    reportRetryCount = 5
    tableMetaCheckEnable = false
    reportSuccessEnable = false
  }
  tm {
    commitRetryCount = 5
    rollbackRetryCount = 5
  }
  undo {
    dataValidation = true
    logSerialization = "jackson"
    logTable = "undo_log"
  }
  log {
    exceptionRate = 100
  }
}

file.conf中需要注意配置TC地址:
在这里插入图片描述

3.4 添加注解

添加分布式事务注解@GlobalTransactional

    @GlobalTransactional
    public Object handleBusiness() {//......}

3.5 测试

启动Seata、后台服务,查看日志,可以看到成功连接TC,并在AT模式下自动代理了数据源:
在这里插入图片描述
当发生业务异常时,可以看到多个RM都进行了回滚操作,查看数据库也发现数据一致,说明集成成功:
**在这里插入图片描述**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨 禹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值