告别订单号重复!🎉 Spring Boot + MySQL 优雅生成唯一批次号实战
哈喽,各位开发者伙伴们!👋 在日常开发中,生成唯一的订单号、流水号或批次号是一个非常常见的需求。最近我在处理一个寄售(Consignment)业务时,就遇到了一个关于批次订单号 (orderNo
) 生成的小挑战,并最终采用了一种简单高效的方式解决了它。今天就来和大家分享一下这个过程,希望能给大家带来一些启发!💡
遇到的问题:恼人的“Duplicate entry” 😭
最初的需求是:前端一次请求会发送一个包含多个寄售商品信息的数组到后端 /saveConsignmentSummary
接口,后端需要为这次请求的所有商品生成一个相同的批次订单号 (orderNo
)。下次请求再来时,生成的 orderNo
要和上一次不同。
听起来很简单?🤔 我一开始也是这么觉得的!于是我快速地在 Controller 层生成了一个基于“高精度时间戳+随机数”的 orderNo
,并赋给列表中的每一个对象,然后调用 saveAll
保存。
然而,现实很快给了我一记“重拳” 🧱:
ERROR ... Duplicate entry 'CON20250430145255427-4083' for key 'consignment_summary.UK_catfmq4ovjdvqndwvh6r6jas1'
Caused by: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry ...
啊哦!数据库报了唯一约束冲突的错误!😱
检查了一下 ConsignmentSummary
实体类和数据库表结构:
// 实体类注解 (部分)
@Column(name = "order_no", unique = true, nullable = true, length = 50)
private String orderNo;
-- 数据库表结构 (部分)
create table consignment_summary (
-- ... 其他列 ...
order_no varchar(50) null,
constraint UK_catfmq4ovjdvqndwvh6r6jas1 unique (order_no) -- 问题根源!
);
原因很明显了:我的代码逻辑要求同一批次内的多条记录拥有相同的 orderNo
,而数据库层面却给 order_no
加了唯一约束!这两者是互相矛盾的。当 saveAll
尝试插入第二条具有相同 orderNo
的记录时,数据库就无情地拒绝了。❌
解决方案:调整约束 + 时间戳大法 ✨
既然 orderNo
的业务含义是批次号,允许多条记录共享同一个批次号,那么解决方案就很清晰了:
- 移除数据库唯一约束 🔧:让数据库允许
order_no
列存在重复值。 - 更新实体类注解 📝:保持代码与数据库结构一致。
- 坚持时间戳+随机数生成策略 ✅:继续使用方案三生成批次号,因为它简单且能满足批次间的唯一性。
Step 1: 移除数据库唯一约束
⚠️ 警告: 修改数据库结构前,请务必备份!
根据之前的表结构,执行以下 SQL 语句删除 order_no
上的唯一索引(即唯一约束):
ALTER TABLE consignment_summary
DROP INDEX UK_catfmq4ovjdvqndwvh6r6jas1;
Step 2: 更新实体类注解
修改 ConsignmentSummary.java
,移除 orderNo
字段上 @Column
注解的 unique = true
属性:
import javax.persistence.Column;
// ... 其他 import ...
public class ConsignmentSummary extends BaseEntity {
// ... 其他字段 ...
@ApiModelProperty(value = "单号", example = "CON20231027001")
// 移除了 unique = true
@Column(name = "order_no", nullable = true, length = 50)
private String orderNo;
// ... 其他字段 ...
}
Step 3: Controller 生成批次号逻辑 (最终版)
Controller 中的代码基本保持不变,核心逻辑就是为整个请求生成一个 batchOrderNo
,并应用到所有条目上:
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
@RestController
@RequestMapping("/api/consignmentSettlement")
public class ConsignmentSettlementController {
@Autowired
private ConsignmentSummaryService consignmentSummaryService;
@PostMapping("/saveConsignmentSummary")
@ApiOperation("保存寄售总表 (基于时间戳+随机数生成批次订单号)")
public BaseResult saveConsignmentSummary(
@ApiIgnore @SessionAttribute(Constants.ADMIN_ID) Integer adminId,
@RequestBody List<ConsignmentSummary> consignmentSummaries) {
try {
if (consignmentSummaries == null || consignmentSummaries.isEmpty()) {
return BaseResult.failure(400, "保存列表不能为空");
}
// ✨ 核心:为本次请求生成唯一的批次号 ✨
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
String randomPart = String.format("%04d", ThreadLocalRandom.current().nextInt(0, 10000));
String batchOrderNo = "CON" + timestamp + "-" + randomPart; // "CON" 是业务前缀
// 遍历列表,设置 adminId 和 统一的批次号
for (ConsignmentSummary summary : consignmentSummaries) {
summary.setAdminId(adminId);
summary.setOrderNo(batchOrderNo); // 应用同一个批次号
}
// 批量保存
consignmentSummaryService.saveAll(consignmentSummaries);
return BaseResult.success("保存成功,批次订单号:" + batchOrderNo);
} catch (Exception e) {
System.err.println("保存 ConsignmentSummary 列表时发生错误: " + e.getMessage());
e.printStackTrace();
return BaseResult.failure(500, "保存处理失败: " + e.getMessage());
}
}
// ... 其他方法 ...
}
现在,再次发送请求,包含多条记录的数组,saveAll
操作就能顺利执行了!🚀 同一批次的记录共享同一个 orderNo
,不同批次的 orderNo
也因时间戳和随机数而不同。
总结与流程回顾 📊
下面用表格总结一下整个过程:
方面 | 描述 |
---|---|
🎯 目标 | 为同一请求内的多条记录生成相同且唯一的批次 orderNo 。 |
😭 初始问题 | order_no 列存在数据库唯一约束,导致 saveAll 时报 Duplicate entry 错误。 |
🤔 原因分析 | 代码逻辑(共享批次号)与数据库约束(orderNo 唯一)冲突。 |
✅ 解决方案 | 1. 移除数据库唯一约束。 2. 更新实体类注解。 3. 采用时间戳+随机数生成批次号。 |
🔑 关键代码 | ALTER TABLE ... DROP INDEX ... (SQL) & Controller 中的批次号生成逻辑 (Java) |
🎉 最终结果 | 成功实现需求,同一批次记录共享 orderNo ,不同批次 orderNo 不同。 |
业务处理流程图 (Mermaid Flowchart) 🌊
请求处理时序图 (Mermaid Sequence Diagram) ⏳➡️
结语 👍
通过移除不必要的数据库唯一约束,并坚持使用“高精度时间戳+随机数”的策略,我们成功解决了批次订单号生成的问题。这个方法虽然简单,但在并发量不是特别极端,且不强求严格递增的场景下,是一个非常实用且易于实现的方案。
当然,对于需要严格递增或超高并发下绝对无冲突的场景,可以考虑使用数据库序列(如 PostgreSQL/Oracle 的 SEQUENCE
或 MySQL 的模拟序列表方案,见之前讨论)。选择哪种方案,最终还是要根据具体的业务需求和系统环境来决定。
希望这次分享对你有所帮助!如果你有更好的方法或者遇到了其他有趣的问题,欢迎在评论区交流!✨ Happy Coding! 🎉