JAVA防止订单重复提交

订单重复提交是许多系统都会面临的常见问题,尤其是在电商等高并发场景中,如果没有合适的防重策略,可能会导致同一订单被处理多次,进而引发库存、支付等严重问题。你提到的 3 毫秒内订单重复提交的问题,涉及到一个非常短的时间窗口,要解决这个问题,可以采取一些简单但有效的防重策略,下面我们逐步讲解几种常见的方案。
1. 基于 Token 防重复提交

Token 防重是一个简单而有效的解决方案。核心思路是:每次用户提交订单时,服务器先生成一个唯一的 Token,发送给客户端。客户端在提交订单时必须携带这个 Token,服务器端会检查该 Token 是否已经使用过,如果已经使用则拒绝订单提交。

步骤:用户请求页面时,服务器生成一个唯一的 Token,并将该 Token 保存在 Redis 或数据库中,同时返回给客户端。客户端提交订单时,带上这个 Token。后台收到请求后,检查 Redis 或数据库中的 Token: 如果 Token 存在且未使用,则处理订单并将 Token 标记为已使用。如果 Token 已使用或不存在,则拒绝订单提交。优点:简单且高效,适用于表单提交或页面操作防重。缺点:需要服务器和客户端之间同步 Token 管理,增加了一些额外的管理逻辑。代码示例:

java 复制代码
// 获取订单的唯一 Token
String token = redisService.getToken(orderId);

if (token == null || redisService.isTokenUsed(token)) {
    throw new RuntimeException("订单已提交,不能重复提交");
}

// 订单处理逻辑...
redisService.markTokenAsUsed(token);

2. 基于数据库唯一约束

另一种比较稳妥的做法是利用数据库的唯一约束,确保订单数据不会重复插入。你可以在订单表中针对某些关键字段(比如订单号、用户 ID)设置唯一索引,这样在订单重复提交时,数据库会直接报错,防止同一订单被重复写入。步骤:在数据库订单表的订单号字段上添加唯一索引。当订单提交时,如果订单号重复,数据库会抛出唯一约束异常(Duplicate Key Exception),从而阻止订单的重复提交。优点:简单可靠,直接从数据库层面防止重复插入。无需额外的缓存或服务支持。缺点:每次订单提交都要访问数据库,性能可能受影响,特别是在高并发场景下。代码示例:

java 复制代码
try {
    // 插入订单到数据库
    orderRepository.save(order)
} catch (DuplicateKeyException e) {
    throw new RuntimeException("订单重复提交");
}
  1. 基于 Redis 的分布式锁
    分布式锁是解决高并发重复提交的有效手段,尤其是在多实例部署的场景下。通过 Redis 实现的分布式锁,可以确保同一订单的提交在同一时刻只能处理一次。
    步骤:
    当用户提交订单时,后台通过 Redis 的 SETNX(set if not exists)命令尝试获取订单号对应的锁。
    如果成功获取锁,处理订单并在处理完成后释放锁。
    如果未能获取锁(意味着订单已在处理中),直接返回提示用户订单正在处理中,防止重复提交。
    优点:
    适用于分布式环境下的并发控制,性能高。
    可以自定义锁的过期时间,防止锁遗留。
    缺点:
    需要引入 Redis,增加系统复杂度。
    处理锁超时和故障恢复需要精心设计。
    代码示例:
java 复制代码
 String lockKey = "order_lock_" + orderId;
boolean isLocked = redisService.tryLock(lockKey, 3000);  // 3 秒过期时间

if (!isLocked) {
    throw new RuntimeException("订单正在处理中,请稍后再试");
}

try {
    // 处理订单逻辑...
} finally {
    redisService.unlock(lockKey);
}

4. 基于接口幂等性

接口幂等性是指同样的请求,无论调用多少次,最终的结果都是一致的。我们可以通过设置一个唯一的标识符(例如订单号)确保即使多次提交,最终也只会执行一次。
步骤:

在请求中携带一个唯一的标识符(例如订单号)

。每次订单提交前检查这个标识符是否已经处理过,未处理则继续处理,否则返回重复提交提示。
优点:

逻辑简单,适用于任意提交请求的场景。保证每个订单请求只会执行一次。
缺点:

需要依赖数据库或缓存来保存已经处理过的请求标识符。

代码示例:

java 复制代码
String orderId = request.getOrderId();
if (redisService.hasProcessed(orderId)) {
    throw new RuntimeException("订单已提交,不能重复提交");
}
// 订单处理逻辑...
redisService.markAsProcessed(orderId);

5. 防重的组合策略

在实际项目中,防止订单重复提交往往需要结合多种策略来应对不同场景。比如可以使用 Redis 分布式锁 + 数据库唯一约束 组合使用,既能提高并发下的性能,又有数据库层面的强一致性保障。组合示例:使用 Redis 分布式锁确保在高并发情况下,只有一个线程处理订单。数据库层面设置唯一约束,双重防止订单重复提交。

**

java 复制代码
String lockKey = "order_lock_" + orderId;
boolean isLocked = redisService.tryLock(lockKey, 3000);  // 获取分布式锁
if (!isLocked) {
    throw new RuntimeException("订单正在处理中");
}
try {
    // 处理订单并插入数据库(带有唯一约束)
    orderRepository.save(order);
} catch (DuplicateKeyException e) {
    throw new RuntimeException("订单重复提交");
} finally {
    redisService.unlock(lockKey);
}

**

总结对于如何防止 3 毫秒内订单重复提交,常见的策略有以下几种:
Token 防重提交 :通过唯一 Token 识别每次订单提交。
数据库唯一约束 :从数据库层面杜绝重复订单。
Redis 分布式锁 :高并发场景下,利用分布式锁保证同一时刻只有一个订单被处理。
接口幂等性 :确保重复提交的请求不会重复执行。
组合策略 :结合 Redis 锁和数据库唯一约束,实现更高的性能和一致性。

在实际项目中,针对不同的场景选择合适的方案,可能还需要结合缓存、消息队列等技术手段,最终保障系统的稳定性和正确性。

相关推荐
Q_19284999064 分钟前
基于Spring Boot的摄影器材租赁回收系统
java·spring boot·后端
qq_433554545 分钟前
C++ 面向对象编程:+号运算符重载,左移运算符重载
开发语言·c++
Code_流苏6 分钟前
VSCode搭建Java开发环境 2024保姆级安装教程(Java环境搭建+VSCode安装+运行测试+背景图设置)
java·ide·vscode·搭建·java开发环境
数据小爬虫@24 分钟前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
ZJ_.26 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
Narutolxy31 分钟前
深入探讨 Go 中的高级表单验证与翻译:Gin 与 Validator 的实践之道20241223
开发语言·golang·gin
Hello.Reader38 分钟前
全面解析 Golang Gin 框架
开发语言·golang·gin
禁默1 小时前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood1 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Code哈哈笑1 小时前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习