通用业务编号生成工具类(MyBatis-Plus + Spring Boot)详解 + 3种调用方式
在企业应用开发中,我们经常需要生成类似 BZ -240704-0001
这种“业务编号”,它通常具有以下特点:
-
前缀:代表业务类型,如 BZ 表示包装
-
日期:年月日格式,通常为
yyMMdd
-
序列号:当天内递增,如
0001
、0002
…
本文介绍一个支持 自动去重、递增编号、通用字段提取 的工具类,并提供了三种调用方式,适配不同场景。
📦 工具类源码:
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 体系,简洁灵活。