02 SpringBoot集成Seata
使用springboot、Seata、nacos、druid、postgresql、dynamic-datasource、openfeign、mybatis-plus等组件实现多数据源的分布式全局事务。
1 创建SpringBoot项目
参考其他文章创建四个微服务模块
- 主调用端:seata-caller
- 从节点1:seata-callee-one
- 从节点2:seata-callee-two
- 多数据源节点:seata-callee-dynamic-datasource
2 项目模块配置
2.1 单数据源
2.1.1 依赖配置(pom文件)
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>seata-callee-one</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>seata-callee-one</name>
<description>seata-callee-one</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.example.seata.SeataApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2.1.2 参数配置
# 应用名称
spring.application.name=seata-callee-one
# 应用服务 WEB 访问端口
server.port=9081
# Actuator Web 访问端口
management.endpoints.jmx.exposure.include=*
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
# spring data
spring.datasource.username=*
spring.datasource.password=*
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&useUniCode=true&characterEncoding=utf-8
spring.cloud.nacos.discovery.group=dev
spring.cloud.nacos.discovery.namespace=seata
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
seata.enabled=true
seata.application-id=${spring.application.name}
seata.tx-service-group=my_test_tx_group
seata.enable-auto-data-source-proxy=true
seata.service.vgroup-mapping.my_test_tx_group=default
seata.registry.type=nacos
seata.registry.nacos.application=seata-server
seata.registry.nacos.cluster=default
seata.registry.nacos.group=dev
seata.registry.nacos.namespace=pscs-middle-platform
seata.registry.nacos.username=*
seata.registry.nacos.password=*
seata.registry.nacos.server-addr=127.0.0.1:8848
seata.config.type=nacos
seata.config.nacos.group=dev
seata.config.nacos.namespace=seata
seata.config.nacos.username=*
seata.config.nacos.password=*
seata.config.nacos.server-addr=127.0.0.1:8848
logging.level.io.seata=info
2.2 多数据源
2.2.1 依赖配置(pom文件)
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>seata-callee-dynamic-datasource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>seata-callee-dynamic-datasource</name>
<description>seata-callee-dynamic-datasource</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.example.seata.datasource.SeataCalleeDynamicDatasourceApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2.2.2 参数配置
# 应用名称
spring.application.name=seata-callee-dynamic-datasource
# Actuator Web 访问端口
management.server.port=8081
management.endpoints.jmx.exposure.include=*
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
# 应用服务 WEB 访问端口
server.port=8080
spring.cloud.nacos.discovery.group=dev
spring.cloud.nacos.discovery.namespace=seata
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.datasource.dynamic.druid.initial-size=5
spring.datasource.dynamic.druid.max-active=8
spring.datasource.dynamic.druid.min-idle=3
spring.datasource.dynamic.druid.max-wait=1000
spring.datasource.dynamic.seata=true
spring.datasource.dynamic.seata-mode=at
spring.datasource.dynamic.primary=gdfw
spring.datasource.dynamic.strict=true
spring.datasource.dynamic.datasource.base.username=*
spring.datasource.dynamic.datasource.base.password=*
spring.datasource.dynamic.datasource.base.driver-class-name=org.postgresql.Driver
spring.datasource.dynamic.datasource.base.url=jdbc:postgresql://127.0.0.1:5432/base?currentSchema=public&useUnicode=true&characterEncoding=utf-8
spring.datasource.dynamic.datasource.test.username=*
spring.datasource.dynamic.datasource.test.password=*
spring.datasource.dynamic.datasource.test.driver-class-name=org.postgresql.Driver
spring.datasource.dynamic.datasource.test.url=jdbc:postgresql://127.0.0.1:5432/test?currentSchema=public&useUnicode=true&characterEncoding=utf-8
spring.datasource.dynamic.datasource.work.username=*
spring.datasource.dynamic.datasource.work.password=*
spring.datasource.dynamic.datasource.work.driver-class-name=org.postgresql.Driver
spring.datasource.dynamic.datasource.work.url=jdbc:postgresql://127.0.0.1:5432/work?currentSchema=public&useUnicode=true&characterEncoding=utf-8
seata.enabled=true
seata.application-id=${spring.application.name}
seata.tx-service-group=my_test_tx_group
seata.enable-auto-data-source-proxy=false
seata.service.vgroup-mapping.my_test_tx_group=default
seata.registry.type=nacos
seata.registry.nacos.application=seata-server
seata.registry.nacos.cluster=default
seata.registry.nacos.group=dev
seata.registry.nacos.namespace=seata
seata.registry.nacos.username=*
seata.registry.nacos.password=*
seata.registry.nacos.server-addr=127.0.0.1:8848
seata.config.type=nacos
seata.config.nacos.group=dev
seata.config.nacos.namespace=seata
seata.config.nacos.username=*
seata.config.nacos.password=*
seata.config.nacos.server-addr=127.0.0.1:8848
mybatis-plus.global-config.db-config.id-type=assign_id
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.configuration.map-underscore-to-camel-case=true
logging.level.io.seata=info
注:使用mybatis-plus 动态数据源组件后undolog无法删除 ?
dynamic-datasource-spring-boot-starter 组件内部开启seata后会自动使用DataSourceProxy来包装DataSource,所以需要以下方式来保持兼容。
- 如果你引入的是seata-all,请不要使用@EnableAutoDataSourceProxy注解.
- 如果你引入的是seata-spring-boot-starter 请关闭自动代理
seata:
enable-auto-data-source-proxy: false
3 客户端数据库建表
地址:https://github.com/seata/seata/tree/develop/script/client/at/db
以postgres为例
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS public.undo_log
(
id SERIAL NOT NULL,
branch_id BIGINT NOT NULL,
xid VARCHAR(128) NOT NULL,
context VARCHAR(128) NOT NULL,
rollback_info BYTEA NOT NULL,
log_status INT NOT NULL,
log_created TIMESTAMP(0) NOT NULL,
log_modified TIMESTAMP(0) NOT NULL,
CONSTRAINT pk_undo_log PRIMARY KEY (id),
CONSTRAINT ux_undo_log UNIQUE (xid, branch_id)
);
CREATE SEQUENCE IF NOT EXISTS undo_log_id_seq INCREMENT BY 1 MINVALUE 1 ;
4 代码编写
4.1 数据库持久化及controller等
参考其他文章,推荐插件字段生成
4.2 多数据源切换
使用 @DS(“”) 切换数据源,在操作不是主库(默认库)时,需要添加此注解
@DS("work")
public interface TGfOrderMapper extends BaseMapper<TGfOrder> {
}
4.3 使用feign 调用其他模块
- 开启feign客户端
@MapperScan(basePackages = {"com.example.seata.datasource.mapper"})
@EnableFeignClients
@SpringBootApplication
public class SeataCalleeDynamicDatasourceApplication {
public static void main(String[] args) {
SpringApplication.run(SeataCalleeDynamicDatasourceApplication.class, args);
}
}
- 自定义feign客户端
@FeignClient("seata-callee-one")
public interface SeataCalleeOneFeign {
@GetMapping("/seata/callee/one/insert")
void insert(@RequestParam("name") String name);
}
4.4 开启全局事务
4.4.1 调用端
@GlobalTransactional(rollbackFor = Exception.class)
@Override
public void insertMultipleDatasourceData() {
System.out.println("insertMultipleDatasourceData xid: "+RootContext.getXID());
List<String> list = new ArrayList<>(5);
for (int i = 0; i < 5; i++) {
list.add(i + "");
}
this.saveBatch(list);
Order order = new Order();
order.setContent(RandomStringUtils.randomPrint(10));
orderService.save(order);
oneFeign.insert("li");
// throw new RuntimeException("测试seata回滚");
}
4.4.2 被调用端
@GetMapping("/insert")
public void insert(@RequestParam String name) {
// twoFeign.insert(name);
// if (name.contains("li")) {
// throw new RuntimeException("测试seata");
// }
System.out.println("seata-callee-one xid: " + RootContext.getXID());
jdbcTemplate.execute("insert into user (name) value ('" + name + "')");
}