在企業應用開發中,我們經常需要生成類似 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 體系,簡潔靈活。