Seata使用

Seata 分布式事务使用指南:从集成到实战

Seata 作为分布式事务解决方案,其使用流程简洁高效,核心通过注解标记全局事务、代理数据源实现自动回滚。本文基于 Spring Cloud Alibaba 生态,详细介绍 Seata 的集成步骤与实战要点。

环境准备

在使用 Seata 前,需确保:

  1. Seata Server 已启动:作为事务协调者(TC),需提前配置并启动(参考前文 Seata Server 配置);

  2. 数据库准备:所有参与分布式事务的微服务数据库需创建undo_log表(AT 模式用于回滚日志存储),SQL 脚本如下:

    CREATE TABLE `undo_log` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `branch_id` bigint(20) NOT NULL,
      `xid` varchar(100) NOT NULL,
      `context` varchar(128) NOT NULL,
      `rollback_info` longblob NOT NULL,
      `log_status` int(11) NOT NULL,
      `log_created` datetime NOT NULL,
      `log_modified` datetime NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

微服务集成 Seata

1. 引入依赖

在各参与分布式事务的微服务(如订单服务、库存服务、账户服务)中添加 Seata 依赖,注意版本需与 Seata Server 一致

<!-- Spring Cloud Alibaba Seata  starter -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <!-- 排除默认Seata依赖,避免版本冲突 -->
    <exclusions>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- 引入与Seata Server一致的版本(如1.1.0) -->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <version>1.1.0</version>
</dependency>

2. 配置文件(application.yml)

核心配置事务分组与注册中心,确保微服务能关联到 Seata Server:

spring:
  application:
    name: springcloudalibaba-seata-order  # 服务名
  cloud:
    alibaba:
      seata:
        tx-service-group: my_test_tx_group  # 事务分组(需与Seata Server的vgroupMapping一致)
    nacos:
      discovery:
        server-addr: localhost:8848  # 注册中心地址(与Seata Server一致)
  datasource:
    druid:  # 本地数据库配置
      username: root
      password: 123456
      url: jdbc:mysql://localhost:3306/seata_order  # 订单库(示例)
      driver-class-name: com.mysql.jdbc.Driver

# MyBatis配置(可选,根据实际ORM框架调整)
mybatis:
  config-location: classpath:mybatis/mybatis.cfg.xml
  type-aliases-package: com.zhanghe.study.springcloudalibaba.model
  mapper-locations: classpath:mybatis/mapper/**/*.xml

关键说明

  • tx-service-group需与 Seata Server 的file.confvgroupMapping.my_test_tx_group配置一致,用于关联 TC 集群;
  • 若使用 Nacos 作为注册中心,需确保微服务与 Seata Server 的registry.conf配置一致。

3. 拷贝 Seata 配置文件

将 Seata Server 的conf目录下的file.confregistry.conf拷贝到微服务的src/main/resources目录下:

  • file.conf:配置事务分组、存储模式等(需与 Server 端保持一致);
  • registry.conf:配置注册中心类型(如 Nacos),确保微服务能发现 Seata Server。

核心配置:数据源代理

Seata 通过代理数据源实现 SQL 拦截与 undo 日志生成(AT 模式核心),需替换默认数据源为 Seata 的DataSourceProxy

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.MybatisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.Resource;
import javax.sql.DataSource;
import java.io.IOException;

@Configuration
@EnableConfigurationProperties(MybatisProperties.class)  // 导入MyBatis配置
public class DataSourceConfiguration {

    // 1. 配置原始数据源(如Druid)
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid")  // 绑定yml中的数据源配置
    public DataSource dataSource() {
        return new DruidDataSource();
    }

    // 2. 用Seata的DataSourceProxy代理数据源
    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    // 3. 配置MyBatis使用代理后的数据源
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSourceProxy dataSourceProxy, 
                                                     MybatisProperties mybatisProperties) throws IOException {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);  // 设置代理数据源

        // 绑定MyBatis配置(mapper路径、config位置等)
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource[] mapperLocations = resolver.getResources(mybatisProperties.getMapperLocations()[0]);
        sqlSessionFactoryBean.setMapperLocations(mapperLocations);
        
        if (mybatisProperties.getConfigLocation() != null) {
            Resource[] configResources = resolver.getResources(mybatisProperties.getConfigLocation());
            sqlSessionFactoryBean.setConfigLocation(configResources[0]);
        }
        return sqlSessionFactoryBean;
    }
}

