每日从0开始生成递增流水号

news/2025/3/6 20:04:12/文章来源:https://www.cnblogs.com/lhcc924/p/18756316

在实际的业务开发中,我们经常会遇到需要生成具有唯一性的业务编号的场景,例如订单编号、留言编号等。这些编号通常是由日期部分和一个递增的序列号部分组成,以确保在同一天内的编号是唯一的,并且能够反映出业务的增长趋势。本文将详细介绍如何在Java中实现这样一个每天随业务量递增的流水号生成器。

需求分析

假设我们需要为一个订单系统生成唯一的订单编号,编号格式要求为yyyyMMdd加上一个四位或者五位的递增流水号,例如:

  • 当天第一条订单编号为202410120001
  • 第二条订单编号为202410120002
  • 当订单数量达到万级以上时,例如第10000条订单,编号应变为2024101210000
  • 当流水号超过五位数时(例如第100000条),计数器重置为1,从下一条留言开始重新计数,如202410120001

解决方案

可以利用对数据库的读写控制,在数据库中存储当日订单量。每次取值时先更新再取值,写和读作为原子操作。这里采用纯代码方式解决,以免对数据库进行频繁读写。
我们可以使用Java中的AtomicInteger类来保证计数器的原子性递增操作,并通过单例模式来确保生成器在整个应用程序中只有一个实例,从而避免多线程环境下的计数器冲突。同时,我们还需要在每天的开始时对计数器进行初始化,以确保编号的日期部分是准确的。

实现步骤

1. 引入依赖

在实现之前,我们需要确保项目中已经引入了必要的依赖项。这里我们使用了Hutool工具包中的DateUtil和StrUtil类来简化日期和字符串的操作,因此需要在项目的pom.xml文件中添加以下依赖:

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.11</version>
</dependency>

