Spring Boot 3.2.5 整合 Seata 1.8.0 详细教程

前言

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

环境准备

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

  • JDK:17 或更高版本(Spring Boot 3.x 要求 JDK 17+)

  • Spring Boot:3.2.5

  • Seata:1.8.0

  • 注册中心:Nacos(本文使用 Nacos 作为注册中心和配置中心)

  • 数据库:MySQL 5.7+

  • 项目构建工具:Maven 3.6+

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 项目:

  • order-service(订单服务,端口 8081)

  • storage-service(库存服务,端口 8082)

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"

观察数据库:

  • 订单表新增一条状态为 1 的记录

  • 库存表对应商品库存减少 2

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 "扣减成功";
}

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

  • 订单表新增的记录状态为 0(说明未被更新)

  • 库存表数量未减少

  • undo_log 表中会记录回滚日志

常见问题及解决方案

1. 版本兼容性问题

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

2. XID 传递失败

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

  • 使用支持 Seata 的微服务组件(如 Spring Cloud Alibaba)

  • 手动实现拦截器传递 XID(如上面的 RestTemplate 配置)

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,对业务代码无侵入,是分布式事务接入的首选方案。

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

  1. 使用数据库模式存储事务日志,保证数据持久化

  2. 合理设置事务超时时间和重试策略

  3. 做好监控和告警,及时发现事务异常

希望本文能帮助你顺利地在项目中落地 Seata 分布式事务。如果在整合过程中遇到问题,欢迎留言交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

BlueSea 每日coding

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

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

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

打赏作者

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

抵扣说明:

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

余额充值