当前位置: 首页 > news >正文

通用业务编号生成工具类(MyBatis-Plus + Spring Boot)详解 + 3种调用方式

在企业应用开发中,我们经常需要生成类似 BZ -240704-0001 这种“业务编号”,它通常具有以下特点:

  • 前缀:代表业务类型,如 BZ 表示包装

  • 日期:年月日格式,通常为 yyMMdd

  • 序列号:当天内递增,如 00010002

本文介绍一个支持 自动去重、递增编号、通用字段提取 的工具类,并提供了三种调用方式,适配不同场景。

📦 工具类源码:

package com.kakarote.pm.common;import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.service.IService;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.stereotype.Component;import java.beans.Introspector;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;/*** 生成唯一业务编号,格式如 FK-240703-0001,支持重试避免重复*/
@Component
public class DocumentCodeGeneratorUtil {/*** 自动生成唯一编号(支持重试,防止重复)* @param prefix        编号前缀,如 "FK"* @param service       MyBatis-Plus 的 Service 对象,用于执行数据库查询* @param columnGetter  要生成编号的字段引用,如 Entity::getDocumentCode* @param <T>           实体类* @return              唯一编号,例如 FK-240704-0001*/private static final int MAX_RETRY = 5;public <T> String generateUniqueCode(String prefix, IService<T> service, SFunction<T, String> columnGetter) {int retry = 0;while (retry < MAX_RETRY) {String code = generateCodeByDatePrefix(prefix, service, columnGetter);int count = service.lambdaQuery().eq(columnGetter, code).count();if (count == 0) {return code;}retry++;}throw new RuntimeException("编号生成失败:连续5次生成重复编号,请稍后重试!");}private <T> String generateCodeByDatePrefix(String prefix, IService<T> service, SFunction<T, String> columnGetter) {String currentDate = new SimpleDateFormat("yyMMdd").format(new Date());String prefixWithDate = prefix + "-" + currentDate + "-";List<T> list = service.lambdaQuery().likeRight(columnGetter, prefixWithDate).orderByDesc(columnGetter).last("limit 1").list();int nextSeq = 1;if (!list.isEmpty()) {try {T entity = list.get(0);String fieldName = getFieldName(columnGetter);String maxCode = (String) PropertyUtils.getProperty(entity, fieldName);String[] parts = maxCode.split("-");if (parts.length == 3) {nextSeq = Integer.parseInt(parts[2]) + 1;}} catch (Exception e) {throw new RuntimeException("反射获取字段值失败", e);}}return prefixWithDate + String.format("%04d", nextSeq);}/*** 通过 SerializedLambda 获取字段名, 例:User::getName => name*/private <T> String getFieldName(SFunction<T, ?> fn) throws Exception {Method writeReplace = fn.getClass().getDeclaredMethod("writeReplace");writeReplace.setAccessible(true);SerializedLambda serializedLambda = (SerializedLambda) writeReplace.invoke(fn);String implMethodName = serializedLambda.getImplMethodName();if (implMethodName.startsWith("get")) {return Introspector.decapitalize(implMethodName.substring(3));} else if (implMethodName.startsWith("is")) {return Introspector.decapitalize(implMethodName.substring(2));}return implMethodName;}
}

 使用方式(3种场景) 

场景 1:在当前 ServiceImpl 内部调用(推荐)

@Autowired

private DocumentCodeGeneratorUtil documentCodeGeneratorUtil;

@Override

public void savePack() {    

String code = documentCodeGeneratorUtil.generateUniqueCode("BZ", this, PmPack::getDocumentCode);    

pmPack.setDocumentCode(code);    

save(pmPack);

}

说明:this 是当前类,已继承 BaseServiceImpl,本身就是 IService<PmPack>
例如:

如果没有就采用方式二的本身的service调用就行

场景 2:在其他类(如 Controller)中调用

@Autowired
private PmPackService pmPackService;

@Autowired
private DocumentCodeGeneratorUtil documentCodeGeneratorUtil;

public void createPackFromController() {
    String code = documentCodeGeneratorUtil.generateUniqueCode("BZ", pmPackService, PmPack::getDocumentCode);
}

场景 3:使用 Spring 上下文动态获取(非推荐,仅限无法注入时)

PmPackService service = SpringContextHolder.getBean(PmPackService.class);

String code = documentCodeGeneratorUtil.generateUniqueCode("BZ", service, PmPack::getDocumentCode);

 你需要实现 SpringContextHolder
 

@Component
public class SpringContextHolder implements ApplicationContextAware {
    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext ctx) {
        context = ctx;
    }

    public static <T> T getBean(Class<T> clazz) {
        return context.getBean(clazz);
    }
}
 

🔧 所需依赖(pom.xml)

// MyBatis Plus 核心依赖
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.5</version>
</dependency>

// BeanUtils(反射读取字段值)

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency> 

示例输出

假设今天是 2024年7月4日,编号前缀为 FK,数据库已有最大编号为:

BZ-240704-0001  以此类推第二天重置
如果重复,会自动重试最多 5 次。

例如:

优点总结

功能支持情况
日期前缀
自增长序列
多表复用
自动重试去重
支持 Lambda 字段提取
支持多个业务类型前缀

结语:

 这个工具类已经在多个模块(如:付款利息、包装、检验)中实际应用,稳定可靠,适配 MyBatis-Plus 体系,简洁灵活。

http://www.dtcms.com/a/266412.html

相关文章:

  • 基于 ETL 工具实现人大金仓数据库的数据迁移与整合实操指南
  • 设计模式之代理模式--数据库查询代理和调用日志记录
  • Unity-MMORPG内容笔记-其三
  • FastAPI 返回 422 Unprocessable Entity
  • 【Linux操作系统 | 第十篇】Linux组管理实践 ---土匪和警察的游戏
  • 【代码复现】YOLO11复现全流程+自定义数据集训练测试
  • 双系统如何做接口认证-V1
  • RabbitMQ 高级特性之重试机制
  • 大流量业务云主机选型:AWS、Oracle、DigitalOcean、Linode、阿里云深度对比
  • 硬件嵌入式学习路线大总结(一):C语言与linux。内功心法——从入门到精通,彻底打通你的任督二脉!
  • 服务器 - - QPS与TPS介绍
  • (2)手摸手-学习 Vue3 之 变量声明【ref 和 reactive】
  • Node.js核心API(fs篇)
  • 状态机管家:MeScroll 的交互秩序维护
  • Qt创建线程的方法
  • Winscope在aosp 13/14/15版本的使用总结
  • AI Agent在企业管理中的落地路径:从概念到实践的转型指南
  • 面试版-前端开发核心知识
  • HTML表格导出为Excel文件的实现方案
  • Excel 实现进制转换 Excel十进制转二进制 Excel 中文转unicode Excel实现Unicode转中文
  • 本地部署Dify并结合ollama大语言模型工具搭建自己的AI知识库
  • 面向开发者的API平台设计与选型建议【附源码示例】
  • flutter封装vlcplayer的控制器
  • 如何使用DeepSeek一键生成系统架构图?
  • 如何将大型视频文件从 iPhone 传输到 PC
  • 怎么更改cursor字体大小
  • 10分钟搭建 PHP 开发环境教程
  • VSCode 安装使用教程
  • SQL Server 进阶语法实战:从动态透视到存储过程的深度应用(第四课)
  • 高功率的照明LN2系列助力电子元件薄膜片检测