方法定义:
/**
* 生成单号
*
* @param sessionName 数据库会话名称
* @param clazz 持久化映射类 类 类型
* @param fieldName 单号字段
* @param preCode 前缀
* @param dateFormatter 时间格式
* @param strongSerial 是否强连续(是:阻塞,否:非阻塞)
* @return 单号
*/
String generate(String sessionName, Class<? extends AbstractEntity> clazz, String fieldName, String preCode, String dateFormatter, ResetRule resetRule, Boolean strongSerial);
使用:
1.弱连续(不保证单号的连续性,非阻塞)
String code = IdGenerator.getInstance().generate("biscuits", Order.class, "code", "10011039", "yyMMdd", ResetRule.DAY, false);
2.强连续(保证单号的连续性,阻塞)
IdGenerator idGenerator = IdGenerator.getInstance();
try{
String preCode = "10011039";
String code = idGenerator.generate("biscuits", Order.class, "code", preCode, "yyMMdd", ResetRule.DAY, true);
// 执行业务代码
// ...
idGenerator.commit(preCode);
} catch(Exception e){
idGenerator.rollback(preCode);
}
弱连续非阻塞实现思路
- 使用 maxSnMap 存储每个单据对应的最序号
- 生成单号时先从 maxSnMap 中查找对应单据的最大单据序号
- 如果找不到则先给 当前单据 上双重锁查询数据库中的最大值
- 如果获取锁失败则等待重试
源码
package org.hv.biscuits.core.sn;
import org.hv.biscuits.core.session.ActiveSessionCenter;
import org.hv.biscuits.function.BiscuitsSupplier;
import org.hv.pocket.criteria.Restrictions;
import org.hv.pocket.exception.SessionException;
import org.hv.pocket.model.AbstractEntity;
import org.hv.pocket.session.Session;
import org.hv.pocket.session.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDate;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
/**
* 单号生成器
*
* @author wujianchuan 2020/11/2 17:32
*/
public class IdGenerator {
private static final Logger LOGGER = LoggerFactory.getLogger(IdGenerator.class);
private static final IdGenerator INSTANCE = new IdGenerator();
private final Map<String/* pre code */, AtomicLong/* max serial number */> maxSnMap = new HashMap<>(60);
private final Map<String/* pre code */, Long/* thread id */> lockMap = new HashMap<>(60);
private final Map<String/* pre code */, LocalDate/* latest date */> latestDateMap = new HashMap<>(60);
private static final int MAX_RETRY_TIMES = 6;
private IdGenerator() {
}
public static IdGenerator getInstance() {
return INSTANCE;
}
/**
* 生成单号
*
* @param sessionName 数据库会话名称
* @param clazz 持久化映射类 类 类型
* @param fieldName 单号字段
* @param preCode 前缀
* @param dateFormatter 时间格式
* @param resetRule 清零规则
* @param strongSerial 是否强连续(是:阻塞,否:非阻塞)
* @return 单号
*/
public String generate(String sessionName, Class<? extends AbstractEntity> clazz, String fieldName, String preCode, String dateFormatter, ResetRule resetRule, Boolean strongSerial) throws Exception {
LocalDate now = LocalDate.now();
String dateStr = now.format(DateTimeFormatter.ofPattern(dateFormatter));
BiscuitsSupplier<AtomicLong> maxSnSupplier = () -> {
Object todayMaxCode;
Session parentSession = ActiveSessionCenter.getCurrentSession();
if (parentSession == null) {
Session newSession = SessionFactory.getSession(sessionName);
if (newSession == null) {
throw new SessionException("请正确开启数据库会话");
}
newSession.open();
todayMaxCode = newSession.createCriteria(clazz).add(Restrictions.like(fieldName, preCode + dateStr + "_____")).max(fieldName);
newSession.close();
} else {
todayMaxCode = parentSession.createCriteria(clazz).add(Restrictions.like(fieldName, preCode + dateStr + "_____")).max(fieldName);
}
latestDateMap.put(preCode, now);
return todayMaxCode == null ? new AtomicLong(0) : new AtomicLong(Long.parseLong(todayMaxCode.toString().replace(preCode + dateStr, "")));
};
return (strongSerial == null || strongSerial) ? this.generateStrongSerial(preCode, dateStr, resetRule, maxSnSupplier) : this.generateWeakSerial(preCode, dateStr, resetRule, maxSnSupplier);
}
/**
* 单号提交
*
* @param preCode 前缀
*/
public void commit(String preCode) {
boolean lock = lockMap.get(preCode) != null;
if (lock) {
lockMap.remove(preCode);
}
}
/**
* 单号回滚
*
* @param preCode 前缀
*/
public void rollback(String preCode) {
boolean lock = lockMap.get(preCode) != null;
if (lock) {
maxSnMap.get(preCode).decrementAndGet();
lockMap.remove(preCode);
}
}
/**
* 生成强连续单号生成
*
* @param preCode 前缀
* @param dateStr 日期格式
* @param resetRule 清零规则
* @param maxSnSupplier 获取最大序号
* @return 单号
*/
private String generateStrongSerial(String preCode, String dateStr, ResetRule resetRule, BiscuitsSupplier<AtomicLong> maxSnSupplier) throws Exception {
// NOTE: 获取锁(首次获取||多次获取);使用 synchronized (preCode) 会大量消耗内存故使用 Map。
boolean lock = lockMap.putIfAbsent(preCode, Thread.currentThread().getId()) == null || lockMap.get(preCode).equals(Thread.currentThread().getId());
if (lock) {
// NOTE: 先从堆中获取当太难最大序列号如果没有再从数据库查询。
AtomicLong maxSn = this.maxSnMap.get(preCode);
if (maxSn == null) {
maxSn = maxSnSupplier.get();
this.maxSnMap.put(preCode, maxSn);
} else {
LocalDate now = LocalDate.now();
LocalDate latestDate = latestDateMap.get(preCode);
switch (resetRule) {
case MONTH:
now = LocalDate.of(now.getYear(), now.getMonth(), 0);
latestDate = LocalDate.of(latestDate.getYear(), latestDate.getMonth(), 0);
break;
case YEAR:
now = LocalDate.of(now.getYear(), Month.JANUARY, 0);
latestDate = LocalDate.of(latestDate.getYear(), Month.JANUARY, 0);
break;
default:
throw new IllegalArgumentException(String.format("不支持的规则 - %s", resetRule));
}
if (now.isAfter(latestDate)) {
maxSn = new AtomicLong(0);
latestDateMap.put(preCode, LocalDate.now());
}
}
return preCode + dateStr + String.format("%05d", maxSn.incrementAndGet());
} else {
long sleepMillis = 2;
for (int i = 1; i < MAX_RETRY_TIMES; i++) {
Thread.sleep(sleepMillis);
if (lockMap.putIfAbsent(preCode, Thread.currentThread().getId()) == null) {
return this.generateStrongSerial(preCode, dateStr, resetRule, maxSnSupplier);
}
sleepMillis = sleepMillis * 2;
}
throw new TimeoutException(String.format("重试次数超过%s次", MAX_RETRY_TIMES));
}
}
/**
* 生成弱连续单号生成
*
* @param preCode 前缀
* @param dateStr 日期格式
* @param resetRule 清零规则
* @param maxSnSupplier 获取最大序号
* @return 单号
*/
private String generateWeakSerial(String preCode, String dateStr, ResetRule resetRule, BiscuitsSupplier<AtomicLong> maxSnSupplier) throws Exception {
AtomicLong maxSn = this.maxSnMap.get(preCode);
if (maxSn == null) {
// NOTE: 获取锁 使用 synchronized (preCode) 会大量消耗内存故使用 Map。
boolean lock = lockMap.putIfAbsent(preCode, Thread.currentThread().getId()) == null;
if (lock) {
maxSn = this.maxSnMap.get(preCode);
if (maxSn == null) {
maxSn = maxSnSupplier.get();
this.maxSnMap.put(preCode, maxSn);
}
lockMap.remove(preCode);
return preCode + dateStr + String.format("%05d", maxSn.incrementAndGet());
} else {
long sleepMillis = 2;
for (int i = 1; i < MAX_RETRY_TIMES; i++) {
Thread.sleep(sleepMillis);
if (this.maxSnMap.get(preCode) != null) {
return this.generateWeakSerial(preCode, dateStr, resetRule, maxSnSupplier);
}
sleepMillis = sleepMillis * 2;
}
throw new TimeoutException(String.format("重试次数超过%s次", MAX_RETRY_TIMES));
}
}
LocalDate now = LocalDate.now();
LocalDate latestDate = latestDateMap.get(preCode);
switch (resetRule) {
case MONTH:
now = LocalDate.of(now.getYear(), now.getMonth(), 0);
latestDate = LocalDate.of(latestDate.getYear(), latestDate.getMonth(), 0);
break;
case YEAR:
now = LocalDate.of(now.getYear(), Month.JANUARY, 0);
latestDate = LocalDate.of(latestDate.getYear(), Month.JANUARY, 0);
break;
default:
throw new IllegalArgumentException(String.format("不支持的规则 - %s", resetRule));
}
if (now.isAfter(latestDate)) {
if (!maxSn.compareAndSet(maxSn.get(), 0)) {
return this.generateWeakSerial(preCode, dateStr, resetRule, maxSnSupplier);
}
latestDateMap.put(preCode, LocalDate.now());
}
return preCode + dateStr + String.format("%05d", maxSn.incrementAndGet());
}
}
package org.hv.biscuits.core.sn;
/**
* 单号清零规则
*
* @author wujianchuan 2020/11/8 13:53
*/
public enum ResetRule {
/**
* 一年清一次
*/
YEAR,
/**
* 一个月清一次
*/
MONTH,
/**
* 一天清一次
*/
DAY;
}