Spring Boot中如何保证数据的一致性?
·
在 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;
}
}
}
总结
- 本地事务:优先使用
@Transactional(rollbackFor = Exception.class),注意注解生效条件(public 方法、避免自调用)。 - 并发一致性:读多写少用乐观锁(版本号),写多读少用悲观锁(FOR UPDATE)。
- 分布式事务:强一致性用 Seata(
@GlobalTransactional),最终一致性用 “消息队列 + 补偿”(如 RocketMQ 事务消息)。
核心原则:优先使用轻量级方案(本地事务 > 乐观锁),分布式场景根据一致性要求选择强一致(Seata)或最终一致(消息补偿)。
更多推荐




所有评论(0)