有道无术,术尚可求,有术无道,止于术。
本系列Seata 版本 2.0.0
本系列Spring Boot 版本 3.2.0
本系列Spring Cloud 版本 2023.0.0
源码地址:https://gitee.com/pearl-organization/study-seata-demo
文章目录
- 1. 概述
- 2. 多数据源环境搭建
- 2.1 数据库
- 2.2 项目搭建
- 3. Seata 集成
- 3.1 undo_log 表
- 3.2 引入依赖
- 3.3 配置
- 3.4 添加注解
- 3.5 测试
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-demodatasource:dynamic:hikari:minimum-idle: 5idle-timeout: 30000maximum-pool-size: 20max-lifetime: 1800000connection-timeout: 50000primary: d_accountdatasource:d_account:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/seata_account?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=trueusername: rootpassword: 123456d_order:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/seata_order?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=trueusername: rootpassword: 123456d_stock:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/seata_stock?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=trueusername: rootpassword: 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 definitionCREATE 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_account
、seata_order
、seata_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: filefile:name: file.conf# 注册中心registry:type: file
多数据源配置开启Seata
,并设置事务模式:
datasource:dynamic:seata: true # 启用Seataseata-mode: at # 事务模式,支持AT、XAhikari:minimum-idle: 5
在resources
目录下添加file.conf
配置文件,内容如下:
transport {# tcp udt unix-domain-sockettype = "TCP"#NIO NATIVEserver = "NIO"#enable heartbeatheartbeat = true# the client batch send request enableenableClientBatchSendRequest = true#thread factory for nettythreadFactory {bossThreadPrefix = "NettyBoss"workerThreadPrefix = "NettyServerNIOWorker"serverExecutorThread-prefix = "NettyServerBizHandler"shareBossWorker = falseclientSelectorThreadPrefix = "NettyClientSelector"clientSelectorThreadSize = 1clientWorkerThreadPrefix = "NettyClientWorkerThread"# netty boss thread size,will not be used for UDTbossThreadSize = 1#auto default pin or 8workerThreadSize = "default"}shutdown {# when destroy server, wait secondswait = 3}serialization = "seata"compressor = "none"
}
service {#transaction service group mappingvgroupMapping.default_tx_group = "default"#only support when registry.type=file, please don't set multiple addressesdefault.grouplist = "127.0.0.1:8091"#degrade, current not supportenableDegrade = false#disable seatadisableGlobalTransaction = false
}client {rm {asyncCommitBufferLimit = 10000lock {retryInterval = 10retryTimes = 30retryPolicyBranchRollbackOnConflict = true}reportRetryCount = 5tableMetaCheckEnable = falsereportSuccessEnable = false}tm {commitRetryCount = 5rollbackRetryCount = 5}undo {dataValidation = truelogSerialization = "jackson"logTable = "undo_log"}log {exceptionRate = 100}
}
file.conf
中需要注意配置TC
地址:
3.4 添加注解
添加分布式事务注解@GlobalTransactional
:
@GlobalTransactionalpublic Object handleBusiness() {//......}
3.5 测试
启动Seata
、后台服务,查看日志,可以看到成功连接TC
,并在AT
模式下自动代理了数据源:
当发生业务异常时,可以看到多个RM
都进行了回滚操作,查看数据库也发现数据一致,说明集成成功: