在 Spring Boot 中保证数据一致性,核心是解决分布式事务本地事务并发操作三类场景下的数据不一致问题。下面我会从易到难,结合实际场景和代码示例,讲解最常用的解决方案。

一、本地事务(单库操作)

这是最基础的场景,针对单个数据库的多表 / 多操作原子性,Spring Boot 提供了开箱即用的事务支持。

1. 核心实现:@Transactional 注解
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    private final OrderMapper orderMapper;
    private final OrderItemMapper orderItemMapper;

    // 构造器注入
    public OrderService(OrderMapper orderMapper, OrderItemMapper orderItemMapper) {
        this.orderMapper = orderMapper;
        this.orderItemMapper = orderItemMapper;
    }

    /**
     * 下单场景:同时保存订单主表和订单项表,保证原子性
     * rollbackFor = Exception.class:指定所有异常都回滚(默认只回滚RuntimeException)
     */
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Order order, List<OrderItem> orderItems) {
        // 1. 保存订单主表
        orderMapper.insert(order);
        // 2. 保存订单项(若这一步失败,上面的insert会回滚)
        orderItemMapper.batchInsert(orderItems);
    }
}
2. 关键配置(application.yml)

yaml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
    username: root
    password: root
  # 事务管理器自动配置(Spring Boot 会默认注入 DataSourceTransactionManager)
  jpa:
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        transaction:
          jta:
            platform: AtomikosPlatform # 分布式事务时用到
3. 核心注意事项
  • rollbackFor = Exception.class:必须显式指定,否则检查型异常(如 IOException)不会触发回滚。
  • 避免在非 public 方法上使用:Spring AOP 基于动态代理,非 public 方法无法被拦截。
  • 避免自调用:同一个类内的方法调用 @Transactional 方法,事务注解会失效(代理对象未生效)。

二、并发操作(多线程 / 多请求修改同一数据)

针对 “超卖”“重复扣款” 等并发场景,需要通过乐观锁保证数据一致性。

1. 乐观锁(推荐,无性能损耗)

原理:基于版本号 / 时间戳,更新时校验数据是否被修改,适合读多写少场景。

// 1. 实体类添加版本号字段
public class Product {
    private Long id;
    private Integer stock; // 库存
    private Integer version; // 乐观锁版本号
}

// 2. Mapper 层(MyBatis 示例)
<update id="deductStock">
    UPDATE product 
    SET stock = stock - #{num}, version = version + 1
    WHERE id = #{id} AND version = #{version} AND stock >= #{num}
</update>

// 3. Service 层
@Service
public class ProductService {
    private final ProductMapper productMapper;

    @Transactional(rollbackFor = Exception.class)
    public boolean deductStock(Long productId, Integer num) {
        // 1. 查询商品(获取当前版本号)
        Product product = productMapper.selectById(productId);
        if (product == null || product.getStock() < num) {
            return false;
        }
        // 2. 扣减库存(版本号校验)
        int affectedRows = productMapper.deductStock(productId, num, product.getVersion());
        // 3. 若影响行数为0,说明数据已被修改,返回失败
        return affectedRows > 0;
    }
}
2. 悲观锁(适合写多读少场景)

原理:查询时直接锁定行,直到事务结束,性能损耗较大。

sql

-- MyBatis 示例:查询时加锁
SELECT * FROM product WHERE id = #{id} FOR UPDATE;

三、分布式事务(多库 / 多服务操作)

跨数据库、跨微服务的场景,需要分布式事务方案,以下是 Spring Boot 中常用的实现:

1. Seata(阿里开源,推荐)

Seata 是轻量级分布式事务框架,支持 AT/TCC/SAGA/TCC 模式,Spring Boot 集成步骤:

步骤 1:引入依赖

xml

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>2022.0.0.0</version>
</dependency>
步骤 2:配置 application.yml

yaml

seata:
  tx-service-group: my_test_tx_group # 事务组名称
  registry:
    type: nacos # 注册中心(按需选择nacos/eureka/zookeeper)
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: ""
      group: SEATA_GROUP
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      namespace: ""
步骤 3:使用 @GlobalTransactional 注解
@Service
public class OrderDistributedService {

    private final OrderService orderService; // 本地服务
    private final PaymentService paymentService; // 远程服务(微服务调用)

    // 全局事务注解:跨服务操作原子性
    @GlobalTransactional(rollbackFor = Exception.class)
    public void createOrderWithPayment(Order order, Payment payment) {
        // 1. 本地创建订单(本地事务)
        orderService.createOrder(order);
        // 2. 远程调用支付服务(若失败,订单会回滚)
        paymentService.pay(payment);
    }
}
2. 最终一致性方案(柔性事务)

如果强一致性要求不高,可采用 “消息队列 + 补偿” 实现最终一致性(如 RocketMQ 事务消息):

@Service
public class OrderMqService {

    private final RocketMQTemplate rocketMQTemplate;
    private final OrderMapper orderMapper;

    // 1. 发送半事务消息
    @Transactional(rollbackFor = Exception.class)
    public void createOrderWithMq(Order order) {
        // 第一步:本地保存订单(待确认状态)
        order.setStatus("PENDING");
        orderMapper.insert(order);
        
        // 第二步:发送半事务消息
        rocketMQTemplate.sendMessageInTransaction(
            "order_topic",
            MessageBuilder.withPayload(order.getId()).build(),
            order // 业务参数
        );
    }

    // 2. 本地事务回调(确认消息是否发送)
    @RocketMQTransactionListener
    public class OrderTransactionListener implements RocketMQLocalTransactionListener {
        @Override
        public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
            try {
                // 校验本地订单是否保存成功
                Order order = (Order) arg;
                if (orderMapper.selectById(order.getId()) != null) {
                    return RocketMQLocalTransactionState.COMMIT; // 提交消息
                } else {
                    return RocketMQLocalTransactionState.ROLLBACK; // 回滚消息
                }
            } catch (Exception e) {
                return RocketMQLocalTransactionState.UNKNOWN; // 未知状态,等待回查
            }
        }

        // 3. 消息回查(补偿机制)
        @Override
        public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
            Long orderId = Long.parseLong(new String((byte[]) msg.getPayload()));
            Order order = orderMapper.selectById(orderId);
            if (order != null && "PENDING".equals(order.getStatus())) {
                return RocketMQLocalTransactionState.COMMIT;
            }
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }
}

总结

  1. 本地事务:优先使用 @Transactional(rollbackFor = Exception.class),注意注解生效条件(public 方法、避免自调用)。
  2. 并发一致性:读多写少用乐观锁(版本号),写多读少用悲观锁(FOR UPDATE)。
  3. 分布式事务:强一致性用 Seata(@GlobalTransactional),最终一致性用 “消息队列 + 补偿”(如 RocketMQ 事务消息)。

核心原则:优先使用轻量级方案(本地事务 > 乐观锁),分布式场景根据一致性要求选择强一致(Seata)或最终一致(消息补偿)。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