在开发过程中,集成了mybatis-plus,数据库在设计过程中,ID主键并没有使用自增的方式来生成,而是采用了它默认的ID生成规则,由于考虑到ID索引查询的问题,因此ID字段设置为数字类型,考虑到业务可能出现的增长,因此ID设置为bigint类型,而Java对应的属性为Long类型。
但是在实际使用中发现,默认生成的ID基本都是20位长度,由于前端js 最多只能识别16位ID,超过16位ID就会失去精度,起初考虑全局过滤,在返回给前端时将ID转换成string,但是觉得这种方式不太好。所以考虑采用自定义ID生成规则的方式来生成ID,经过几次调试,最终确认采用时间戳+AtomicInteger 自增数字的方式来生成ID,但是在测试的时候发现,在普通的一万多并发的时候,ID重复居然超过100,而且在线上进行数据批量导入的时候,就会出现ID重复的情况,因此经过不断改良,最终修改优化如下:
public class CustomIdGenerator implements IdentifierGenerator{
private static AtomicInteger atomicInteger = new AtomicInteger(0);
// 由于JS最多识别16位长度,因此这里控制长度不超过16位,这里控制为16位
private static int ID_LENGTH = 16;
@Override
public Number nextId(Object entity) {
// 生成最大4位随技术
int i2 = ThreadLocalRandom.current().nextInt(9999);
String timeStr = String.valueOf(System.currentTimeMillis());
// 取出时间串前面相同的部分
timeStr = timeStr.substring(5);
// 递增生成最大9999的递增ID
if (atomicInteger.get() == 9999) {
atomicInteger.set(0);
}
int i1 = atomicInteger.getAndIncrement();
String id = timeStr.concat(String.valueOf(i2)).concat(i1+"");
// 严格控制ID长度,如果过长 从最前面截取
if (id.length() > ID_LENGTH) {
// 计算多了多少位
int surplusLenth = id.length() - ID_LENGTH;
id = id.substring(surplusLenth);
}
return Long.valueOf(id);
}
public static void main(String[] args) {
CustomIdGenerator customIdGenerator=new CustomIdGenerator();
customIdGenerator.nextId(null).longValue();
final CountDownLatch latch=new CountDownLatch(1000000);
Set<Long> set=new ConcurrentHashSet<>();
for(int i=0;i<1000000;i++){
Thread thread=new Thread(){
@Override
public void run() {
for(int j=0;j<1;j++){
Number number = customIdGenerator.nextId(new Object());
long l = number.longValue();
boolean add = set.add(l);
if(!add) {
System.out.println("重复"+l);
}
}
latch.countDown();
}
};
thread.start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
采用截取时间戳 + 随机数 + 自增数的方式,来生成ID,经测试在百万并发的情况下,可以做到完全不重复。
这里为什么要截取时间呢?因为时间的前面部分,基本上都是一样,而且还要占位,在空间有限的情况下,就会大大增加重复率,因此最小限度的去除了前面的相同部分,使得生成的ID 看起来不会乱,后面拼接自增数字,也使得生成的ID看起来更有规律性。
以上便是本人的ID生成方案,各位如有更好的方案可以留言交流。