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.exception.locale.LocalizedFormats;
|
||||||
import com.mybatisflex.core.keygen.impl.FlexIDKeyGenerator;
|
import com.mybatisflex.core.keygen.impl.FlexIDKeyGenerator;
|
||||||
import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator;
|
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.keygen.impl.UUIDKeyGenerator;
|
||||||
import com.mybatisflex.core.util.StringUtil;
|
import com.mybatisflex.core.util.StringUtil;
|
||||||
|
|
||||||
@ -39,6 +40,8 @@ public class KeyGeneratorFactory {
|
|||||||
register(KeyGenerators.uuid, new UUIDKeyGenerator());
|
register(KeyGenerators.uuid, new UUIDKeyGenerator());
|
||||||
register(KeyGenerators.flexId, new FlexIDKeyGenerator());
|
register(KeyGenerators.flexId, new FlexIDKeyGenerator());
|
||||||
register(KeyGenerators.snowFlakeId, new SnowFlakeIDKeyGenerator());
|
register(KeyGenerators.snowFlakeId, new SnowFlakeIDKeyGenerator());
|
||||||
|
register(KeyGenerators.ulid, new ULIDKeyGenerator());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -49,7 +52,7 @@ public class KeyGeneratorFactory {
|
|||||||
* @return 主键生成器
|
* @return 主键生成器
|
||||||
*/
|
*/
|
||||||
public static IKeyGenerator getKeyGenerator(String name) {
|
public static IKeyGenerator getKeyGenerator(String name) {
|
||||||
if (StringUtil.isBlank(name)){
|
if (StringUtil.isBlank(name)) {
|
||||||
throw FlexExceptions.wrap(LocalizedFormats.KEY_GENERATOR_BLANK);
|
throw FlexExceptions.wrap(LocalizedFormats.KEY_GENERATOR_BLANK);
|
||||||
}
|
}
|
||||||
return KEY_GENERATOR_MAP.get(name.trim());
|
return KEY_GENERATOR_MAP.get(name.trim());
|
||||||
|
|||||||
@ -38,4 +38,9 @@ public class KeyGenerators {
|
|||||||
*/
|
*/
|
||||||
public static final String snowFlakeId = "snowFlakeId";
|
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);
|
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) {
|
public static RowKey of(String keyColumn) {
|
||||||
SqlUtil.keepColumnSafely(keyColumn);
|
SqlUtil.keepColumnSafely(keyColumn);
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import com.mybatisflex.core.keygen.KeyGenerators;
|
|||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.stream.LongStream;
|
import java.util.stream.LongStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,4 +55,15 @@ public class IdGenTest {
|
|||||||
Assert.assertEquals(size, LongStream.of(ids).distinct().count());
|
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