java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot整合Seata

SpringBoot3.2.5整合Seata1.8.0详细教程

作者:BlueSea 每日coding

本文详细介绍了SpringBoot3.2.5项目整合Seata1.8.0实现分布式事务的全过程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

在微服务架构中,分布式事务是一个无法回避的难题。Seata 作为一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。本文将通过一步步的教程,带你掌握如何在 Spring Boot 3.2.5 项目中整合 Seata 1.8.0,实现分布式事务控制。

环境准备

在开始整合之前,请确保你的开发环境满足以下要求:

Seata 服务端部署

Seata 服务端(TC,即 Transaction Coordinator)是分布式事务的协调器,我们需要先部署好 Seata Server。

1. 下载 Seata Server

从 Seata 官方 GitHub Release 页面下载 seata-server-1.8.0 的压缩包:

wget https://github.com/seata/seata/releases/download/v1.8.0/seata-server-1.8.0.tar.gz
tar -zxvf seata-server-1.8.0.tar.gz

2. 配置 Seata Server

Seata 1.8.0 版本使用 application.yml 作为配置文件,位于 seata/conf 目录下。

2.1 配置注册中心和配置中心

修改 application.yml,设置使用 Nacos 作为注册中心和配置中心:

seata:
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: ""
      group: SEATA_GROUP
      username: "nacos"
      password: "nacos"
      data-id: seataServer.properties
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace: ""
      cluster: default
      username: "nacos"
      password: "nacos"

2.2 配置存储模式(可选)

如果需要持久化事务日志,可以配置使用数据库存储。在 Nacos 配置中心创建 data-id 为 seataServer.properties 的配置,添加以下内容:

# 存储模式
store.mode=db

# 数据库配置
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata_server?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.lockTable=lock_table
store.db.queryLimit=100

3. 初始化数据库表

如果使用数据库模式,需要在 MySQL 中创建 Seata 服务端所需的表:

-- 创建数据库
CREATE DATABASE IF NOT EXISTS seata_server;

-- 建表脚本位于 seata/script/server/db/ 目录下
-- 执行 mysql.sql 文件
SOURCE /path/to/seata/script/server/db/mysql.sql;

4. 启动 Seata Server

执行启动脚本:

# Linux/Mac
sh ./bin/seata-server.sh

# Windows
.\bin\seata-server.bat

服务启动后,默认监听端口:7091(控制台)和 8091(服务端口)。访问 Nacos 控制台,如果看到 seata-server 服务注册成功,则表示部署完成。

Spring Boot 客户端整合

接下来,我们将创建两个 Spring Boot 微服务(订单服务和库存服务)来演示分布式事务。

1. 创建 Spring Boot 项目

使用 Spring Initializr 创建两个 Spring Boot 3.2.5 项目:

2. 引入依赖

在每个服务的 pom.xml 中添加 Seata 依赖:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.5</version>
</parent>

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Boot Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- MySQL Driver -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Seata Spring Boot Starter -->
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-spring-boot-starter</artifactId>
        <version>1.8.0</version>
    </dependency>

    <!-- Nacos 注册中心客户端(Seata 注册需要) -->
    <dependency>
        <groupId>com.alibaba.nacos</groupId>
        <artifactId>nacos-client</artifactId>
        <version>2.2.3</version>
    </dependency>
</dependencies>

3. 客户端配置

在每个服务的 application.yml 中添加 Seata 配置:

server:
  port: 8081  # 订单服务端口

spring:
  application:
    name: order-service
  datasource:
    url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

# Seata 配置
seata:
  enabled: true
  application-id: ${spring.application.name}
  tx-service-group: default_tx_group
  # 服务端分组映射
  service:
    vgroup-mapping:
      default_tx_group: default
  # 注册中心配置
  registry:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: ""
      group: SEATA_GROUP
      username: "nacos"
      password: "nacos"
  # 配置中心(可选)
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: ""
      group: SEATA_GROUP
      username: "nacos"
      password: "nacos"
      data-id: seataClient.properties
  # 数据源代理(自动开启)
  enable-auto-data-source-proxy: true
  # 使用 AT 模式
  data-source-proxy-mode: AT

4. 创建 undo_log 表

Seata AT 模式需要在每个业务数据库中创建 undo_log 表,用于记录事务回滚日志:

