背景
有时候我需要生成一个递增的数,并且里面增加一些标识的需求,如
GHM11540000021563, GHM11540000021564, GHM11540000021565, GHM11540000021566
这个数有3部分组成
- GHM:固定标识
- 1:机构的类型
- 1540:机构的ID
- 000021563:递增数
怎么实现
借助Mysql,Mysql有一个函数LAST_INSERT_ID
,这个函数会返回调用这个函数最后一次设置的值。
select LAST_INSERT_ID(); -- 输出0
select LAST_INSERT_ID(11); -- 输出11
select LAST_INSERT_ID(); -- 输出11
我们可以在每次获取这个值的时候用当前值加1
好了,我们现在实现了一个递增序列
建一张表,保存最近的最大值。
create table voucher_sn
(
system_type tinyint not null,
belong int not null,
voucher_type smallint not null,
sn_date date not null,
max_value smallint not null,
primary key (system_type, belong, voucher_type, sn_date)
);
JAVA端关键代码
MaxSnMapper
这个方法是关键,借助了函数LAST_INSERT_ID
,下面代码构建的结果核心sql为
insert into voucher_sn(system_type, belong,voucher_type,sn_date, max_value) values(xxx, xxx,xxx,date, LAST_INSERT_ID(size))
on duplicate key update next = LAST_INSERT_ID(next + size);
@InsertProvider(type = SqlProvider.class, method = "incMaxSn")
@SelectKey(statement = "select LAST_INSERT_ID()", keyProperty = "maxValue", before = false, resultType = long.class)
int incMaxSn(MaxSnParam param);
static class SqlProvider {
public String incMaxSn(MaxSnParam param) {
Map<String, Object> keys = param.keys;
SQL sql = new SQL();
sql.INSERT_INTO(param.tableName);
keys.forEach((k, v) -> sql.VALUES(k, String.format("#{keys.%s}", k)));
sql.VALUES("max_value", "LAST_INSERT_ID(#{size})");
return sql.toString() + " ON DUPLICATE KEY UPDATE max_value=LAST_INSERT_ID(max_value + #{size})";
}
}
MaxSnService
@Transactional(rollbackFor = Exception.class)
public long getMaxSn(String tableName, Map<String, Object> keys, int count) {
return maxSnMapper.getMaxSn(tableName, keys, count);
}
VoucherNumberService
private static final SimpleDateFormat yyyyMMdd = new SimpleDateFormat("yyyy-MM-dd");
private static final String voucherSnTable = "voucher_sn";
private static final String SN_FORMAT = "%s-%s-%04d";
public static final String BARCODE_FORMAT = "%s%s%s%09d";
private final MaxSnService snService;
/**
* 获取一个递增的编号,会拼接机构ID,同机构ID同机构类型的编号会递增
*
* @param voucherType
* @param systemType
* @param orgId
* @param format
* @return
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
public List<String> getMaxSnAppendOrgId(VoucherType voucherType, SystemType systemType, long orgId, String format, int count) {
Utils.assertFalse(count > 10000, "单次最多生成10000条记录");
Date date = DateUtils.parseDate("2099-12-31", DateUtils.YYYY_MM_DD);
Map<String, Object> keys = new HashMap<>();
keys.put("system_type", systemType);
keys.put("belong", orgId);
keys.put("voucher_type", voucherType);
keys.put("sn_date", date);
long startInclusive = snService.getMaxSn(voucherSnTable, keys, count);
List<String> list = IntStream.range(0, count).mapToObj(i -> buildSnByFormat(format, voucherType, systemType, orgId, startInclusive + i)).collect(Collectors.toList());
return list;
}
VoucherType
public enum VoucherType implements BaseIntEnum {
LABEL_BARCODE(56, "GHM", "供货商供货码(条码)"),
}
调用
public List<String> genBarcode(SystemType systemType, long orgId, int cound) {
Utils.assertFalse(cound > 10000, "单次最多生成10000条标签");
return voucherNumberService.getMaxSnAppendOrgId(VoucherType.LABEL_BARCODE, systemType, orgId, VoucherNumberService.BARCODE_FORMAT, cound);
}
// 调用
List<String> list = genBarcode(orgType, 1540, 10000);
System.out.println(list);
输出如下
可以看到耗时只有几百毫秒
数据库记录如下
select * from voucher_sn where voucher_type=56;
总结
实现这个的关键在于使用LAST_INSERT_ID
函数,这个函数会返回调用这个函数最后一次设置的值。那我要获取10000个即使用next = LAST_INSERT_ID(next + 10000)
, 然后在代码内存里面去分配这10000个值是什么,这样就可以缓解数据库压力
这种方案的优点我认为有以下几个
- 性能好
- 并发不会重复
- 只需借助数据库就可以,无需借助如redis之类的中间件
- 有持久化机制,程序重启不丢失当前index
- 灵活,可以自己定义数据的模式结构
当你看懂了上面的原理之后就可以自定义数据的结构了,甚至你可以实现根据日期的不同每日从1递增,只需要设置上面写死的Date date = DateUtils.parseDate("2099-12-31", DateUtils.YYYY_MM_DD);
改成当前日期即可