为什么需要代理数据源?

  • Seata 通过代理数据源拦截 SQL 执行,自动生成 undo 日志(记录数据修改前的状态);
  • 全局回滚时,通过 undo 日志恢复数据,实现无侵入式事务回滚。

启动类配置

需排除 Spring Boot 默认的数据源自动配置,避免与 Seata 代理数据源冲突:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)  // 排除默认数据源配置
@EnableDiscoveryClient  // 启用服务发现
@EnableFeignClients  // 启用Feign(用于服务间调用)
public class OrderApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class, args);
    }
}

标记全局事务:@GlobalTransactional

在分布式事务的发起方(TM)的核心业务方法上添加@GlobalTransactional注解,标记为全局事务:

import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Autowired
    private OrderDao orderDao;  // 订单DAO

    @Autowired
    private StorageClient storageClient;  // Feign客户端:调用库存服务

    @Autowired
    private AccountClient accountClient;  // Feign客户端:调用账户服务

    /**
     * 创建订单(全局事务入口)
     */
    @GlobalTransactional(
        name = "create-order",  // 事务名称(唯一标识)
        rollbackFor = Exception.class  // 发生任何异常均回滚
    )
    public void create(Order order) {
        // 1. 创建订单(本地事务)
        System.out.println("创建订单");
        orderDao.addOrder(order);

        // 2. 调用库存服务:扣减库存(远程事务)
        System.out.println("扣减库存");
        storageClient.decrease(order.getProductId(), order.getCount());

        // 3. 调用账户服务:扣减余额(远程事务)
        System.out.println("扣减账户余额");
        accountClient.decrease(order.getUserId(), order.getMoney());

        // 4. 修改订单状态为“完成”(本地事务)
        System.out.println("修改订单状态");
        orderDao.updateStatus(order.getId(), 1);  // 1表示成功
    }
}

注解参数说明

  • name:全局事务名称(建议唯一,便于追踪);
  • rollbackFor:指定触发回滚的异常类型(默认仅回滚RuntimeException);
  • timeoutMills:全局事务超时时间(默认 60000ms)。

服务间调用:Feign 客户端

远程服务调用需通过 Feign 实现(确保 XID 全局事务 ID 在调用链路中传播):

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

// 库存服务Feign客户端
@FeignClient(name = "springcloudalibaba-seata-storage")  // 目标服务名
public interface StorageClient {

    // 扣减库存
    @PostMapping("/storage/decrease")
    void decrease(@RequestParam("productId") Long productId, 
                  @RequestParam("count") Integer count);
}

// 账户服务Feign客户端(类似)
@FeignClient(name = "springcloudalibaba-seata-account")
public interface AccountClient {
    @PostMapping("/account/decrease")
    void decrease(@RequestParam("userId") Long userId, 
                  @RequestParam("money") BigDecimal money);
}

XID 传播机制
Seata 通过ThreadLocal存储 XID(全局事务 ID),Feign 调用时会自动将 XID 放入请求头,远程服务接收后通过拦截器提取 XID 并绑定到当前线程,确保分支事务关联到全局事务。

测试分布式事务

  1. 正常流程:所有服务调用成功,全局事务提交,undo_log表记录被删除;
  2. 异常流程:任意远程服务抛出异常(如库存不足),全局事务回滚,所有操作(创建订单、扣减库存、扣减余额)均被撤销,undo_log表记录用于回滚恢复。

注意事项

  1. 版本一致性:微服务的seata-all版本必须与 Seata Server 版本一致,否则可能出现通信异常;
  2. 事务分组匹配tx-service-group需与 Seata Server 的vgroupMapping严格一致(大小写敏感);
  3. undo_log 表:所有参与事务的数据库必须创建undo_log表,否则 AT 模式回滚会失败;
  4. 数据源代理:必须使用DataSourceProxy代理数据源,否则 Seata 无法拦截 SQL 生成 undo 日志;
  5. 全局事务范围@GlobalTransactional仅需添加在事务发起方(TM),参与方(RM)无需额外注解。

总结

Seata 通过@GlobalTransactional注解、数据源代理和 XID 传播机制,实现了分布式事务的无侵入式管理。核心步骤包括:集成依赖、配置事务分组、代理数据源、标记全局事务