HHH-18377 UUID Version 6 & UUID Version 7 implementations

This commit is contained in:
Čedomir Igaly 2024-07-26 13:12:31 +02:00 committed by Steve Ebersole
parent df405d37ab
commit 4e85302086
4 changed files with 285 additions and 2 deletions

View File

@ -9,6 +9,8 @@ import java.lang.annotation.Target;
import java.util.UUID;
import org.hibernate.Incubating;
import org.hibernate.id.uuid.UuidVersion6Strategy;
import org.hibernate.id.uuid.UuidVersion7Strategy;
import org.hibernate.id.uuid.UuidValueGenerator;
import static java.lang.annotation.ElementType.FIELD;
@ -52,7 +54,19 @@ public @interface UuidGenerator {
* @implNote Can be a bottleneck, since synchronization is used when
* incrementing an internal counter as part of the algorithm.
*/
TIME
TIME,
/**
* Use a time-based generation strategy consistent with RFC 4122
* version 6.
* @see UuidVersion6Strategy
*/
VERSION_6,
/**
* Use a time-based generation strategy consistent with RFC 4122
* version 7.
* @see UuidVersion7Strategy
*/
VERSION_7
}
/**

View File

@ -23,6 +23,8 @@ import org.hibernate.type.descriptor.java.UUIDJavaType.ValueTransformer;
import static org.hibernate.annotations.UuidGenerator.Style.AUTO;
import static org.hibernate.annotations.UuidGenerator.Style.TIME;
import static org.hibernate.annotations.UuidGenerator.Style.VERSION_6;
import static org.hibernate.annotations.UuidGenerator.Style.VERSION_7;
import static org.hibernate.generator.EventTypeSets.INSERT_ONLY;
import static org.hibernate.internal.util.ReflectHelper.getPropertyType;
@ -78,9 +80,15 @@ public class UuidGenerator implements BeforeExecutionGenerator {
}
return instantiateCustomGenerator( config.algorithm() );
}
else if ( config.style() == TIME ) {
if ( config.style() == TIME ) {
return new CustomVersionOneStrategy();
}
else if ( config.style() == VERSION_6 ) {
return UuidVersion6Strategy.INSTANCE;
}
else if ( config.style() == VERSION_7 ) {
return UuidVersion7Strategy.INSTANCE;
}
}
return StandardRandomStrategy.INSTANCE;

View File

@ -0,0 +1,130 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.id.uuid;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.UUIDGenerationStrategy;
/**
* Implements UUID Version 6 generation strategy as defined by the <a href="https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-6">RFC 9562</a>.
*
* <ul>
* <li>32 bits - the most significant 32 bits of the 60-bit starting timestamp.</li>
* <li>16 bits - the middle 16 bits of the 60-bit starting timestamp.</li>
* <li>4 bits - version field, set to 0b0110 (6).</li>
* <li>12 bits - the least significant 12 bits from the 60-bit starting timestamp.</li>
* <li>2 bits - variant field, set to 0b10.</li>
* <li>14 bits - the clock sequence, resets to 0 when timestamp changes. </li>
* <li>48 bits - pseudorandom data to provide uniqueness.</li>
* </ul>
*
* @author Cedomir Igaly
*/
public class UuidVersion6Strategy implements UUIDGenerationStrategy, UuidValueGenerator {
private static final Instant EPOCH_1582;
static {
EPOCH_1582 = LocalDate.of( 1582, 10, 15 )
.atStartOfDay( ZoneId.of( "UTC" ) )
.toInstant();
}
private static class Holder {
static final SecureRandom numberGenerator = new SecureRandom();
}
public static final UuidVersion6Strategy INSTANCE = new UuidVersion6Strategy();
private final Lock lock = new ReentrantLock( true );
private long currentTimestamp;
private final AtomicLong clockSequence = new AtomicLong( 0 );
public UuidVersion6Strategy() {
this( getCurrentTimestamp(), 0 );
}
public UuidVersion6Strategy(final long currentTimestamp, final long clockSequence) {
this.currentTimestamp = currentTimestamp;
this.clockSequence.set( clockSequence );
}
/**
* A variant 6
*/
@Override
public int getGeneratedVersion() {
// UUIDv6 is a field-compatible version of UUIDv1, reordered for improved DB locality
return 6;
}
/**
* Delegates to {@link #generateUuid}
*/
@Override
public UUID generateUUID(SharedSessionContractImplementor session) {
return generateUuid( session );
}
/**
* @param session session
*
* @return UUID version 6
* @see UuidValueGenerator#generateUuid(SharedSessionContractImplementor)
*/
@Override
public UUID generateUuid(SharedSessionContractImplementor session) {
final long currentTimestamp = getCurrentTimestamp();
return new UUID(
// MSB bits 0-47 - most significant 32 bits of the 60-bit starting timestamp
currentTimestamp << 4 & 0xFFFF_FFFF_FFFF_0000L
// MSB bits 48-51 - version = 6
| 0x6000L
// MSB bits 52-63 - least significant 12 bits from the 60-bit starting timestamp
| currentTimestamp & 0x0FFFL,
// LSB bits 0-1 - variant = 4
0x8000_0000_0000_0000L
// LSB bits 2-15 - clock sequence
| ( getSequence( currentTimestamp ) & 0x3FFFL ) << 48
// LSB bits 16-63 - pseudorandom data
| Holder.numberGenerator.nextLong() & 0xFFFF_FFFF_FFFFL
);
}
private long getSequence(final long currentTimestamp) {
lock.lock();
try {
if ( this.currentTimestamp > currentTimestamp ) {
this.currentTimestamp = currentTimestamp;
clockSequence.set( 0 );
}
}
finally {
lock.unlock();
}
return clockSequence.getAndIncrement();
}
private static long getCurrentTimestamp() {
final Duration duration = Duration.between( EPOCH_1582, Instant.now() );
return duration.toSeconds() * 10_000_000 + duration.toNanosPart() / 100;
}
}

