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;
}
}