JAVA借助Mysql如何实现一个递增的固定位数,需要拼接特殊标识(考虑并发不能重复和性能好)

背景

有时候我需要生成一个递增的数,并且里面增加一些标识的需求,如
GHM11540000021563, GHM11540000021564, GHM11540000021565, GHM11540000021566
这个数有3部分组成

  1. GHM:固定标识
  2. 1:机构的类型
  3. 1540:机构的ID
  4. 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个值是什么,这样就可以缓解数据库压力
这种方案的优点我认为有以下几个

  1. 性能好
  2. 并发不会重复
  3. 只需借助数据库就可以,无需借助如redis之类的中间件
  4. 有持久化机制,程序重启不丢失当前index
  5. 灵活,可以自己定义数据的模式结构

当你看懂了上面的原理之后就可以自定义数据的结构了,甚至你可以实现根据日期的不同每日从1递增,只需要设置上面写死的Date date = DateUtils.parseDate("2099-12-31", DateUtils.YYYY_MM_DD);改成当前日期即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值