diff --git a/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidVersion6Strategy.java b/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidVersion6Strategy.java index e773dbb658..e943304264 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidVersion6Strategy.java +++ b/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidVersion6Strategy.java @@ -5,14 +5,11 @@ 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 java.util.concurrent.atomic.AtomicReference; import org.hibernate.Internal; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -31,34 +28,61 @@ import org.hibernate.id.UUIDGenerationStrategy; *
  • 48 bits - pseudorandom data to provide uniqueness.
  • * * - * @apiNote This strategy is field-compatible with Version 1, with the time bits reordered for improved DB locality. - * * @author Cedomir Igaly + * @apiNote This strategy is field-compatible with Version 1, with the time bits reordered for improved DB locality. */ public class UuidVersion6Strategy implements UUIDGenerationStrategy, UuidValueGenerator { public static final UuidVersion6Strategy INSTANCE = new UuidVersion6Strategy(); private static class Holder { static final SecureRandom numberGenerator = new SecureRandom(); - static final Instant EPOCH_1582 = LocalDate.of( 1582, 10, 15 ) + static final long EPOCH_1582_SECONDS = LocalDate.of( 1582, 10, 15 ) .atStartOfDay( ZoneId.of( "UTC" ) ) - .toInstant(); + .toInstant().getEpochSecond(); + } - private final Lock lock = new ReentrantLock( true ); - private final AtomicLong clockSequence = new AtomicLong( 0 ); - private long currentTimestamp; + private record State(long timestamp, int sequence) { + public State getNextState() { + final long now = instantToTimestamp(); + if ( this.timestamp < now ) { + return new State( + now, + randomSequence() + ); + } + else if ( sequence == 0x3FFF ) { + return new State( + this.timestamp + 1, + randomSequence() + ); + } + else { + return new State( timestamp, sequence + 1 ); + } + } + private static int randomSequence() { + return Holder.numberGenerator.nextInt( 1 << 14 ); + } + + private static long instantToTimestamp() { + final Instant instant = Instant.now(); + final long seconds = instant.getEpochSecond() - Holder.EPOCH_1582_SECONDS; + return seconds * 10_000_000 + instant.getNano() / 100; + } + } + + private final AtomicReference lastState; @Internal public UuidVersion6Strategy() { - this( getCurrentTimestamp(), 0 ); + this( Long.MIN_VALUE, Integer.MIN_VALUE ); } @Internal - public UuidVersion6Strategy(final long currentTimestamp, final long clockSequence) { - this.currentTimestamp = currentTimestamp; - this.clockSequence.set( clockSequence ); + public UuidVersion6Strategy(final long initialTimestamp, final int initialSequence) { + this.lastState = new AtomicReference<>( new State( initialTimestamp, initialSequence ) ); } /** @@ -70,47 +94,31 @@ public class UuidVersion6Strategy implements UUIDGenerationStrategy, UuidValueGe } @Override - public UUID generateUUID(SharedSessionContractImplementor session) { + public UUID generateUUID(final SharedSessionContractImplementor session) { return generateUuid( session ); } @Override - public UUID generateUuid(SharedSessionContractImplementor session) { - final long currentTimestamp = getCurrentTimestamp(); + public UUID generateUuid(final SharedSessionContractImplementor session) { + final State state = lastState.updateAndGet( State::getNextState ); return new UUID( // MSB bits 0-47 - most significant 32 bits of the 60-bit starting timestamp - currentTimestamp << 4 & 0xFFFF_FFFF_FFFF_0000L + state.timestamp << 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, + | state.timestamp & 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 + | ( state.sequence & 0x3FFFL ) << 48 + // LSB bits 16-63 - pseudorandom data, least significant bit of the first octet is set to 1 + | randomNode() ); } - - private long getSequence(final long currentTimestamp) { - lock.lock(); - try { - if ( this.currentTimestamp < currentTimestamp ) { - this.currentTimestamp = currentTimestamp; - clockSequence.updateAndGet( l -> l & 0x1FFFL ); - } - } - finally { - lock.unlock(); - } - return clockSequence.getAndIncrement(); - } - - private static long getCurrentTimestamp() { - final Duration duration = Duration.between( Holder.EPOCH_1582, Instant.now() ); - return duration.toSeconds() * 10_000_000 + duration.toNanosPart() / 100; + private static long randomNode() { + return Holder.numberGenerator.nextLong( 0x1_0000_0000_0000L ) | 0x1000_0000_0000L; } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidVersion7Strategy.java b/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidVersion7Strategy.java index 2f67d3ba70..a3fbdac288 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidVersion7Strategy.java +++ b/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidVersion7Strategy.java @@ -7,9 +7,7 @@ package org.hibernate.id.uuid; import java.security.SecureRandom; 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 java.util.concurrent.atomic.AtomicReference; import org.hibernate.Internal; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -32,33 +30,63 @@ import static java.time.temporal.ChronoUnit.MILLIS; *
  • 48 bits - pseudorandom data to provide uniqueness.
  • * * + * @author Cedomir Igaly * @apiNote Version 7 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. - * - * @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 static final SecureRandom numberGenerator = new SecureRandom(); + } - private final Lock lock = new ReentrantLock( true ); - private final AtomicLong clockSequence; - private Instant currentTimestamp; + public record State(Instant timestamp, int sequence) { + public long millis() { + return timestamp.toEpochMilli(); + } + + public long nanos() { + return (long) ( ( timestamp.getNano() % 1_000_000L ) * 0.004096 ); + } + + public State getNextState() { + final Instant now = Instant.now(); + if ( timestamp.toEpochMilli() < now.toEpochMilli() ) { + return new State( + now.truncatedTo( MILLIS ), + randomSequence() + ); + } + else if ( sequence == 0x3FFF ) { + return new State( + timestamp.plusMillis( 1 ), + Holder.numberGenerator.nextInt( 1 << 14 ) + ); + } + else { + return new State( timestamp, sequence + 1 ); + } + } + + private static int randomSequence() { + return Holder.numberGenerator.nextInt( 1 << 14 ); + } + } + + private final AtomicReference lastState; @Internal public UuidVersion7Strategy() { - this( Instant.now(), 0 ); + this( Instant.EPOCH, Integer.MIN_VALUE ); } @Internal - public UuidVersion7Strategy(final Instant currentTimestamp, final long clockSequence) { - this.currentTimestamp = currentTimestamp; - this.clockSequence = new AtomicLong( clockSequence ); + public UuidVersion7Strategy(final Instant initialTimestamp, final int initialSequence) { + this.lastState = new AtomicReference<>( new State( initialTimestamp, initialSequence ) ); } /** @@ -70,46 +98,31 @@ public class UuidVersion7Strategy implements UUIDGenerationStrategy, UuidValueGe } @Override - public UUID generateUUID(SharedSessionContractImplementor session) { + public UUID generateUUID(final SharedSessionContractImplementor session) { return generateUuid( session ); } @Override - public UUID generateUuid(SharedSessionContractImplementor session) { - final Instant currentTimestamp = Instant.now(); - - final long seq = getSequence( currentTimestamp ); - - final long millis = currentTimestamp.toEpochMilli(); - final long nanosPart = (long) ( ( currentTimestamp.getNano() % 1_000_000L ) * 0.004096 ); + public UUID generateUuid(final SharedSessionContractImplementor session) { + final State state = lastState.updateAndGet( State::getNextState ); 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 + state.millis() << 16 & 0xFFFF_FFFF_FFFF_0000L // MSB bits 48-51 - version = 7 | 0x7000L // MSB bits 52-63 - sub-milliseconds part of timestamp - | nanosPart & 0xFFFL, + | state.nanos() & 0xFFFL, // LSB bits 0-1 - variant = 4 0x8000_0000_0000_0000L // LSB bits 2-15 - counter - | ( seq & 0x3FFFL ) << 48 + | ( state.sequence & 0x3FFFL ) << 48 // LSB bits 16-63 - pseudorandom data - | Holder.numberGenerator.nextLong() & 0xFFFF_FFFF_FFFFL + | randomNode() ); } - private long getSequence(final Instant currentTimestamp) { - lock.lock(); - try { - if ( this.currentTimestamp.toEpochMilli() < currentTimestamp.toEpochMilli() ) { - this.currentTimestamp = currentTimestamp.truncatedTo( MILLIS ); - clockSequence.updateAndGet( l -> l & 0x1FFFL ); - } - } - finally { - lock.unlock(); - } - return clockSequence.getAndIncrement(); + private static long randomNode() { + return Holder.numberGenerator.nextLong( 0x1_0000_0000_0000L ) | 0x1000_0000_0000L; } }