HHH-18377 UUID Version 6 & UUID Version 7 implementations
This commit is contained in:
parent
df405d37ab
commit
4e85302086
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue