mirror of
https://gitee.com/mybatis-flex/mybatis-flex.git
synced 2025-12-07 00:58:24 +08:00
ID主键生成策略新增ULID算法
对比UUID,ULID更加简洁,更加安全,更加快速,最关键的事可排序。
This commit is contained in:
parent
d5d8c86e1c
commit
eabd2212c7
@ -19,6 +19,7 @@ import com.mybatisflex.core.exception.FlexExceptions;
|
||||
import com.mybatisflex.core.exception.locale.LocalizedFormats;
|
||||
import com.mybatisflex.core.keygen.impl.FlexIDKeyGenerator;
|
||||
import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator;
|
||||
import com.mybatisflex.core.keygen.impl.ULIDKeyGenerator;
|
||||
import com.mybatisflex.core.keygen.impl.UUIDKeyGenerator;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
|
||||
@ -39,6 +40,8 @@ public class KeyGeneratorFactory {
|
||||
register(KeyGenerators.uuid, new UUIDKeyGenerator());
|
||||
register(KeyGenerators.flexId, new FlexIDKeyGenerator());
|
||||
register(KeyGenerators.snowFlakeId, new SnowFlakeIDKeyGenerator());
|
||||
register(KeyGenerators.ulid, new ULIDKeyGenerator());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -38,4 +38,9 @@ public class KeyGenerators {
|
||||
*/
|
||||
public static final String snowFlakeId = "snowFlakeId";
|
||||
|
||||
/**
|
||||
* ulid 主键生成器
|
||||
* {@link com.mybatisflex.core.keygen.impl.ULIDKeyGenerator}
|
||||
*/
|
||||
public static final String ulid = "ulid";
|
||||
}
|
||||
|
||||
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (c) 2022-2025, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.keygen.impl;
|
||||
|
||||
import com.mybatisflex.core.keygen.IKeyGenerator;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* ULID: 对比UUID的优势在于可排序性和性能。
|
||||
* <p>
|
||||
* 特点:
|
||||
* 1、保证 id 生成的顺序为时间顺序,越往后生成的 ID 值越大;
|
||||
* 2、可以按照生成的时间进行排序,而不需要全局协调;
|
||||
* 3、生成速度快;
|
||||
* <p>
|
||||
* <p>参考:<a href="https://github.com/ulid/spec">Sequence</a>
|
||||
*/
|
||||
public class ULIDKeyGenerator implements IKeyGenerator {
|
||||
|
||||
private static final char[] ENCODING_CHARS = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K',
|
||||
'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X',
|
||||
'Y', 'Z'
|
||||
};
|
||||
|
||||
private static final long TIMESTAMP_OVERFLOW_MASK = 0xFFFF_0000_0000_0000L;
|
||||
|
||||
private static final ThreadLocal<StringBuilder> THREAD_LOCAL_BUILDER =
|
||||
ThreadLocal.withInitial(() -> new StringBuilder(26));
|
||||
|
||||
private long lastTimestamp = 0;
|
||||
|
||||
private long lastRandom = 0;
|
||||
|
||||
@Override
|
||||
public Object generate(Object entity, String keyColumn) {
|
||||
return nextId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成一个 ULID
|
||||
*
|
||||
* @return ULID
|
||||
*/
|
||||
public String nextId() {
|
||||
return generateULID(System.currentTimeMillis()).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成一个严格单调的 ULID
|
||||
*
|
||||
* @return ULID
|
||||
*/
|
||||
public synchronized String nextMonotonicId() {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
if (timestamp > lastTimestamp) {
|
||||
lastTimestamp = timestamp;
|
||||
lastRandom = ThreadLocalRandom.current().nextLong();
|
||||
} else {
|
||||
lastRandom++;
|
||||
if (lastRandom == 0) {
|
||||
timestamp = waitNextMillis(lastTimestamp);
|
||||
lastTimestamp = timestamp;
|
||||
lastRandom = ThreadLocalRandom.current().nextLong();
|
||||
}
|
||||
}
|
||||
return generateULID(lastTimestamp, lastRandom).toLowerCase();
|
||||
}
|
||||
|
||||
private String generateULID(long timestamp) {
|
||||
return generateULID(timestamp, ThreadLocalRandom.current().nextLong());
|
||||
}
|
||||
|
||||
private String generateULID(long timestamp, long random) {
|
||||
checkTimestamp(timestamp);
|
||||
StringBuilder builder = THREAD_LOCAL_BUILDER.get();
|
||||
builder.setLength(0);
|
||||
|
||||
appendCrockford(builder, timestamp, 10);
|
||||
appendCrockford(builder, random, 16);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private long waitNextMillis(long lastTimestamp) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
while (timestamp <= lastTimestamp) {
|
||||
timestamp = System.currentTimeMillis();
|
||||
}
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
private static void appendCrockford(StringBuilder builder, long value, int count) {
|
||||
for (int i = (count - 1) * 5; i >= 0; i -= 5) {
|
||||
int index = (int) ((value >>> i) & 0x1F);
|
||||
builder.append(ENCODING_CHARS[index]);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkTimestamp(long timestamp) {
|
||||
if ((timestamp & TIMESTAMP_OVERFLOW_MASK) != 0) {
|
||||
throw new IllegalArgumentException("ULID does not support timestamps after +10889-08-02T05:31:50.655Z!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -46,6 +46,11 @@ public class RowKey implements Serializable {
|
||||
*/
|
||||
public static final RowKey SNOW_FLAKE_ID = RowKey.of("id", KeyType.Generator, KeyGenerators.snowFlakeId, true);
|
||||
|
||||
/**
|
||||
* ulid
|
||||
*/
|
||||
public static final RowKey ULID = RowKey.of("id", KeyType.Generator, KeyGenerators.ulid, true);
|
||||
|
||||
|
||||
public static RowKey of(String keyColumn) {
|
||||
SqlUtil.keepColumnSafely(keyColumn);
|
||||
|
||||
@ -22,6 +22,7 @@ import com.mybatisflex.core.keygen.KeyGenerators;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.LongStream;
|
||||
|
||||
/**
|
||||
@ -54,4 +55,15 @@ public class IdGenTest {
|
||||
Assert.assertEquals(size, LongStream.of(ids).distinct().count());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ULID() {
|
||||
int size = 100_0000;
|
||||
String[] ids = new String[size];
|
||||
IKeyGenerator keyGenerator = KeyGeneratorFactory.getKeyGenerator(KeyGenerators.ulid);
|
||||
for (int i = 0; i < size; i++) {
|
||||
ids[i] = (String) keyGenerator.generate(null, null);
|
||||
}
|
||||
long distinctCount = Arrays.stream(ids).distinct().count();
|
||||
Assert.assertEquals(size, distinctCount);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user