-- 注意:在 order_db 和 storage_db 中都执行
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `id`            BIGINT(20)   NOT NULL AUTO_INCREMENT COMMENT 'increment id',
    `branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(100) 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     NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME     NOT NULL COMMENT 'modify datetime',
    PRIMARY KEY (`id`),
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';

5. 业务代码实现

5.1 库存服务 (Storage Service)

库存服务提供扣减库存的接口:

@RestController
@RequestMapping("/storage")
public class StorageController {

    @Autowired
    private StorageRepository storageRepository;

    @PostMapping("/deduct")
    public String deduct(@RequestParam Long productId, @RequestParam Integer count) {
        Storage storage = storageRepository.findByProductId(productId);
        if (storage.getCount() < count) {
            throw new RuntimeException("库存不足");
        }
        storage.setCount(storage.getCount() - count);
        storageRepository.save(storage);
        return "扣减成功";
    }
}

@Entity
public class Storage {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long productId;
    private Integer count;
    // getters and setters
}

5.2 订单服务 (Order Service)

订单服务创建订单并调用库存服务:

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private OrderRepository orderRepository;

    @PostMapping("/create")
    @GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
    public String createOrder(@RequestParam Long productId, 
                              @RequestParam Integer count,
                              @RequestParam BigDecimal amount) {
        // 1. 创建本地订单
        Order order = new Order();
        order.setProductId(productId);
        order.setCount(count);
        order.setAmount(amount);
        order.setStatus(0); // 待处理
        orderRepository.save(order);

        // 2. 远程调用库存服务扣减库存
        String url = "http://localhost:8082/storage/deduct?productId=" + productId + "&count=" + count;
        String result = restTemplate.postForObject(url, null, String.class);

        // 3. 更新订单状态
        order.setStatus(1); // 已完成
        orderRepository.save(order);

        return "订单创建成功";
    }
}

注意:@GlobalTransactional 注解是关键,它标识该方法需要开启全局事务。

6. 配置 RestTemplate

在订单服务中,需要配置 RestTemplate 以实现服务调用,并确保 Seata 的 XID 能够透传:

@Configuration
public class SeataRestTemplateConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    /**
     * 配置 RestTemplate 拦截器,实现 XID 传递
     */
    @Bean
    public RestTemplate restTemplate(RestTemplate restTemplate) {
        restTemplate.setInterceptors(Collections.singletonList(new RestTemplateInterceptor()));
        return restTemplate;
    }

    /**
     * 自定义拦截器,将 Seata 的 XID 放入请求头
     */
    public static class RestTemplateInterceptor implements ClientHttpRequestInterceptor {
        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, 
                                            ClientHttpRequestExecution execution) throws IOException {
            String xid = RootContext.getXID();
            if (StringUtils.isNotBlank(xid)) {
                request.getHeaders().add(RootContext.KEY_XID, xid);
            }
            return execution.execute(request, body);
        }
    }
}

测试分布式事务

1. 正常流程测试

请求订单创建接口:

curl "http://localhost:8081/order/create?productId=1&count=2&amount=100"

观察数据库:

2. 异常回滚测试

修改库存服务,主动抛出异常:

@PostMapping("/deduct")
public String deduct(@RequestParam Long productId, @RequestParam Integer count) {
    Storage storage = storageRepository.findByProductId(productId);
    if (storage.getCount() < count) {
        throw new RuntimeException("库存不足");
    }
    storage.setCount(storage.getCount() - count);
    storageRepository.save(storage);
    
    // 模拟异常
    if (productId == 1) {
        throw new RuntimeException("模拟库存服务异常");
    }
    return "扣减成功";
}

再次请求订单创建接口,观察结果:

常见问题及解决方案

1. 版本兼容性问题

Spring Boot 3.2.5 使用了 Jakarta EE 9+(javax 包名改为 jakarta),而 Seata 1.8.0 已经完全支持 Jakarta,无需额外配置。如果遇到类找不到的问题,检查是否有依赖引入了旧的 javax 包。

2. XID 传递失败

跨服务调用时,如果下游服务无法获取 XID,事务会失效。解决方案:

3. 数据源代理冲突

如果项目中同时使用 Druid 等连接池,需要确保 Seata 的数据源代理正确执行。Seata 的 seata-spring-boot-starter 会自动代理数据源,无需额外配置。

4. 注册中心连接失败

检查 Nacos 地址和端口是否正确,确保 Seata Server 已经成功注册到 Nacos。

总结

本文详细介绍了 Spring Boot 3.2.5 整合 Seata 1.8.0 的全过程,包括服务端部署、客户端配置、业务代码实现以及测试验证。Seata 的 AT 模式通过代理数据源和记录 undo_log,对业务代码无侵入,是分布式事务接入的首选方案。

在实际生产环境中,建议:

到此这篇关于SpringBoot3.2.5整合Seata1.8.0详细教程的文章就介绍到这了,更多相关SpringBoot整合Seata内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文