diff --git a/src/main/java/com/eactive/apim/portal/jpa/UuidV7Generator.java b/src/main/java/com/eactive/apim/portal/jpa/UuidV7Generator.java new file mode 100644 index 0000000..ab8f858 --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/jpa/UuidV7Generator.java @@ -0,0 +1,66 @@ +package com.eactive.apim.portal.jpa; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.id.IdentifierGenerator; + +import java.io.Serializable; +import java.security.SecureRandom; + +/** + * UUID version 7 (time-ordered, RFC 9562) 생성기. + * + *

상위 48비트에 Unix epoch 밀리초 타임스탬프를 두어 생성 시각 순으로 정렬되므로, + * 무작위 v4 대비 인덱스(B-Tree) 삽입 지역성이 좋아 PK 인덱스 단편화를 줄인다. + * canonical 36자 문자열로 반환하여 기존 {@code VARCHAR2(36)} 컬럼과 호환된다.

+ * + *

레이아웃(128비트): unix_ts_ms(48) | ver=0b0111(4) | rand_a(12) | var=0b10(2) | rand_b(62)

+ * + *

Java 8 호환(java.util.UUID + SecureRandom). Hibernate {@link IdentifierGenerator} 구현이며 + * 엔티티에서 {@code @GenericGenerator(strategy = "...UuidV7Generator")} 로 지정한다.

+ */ +public class UuidV7Generator implements IdentifierGenerator { + + private static final SecureRandom RANDOM = new SecureRandom(); + + @Override + public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException { + return generateString(); + } + + /** UUID v7 을 canonical 36자 문자열로 생성한다. */ + public static String generateString() { + long timestamp = System.currentTimeMillis(); + + byte[] value = new byte[16]; + + // 상위 48비트: unix epoch millis (big-endian) + value[0] = (byte) ((timestamp >>> 40) & 0xFF); + value[1] = (byte) ((timestamp >>> 32) & 0xFF); + value[2] = (byte) ((timestamp >>> 24) & 0xFF); + value[3] = (byte) ((timestamp >>> 16) & 0xFF); + value[4] = (byte) ((timestamp >>> 8) & 0xFF); + value[5] = (byte) (timestamp & 0xFF); + + // 나머지 10바이트는 난수로 채운다. + byte[] rand = new byte[10]; + RANDOM.nextBytes(rand); + System.arraycopy(rand, 0, value, 6, 10); + + // version 7: value[6] 상위 니블을 0111 로 설정 + value[6] = (byte) ((value[6] & 0x0F) | 0x70); + // variant 10xx: value[8] 상위 2비트를 10 으로 설정 + value[8] = (byte) ((value[8] & 0x3F) | 0x80); + + long msb = 0L; + for (int i = 0; i < 8; i++) { + msb = (msb << 8) | (value[i] & 0xFFL); + } + long lsb = 0L; + for (int i = 8; i < 16; i++) { + lsb = (lsb << 8) | (value[i] & 0xFFL); + } + + return new java.util.UUID(msb, lsb).toString(); + } +}