From eabd2212c75c9b6ecae6d6a93cc88b462841c32e Mon Sep 17 00:00:00 2001 From: dh-free Date: Tue, 23 Jul 2024 11:56:36 +0800 Subject: [PATCH] =?UTF-8?q?ID=E4=B8=BB=E9=94=AE=E7=94=9F=E6=88=90=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E6=96=B0=E5=A2=9EULID=E7=AE=97=E6=B3=95=20=E5=AF=B9?= =?UTF-8?q?=E6=AF=94UUID=EF=BC=8CULID=E6=9B=B4=E5=8A=A0=E7=AE=80=E6=B4=81?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E5=8A=A0=E5=AE=89=E5=85=A8=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E5=8A=A0=E5=BF=AB=E9=80=9F=EF=BC=8C=E6=9C=80=E5=85=B3=E9=94=AE?= =?UTF-8?q?=E7=9A=84=E4=BA=8B=E5=8F=AF=E6=8E=92=E5=BA=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/keygen/KeyGeneratorFactory.java | 5 +- .../core/keygen/KeyGenerators.java | 5 + .../core/keygen/impl/ULIDKeyGenerator.java | 120 ++++++++++++++++++ .../java/com/mybatisflex/core/row/RowKey.java | 5 + .../com/mybatisflex/coretest/IdGenTest.java | 12 ++ 5 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/impl/ULIDKeyGenerator.java diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/KeyGeneratorFactory.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/KeyGeneratorFactory.java index 895f3822..fd0d480b 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/KeyGeneratorFactory.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/KeyGeneratorFactory.java @@ -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()); + } @@ -49,7 +52,7 @@ public class KeyGeneratorFactory { * @return 主键生成器 */ public static IKeyGenerator getKeyGenerator(String name) { - if (StringUtil.isBlank(name)){ + if (StringUtil.isBlank(name)) { throw FlexExceptions.wrap(LocalizedFormats.KEY_GENERATOR_BLANK); } return KEY_GENERATOR_MAP.get(name.trim()); diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/KeyGenerators.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/KeyGenerators.java index 90eee69b..062bc6eb 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/KeyGenerators.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/KeyGenerators.java @@ -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"; } diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/impl/ULIDKeyGenerator.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/impl/ULIDKeyGenerator.java new file mode 100644 index 00000000..2bbb511c --- /dev/null +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/impl/ULIDKeyGenerator.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2022-2025, Mybatis-Flex (fuhai999@gmail.com). + *

+ * 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 + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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的优势在于可排序性和性能。 + *

+ * 特点: + * 1、保证 id 生成的顺序为时间顺序,越往后生成的 ID 值越大; + * 2、可以按照生成的时间进行排序,而不需要全局协调; + * 3、生成速度快; + *

+ *

参考:Sequence + */ +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 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!"); + } + } +} diff --git a/mybatis-flex-core/src/main/java/com/mybatisflex/core/row/RowKey.java b/mybatis-flex-core/src/main/java/com/mybatisflex/core/row/RowKey.java index ecac3c0d..9bc90c2e 100644 --- a/mybatis-flex-core/src/main/java/com/mybatisflex/core/row/RowKey.java +++ b/mybatis-flex-core/src/main/java/com/mybatisflex/core/row/RowKey.java @@ -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); diff --git a/mybatis-flex-core/src/test/java/com/mybatisflex/coretest/IdGenTest.java b/mybatis-flex-core/src/test/java/com/mybatisflex/coretest/IdGenTest.java index 256ccebd..4af39608 100644 --- a/mybatis-flex-core/src/test/java/com/mybatisflex/coretest/IdGenTest.java +++ b/mybatis-flex-core/src/test/java/com/mybatisflex/coretest/IdGenTest.java @@ -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); + } }