View File

@ -0,0 +1,131 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.id.uuid;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.Instant;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.UUIDGenerationStrategy;
import static java.time.Instant.EPOCH;
import static java.time.temporal.ChronoUnit.MILLIS;
/**
* Implements UUID Version 7 generation strategy as defined by the <a href="https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-7">RFC 9562</a>.
*
* <ul>
* <li>48 bits - 48-bit big-endian unsigned number of the Unix Epoch timestamp in milliseconds.</li>
* <li>4 bits - version field, set to 0b0111 (7).</li>
* <li>
* 12 bits - sub-milliseconds part of timestamp (resolution approximately 1/4 of millisecond)
* to guarantee additional monotonicity.
* </li>
* <li>2 bits - variant field, set to 0b10.</li>
* <li>14 bits - counter to guarantee additional monotonicity, resets to 0 when timestamp changes. </li>
* <li>48 bits - pseudorandom data to provide uniqueness.</li>
* </ul>
*
* @author Cedomir Igaly
*/
public class UuidVersion7Strategy implements UUIDGenerationStrategy, UuidValueGenerator {
public static final UuidVersion7Strategy INSTANCE = new UuidVersion7Strategy();
private static class Holder {
static final SecureRandom numberGenerator = new SecureRandom();
}
private final Lock lock = new ReentrantLock( true );
private Duration currentTimestamp;
private final AtomicLong clockSequence;
public UuidVersion7Strategy() {
this( getCurrentTimestamp(), 0 );
}
public UuidVersion7Strategy(final Duration currentTimestamp, final long clockSequence) {
this.currentTimestamp = currentTimestamp;
this.clockSequence = new AtomicLong( clockSequence );
}
/**
* A variant 7
*/
@Override
public int getGeneratedVersion() {
/*
* UUIDv7 features a time-ordered value field derived from the widely implemented and well-
* known Unix Epoch timestamp source, the number of milliseconds since midnight 1 Jan 1970 UTC,
* leap seconds excluded.
*/
return 7;
}
/**
* Delegates to {@link #generateUuid}
*/
@Override
public UUID generateUUID(SharedSessionContractImplementor session) {
return generateUuid( session );
}
/**
* @param session session
*
* @return UUID version 7
* @see UuidValueGenerator#generateUuid(SharedSessionContractImplementor)
*/
@Override
public UUID generateUuid(SharedSessionContractImplementor session) {
final Duration currentTimestamp = getCurrentTimestamp();
final long seq = getSequence( currentTimestamp );
final long millis = currentTimestamp.getSeconds() * 1000 + currentTimestamp.getNano() / 1_000_000;
final long nanosPart = Math.round( ( currentTimestamp.getNano() % 1_000_000L ) * 0.004096 );
return new UUID(
// MSB bits 0-47 - 48-bit big-endian unsigned number of the Unix Epoch timestamp in milliseconds
millis << 16 & 0xFFFF_FFFF_FFFF_0000L
// MSB bits 48-51 - version = 7
| 0x7000L
// MSB bits 52-63 - sub-milliseconds part of timestamp
| nanosPart & 0xFFFL,
// LSB bits 0-1 - variant = 4
0x8000_0000_0000_0000L
// LSB bits 2-15 - counter
| ( seq & 0x3FFFL ) << 48
// LSB bits 16-63 - pseudorandom data
| Holder.numberGenerator.nextLong() & 0xFFFF_FFFF_FFFFL
);
}
private long getSequence(final Duration currentTimestamp) {
lock.lock();
try {
if ( !this.currentTimestamp.equals( currentTimestamp ) ) {
this.currentTimestamp = currentTimestamp;
clockSequence.set( 0 );
}
}
finally {
lock.unlock();
}
return clockSequence.getAndIncrement();
}
private static Duration getCurrentTimestamp() {
return Duration.between( EPOCH, Instant.now() ).truncatedTo( MILLIS );
}
}