2. 创建MessageIdGenerator工具类

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import java.util.concurrent.atomic.AtomicInteger;public class MessageIdGenerator {private AtomicInteger sequence = new AtomicInteger(0);private static volatile MessageIdGenerator instance = null;private MessageIdGenerator() {}public static MessageIdGenerator getInstance() {if (instance == null) {synchronized (MessageIdGenerator.class) {if (instance == null) {instance = new MessageIdGenerator();}}}return instance;}public synchronized String generateId() {DateTime now = DateUtil.date();String todayDate = DateUtil.format(now, "yyyyMMdd");int count = sequence.incrementAndGet();// 如果计数超过99999,重置计数if (count > 99999) {sequence.set(1);}String sequenceStr = StrUtil.padPre(String.valueOf(count), count > 9999 ? 5 : 4, '0');return todayDate + sequenceStr;}// 初始化当天的计数public void initSequence(MessageRecordMapper messageRecordMapper) {String todayDate = DateUtil.format(DateUtil.date(), "yyyyMMdd");QueryWrapper<MessageRecord> queryWrapper = new QueryWrapper<>();queryWrapper.like("message_id", todayDate);int count = messageRecordMapper.selectCount(queryWrapper);sequence.set(count);}
}

3. 在Service中使用生成器

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.stereotype.Service;@Service
public class MessageRecordService {private final MessageRecordMapper messageRecordMapper;private final MessageIdGenerator messageIdGenerator = MessageIdGenerator.getInstance();public MessageRecordService(MessageRecordMapper messageRecordMapper) {this.messageRecordMapper = messageRecordMapper;// 初始化当天的计数messageIdGenerator.initSequence(messageRecordMapper);}public void addMessage(MessageRecord messageRecord) {String messageId = messageIdGenerator.generateId();messageRecord.setMessageId(messageId);messageRecordMapper.insert(messageRecord);}
}

代码详解

1.单例模式

在MessageIdGenerator类中,我们使用了双重检查锁(Double-Checked Locking)的懒汉式单例模式来确保生成器只有一个实例。这种方式既保证了延迟初始化,又能够在多线程环境下安全地创建唯一的实例:

private static volatile MessageIdGenerator instance = null;public static MessageIdGenerator getInstance() {if (instance == null) {synchronized (MessageIdGenerator.class) {if (instance == null) {instance = new MessageIdGenerator();}}}return instance;
}

2. 计数器初始化

在生成器的initSequence方法中,我们根据当天的日期查询数据库中已有的记录数量,并设置初始计数。这一步确保了在应用程序重启后,计数器能够从当天的最新计数继续递增,而不会重复生成编号。

public void initSequence(MessageRecordMapper messageRecordMapper) {String todayDate = DateUtil.format(DateUtil.date(), "yyyyMMdd");QueryWrapper<MessageRecord> queryWrapper = new QueryWrapper<>();queryWrapper.like("message_id", todayDate);int count = messageRecordMapper.selectCount(queryWrapper);sequence.set(count);
}

3. 编号生成

在generateId方法中,我们首先获取当前日期格式化为yyyyMMdd,然后递增计数器,并根据计数器的值来判断是否需要补全为五位数字。使用synchronized关键字保证了在多线程环境下生成编号的唯一性和正确性。

public synchronized String generateId() {DateTime now = DateUtil.date();String todayDate = DateUtil.format(now, "yyyyMMdd");int count = sequence.incrementAndGet();// 如果计数超过99999,重置计数if (count > 99999) {sequence.set(1);}String sequenceStr = StrUtil.padPre(String.valueOf(count), count > 9999 ? 5 : 4, '0');return todayDate + sequenceStr;
}

4.测试验证

为了验证生成器的正确性和线程安全性,我们可以编写一个简单的测试用例:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class MessageRecordTest {@Autowiredprivate MessageRecordService messageRecordService;@Testvoid testAddMessage() {MessageRecord messageRecord = new MessageRecord();messageRecord.setContent("测试留言内容");messageRecord.setUserId(1L);messageRecordService.addMessage(messageRecord);// 可以多次调用addMessage方法来模拟多条留言的添加}
}

总结

通过上述实现,我们成功地创建了一个能够生成每天随业务量递增的流水号的生成器。在实际应用中,可以根据具体需求对生成器进行进一步的优化和扩展,例如增加对不同业务类型的编号生成支持,或者将生成器封装为一个通用的工具类,供多个模块使用。

希望这篇技术博客对你有所帮助!如果你有任何问题或建议,欢迎在评论区留言。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/894784.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言小记

int a =10; int b=3; int c= pow(10,3); //表示10的三次方unsigned 表示不用补码表示 //数字的输入,%d 包括 char,short,int%u unsigned%ld long long%lu unsigned long long输入 float 是 %f double 是 %lf shuchu dou shi %f保留小数的话是大于5才入,小于等于5舍去强…

系统流程图联系

练习题一:图书馆借阅管理系统流程图绘制 背景说明:在学校图书馆借阅管理系统中,学生借阅图书需要经过一系列流程。首先,学生携带校园卡前往借阅处,工作人员通过刷卡设备读取学生信息,系统验证学生身份是否有效。若身份无效,系统提示原因(如校园卡过期、欠费等)。若身份…

推荐4本专著《AI芯片开发核心技术详解》、《智能汽车传感器:原理设计应用》、《TVM编译器原理与实践》、《LLVM编译器原理与实践》书,非常感谢

4本书推荐《AI芯片开发核心技术详解》、《智能汽车传感器:原理设计应用》、《TVM编译器原理与实践》、《LLVM编译器原理与实践》由清华大学出版社资深编辑赵佳霓老师策划编辑的新书《AI芯片开发核心技术详解》已经出版,京东、淘宝天猫、当当等网上,相应陆陆续续可以购买。该…

SAS 9.4软件下载与安装教程

1、安装包 扫描下方二维码关注「软知社」,后台回复【043】三位数字即可免费获取分享链接,无广告拒绝套路;2、安装教程双击setup.exe安装,弹窗安装对话框简体中文,点击确定默认选择,点击下一步指定SAS安装主目录,选择C盘之外磁盘,点击下一步选择第二个,安装SAS Foundat…

MyBatis与其使用方法讲解

ORM在讲解Mybatis之前,我们需了解一个概念ORM(Object-Relational Mapping)对象关系映射,其是数据库与Java对象进行映射的一个技术.通过使用ORM,我们可以不用编写负责的Sql语句,而是通过操作对象来实现增删改查操作缺优分析优点提高开发效率,减少代码的重复性和维护成本 增加代码…

系统流程图

1.图书馆借阅管理系统流程图: 背景说明:在学校图书馆借阅管理系统中,学生借阅图书需要经过一系列流程。首先,学生携带校园卡前往借阅处,工作人员通过刷卡设备读取学生信息,系统验证学生身份是否有效。若身份无效,系统提示原因(如校园卡过期、欠费等)。若身份有效,学生…