HHH-18377 Holding state in immutable object to allow atomic calculation and change
This commit is contained in:
parent
2e42db82dd
commit
6daec2e410
|
@ -5,14 +5,11 @@
|
||||||
package org.hibernate.id.uuid;
|
package org.hibernate.id.uuid;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
import org.hibernate.Internal;
|
import org.hibernate.Internal;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
@ -31,34 +28,61 @@ import org.hibernate.id.UUIDGenerationStrategy;
|
||||||
* <li>48 bits - pseudorandom data to provide uniqueness.</li>
|
* <li>48 bits - pseudorandom data to provide uniqueness.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @apiNote This strategy is field-compatible with Version 1, with the time bits reordered for improved DB locality.
|
|
||||||
*
|
|
||||||
* @author Cedomir Igaly
|
* @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 class UuidVersion6Strategy implements UUIDGenerationStrategy, UuidValueGenerator {
|
||||||
public static final UuidVersion6Strategy INSTANCE = new UuidVersion6Strategy();
|
public static final UuidVersion6Strategy INSTANCE = new UuidVersion6Strategy();
|
||||||
|
|
||||||
private static class Holder {
|
private static class Holder {
|
||||||
static final SecureRandom numberGenerator = new SecureRandom();
|
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" ) )
|
.atStartOfDay( ZoneId.of( "UTC" ) )
|
||||||
.toInstant();
|
.toInstant().getEpochSecond();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Lock lock = new ReentrantLock( true );
|
private record State(long timestamp, int sequence) {
|
||||||
private final AtomicLong clockSequence = new AtomicLong( 0 );
|
public State getNextState() {
|
||||||
private long currentTimestamp;
|
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<State> lastState;
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
public UuidVersion6Strategy() {
|
public UuidVersion6Strategy() {
|
||||||
this( getCurrentTimestamp(), 0 );
|
this( Long.MIN_VALUE, Integer.MIN_VALUE );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
public UuidVersion6Strategy(final long currentTimestamp, final long clockSequence) {
|
public UuidVersion6Strategy(final long initialTimestamp, final int initialSequence) {
|
||||||
this.currentTimestamp = currentTimestamp;
|
this.lastState = new AtomicReference<>( new State( initialTimestamp, initialSequence ) );
|
||||||
this.clockSequence.set( clockSequence );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -70,47 +94,31 @@ public class UuidVersion6Strategy implements UUIDGenerationStrategy, UuidValueGe
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UUID generateUUID(SharedSessionContractImplementor session) {
|
public UUID generateUUID(final SharedSessionContractImplementor session) {
|
||||||
return generateUuid( session );
|
return generateUuid( session );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UUID generateUuid(SharedSessionContractImplementor session) {
|
public UUID generateUuid(final SharedSessionContractImplementor session) {
|
||||||
final long currentTimestamp = getCurrentTimestamp();
|
final State state = lastState.updateAndGet( State::getNextState );
|
||||||
|
|
||||||
return new UUID(
|
return new UUID(
|
||||||
// MSB bits 0-47 - most significant 32 bits of the 60-bit starting timestamp
|
// 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
|
// MSB bits 48-51 - version = 6
|
||||||
| 0x6000L
|
| 0x6000L
|
||||||
// MSB bits 52-63 - least significant 12 bits from the 60-bit starting timestamp
|
// 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
|
// LSB bits 0-1 - variant = 4
|
||||||
0x8000_0000_0000_0000L
|
0x8000_0000_0000_0000L
|
||||||
// LSB bits 2-15 - clock sequence
|
// LSB bits 2-15 - clock sequence
|
||||||
| ( getSequence( currentTimestamp ) & 0x3FFFL ) << 48
|
| ( state.sequence & 0x3FFFL ) << 48
|
||||||
// LSB bits 16-63 - pseudorandom data
|
// LSB bits 16-63 - pseudorandom data, least significant bit of the first octet is set to 1
|
||||||
| Holder.numberGenerator.nextLong() & 0xFFFF_FFFF_FFFFL
|
| randomNode()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static long randomNode() {
|
||||||
private long getSequence(final long currentTimestamp) {
|
return Holder.numberGenerator.nextLong( 0x1_0000_0000_0000L ) | 0x1000_0000_0000L;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,7 @@ package org.hibernate.id.uuid;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
import org.hibernate.Internal;
|
import org.hibernate.Internal;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
@ -32,33 +30,63 @@ import static java.time.temporal.ChronoUnit.MILLIS;
|
||||||
* <li>48 bits - pseudorandom data to provide uniqueness.</li>
|
* <li>48 bits - pseudorandom data to provide uniqueness.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
|
* @author Cedomir Igaly
|
||||||
* @apiNote Version 7 features a time-ordered value field derived from the widely implemented and
|
* @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,
|
* well-known Unix Epoch timestamp source, the number of milliseconds since midnight 1 Jan 1970 UTC,
|
||||||
* leap seconds excluded.
|
* leap seconds excluded.
|
||||||
*
|
|
||||||
* @author Cedomir Igaly
|
|
||||||
*/
|
*/
|
||||||
public class UuidVersion7Strategy implements UUIDGenerationStrategy, UuidValueGenerator {
|
public class UuidVersion7Strategy implements UUIDGenerationStrategy, UuidValueGenerator {
|
||||||
|
|
||||||
public static final UuidVersion7Strategy INSTANCE = new UuidVersion7Strategy();
|
public static final UuidVersion7Strategy INSTANCE = new UuidVersion7Strategy();
|
||||||
|
|
||||||
private static class Holder {
|
private static class Holder {
|
||||||
static final SecureRandom numberGenerator = new SecureRandom();
|
private static final SecureRandom numberGenerator = new SecureRandom();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Lock lock = new ReentrantLock( true );
|
public record State(Instant timestamp, int sequence) {
|
||||||
private final AtomicLong clockSequence;
|
public long millis() {
|
||||||
private Instant currentTimestamp;
|
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<State> lastState;
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
public UuidVersion7Strategy() {
|
public UuidVersion7Strategy() {
|
||||||
this( Instant.now(), 0 );
|
this( Instant.EPOCH, Integer.MIN_VALUE );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
public UuidVersion7Strategy(final Instant currentTimestamp, final long clockSequence) {
|
public UuidVersion7Strategy(final Instant initialTimestamp, final int initialSequence) {
|
||||||
this.currentTimestamp = currentTimestamp;
|
this.lastState = new AtomicReference<>( new State( initialTimestamp, initialSequence ) );
|
||||||
this.clockSequence = new AtomicLong( clockSequence );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -70,46 +98,31 @@ public class UuidVersion7Strategy implements UUIDGenerationStrategy, UuidValueGe
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UUID generateUUID(SharedSessionContractImplementor session) {
|
public UUID generateUUID(final SharedSessionContractImplementor session) {
|
||||||
return generateUuid( session );
|
return generateUuid( session );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UUID generateUuid(SharedSessionContractImplementor session) {
|
public UUID generateUuid(final SharedSessionContractImplementor session) {
|
||||||
final Instant currentTimestamp = Instant.now();
|
final State state = lastState.updateAndGet( State::getNextState );
|
||||||
|
|
||||||
final long seq = getSequence( currentTimestamp );
|
|
||||||
|
|
||||||
final long millis = currentTimestamp.toEpochMilli();
|
|
||||||
final long nanosPart = (long) ( ( currentTimestamp.getNano() % 1_000_000L ) * 0.004096 );
|
|
||||||
|
|
||||||
return new UUID(
|
return new UUID(
|
||||||
// MSB bits 0-47 - 48-bit big-endian unsigned number of the Unix Epoch timestamp in milliseconds
|
// 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
|
// MSB bits 48-51 - version = 7
|
||||||
| 0x7000L
|
| 0x7000L
|
||||||
// MSB bits 52-63 - sub-milliseconds part of timestamp
|
// MSB bits 52-63 - sub-milliseconds part of timestamp
|
||||||
| nanosPart & 0xFFFL,
|
| state.nanos() & 0xFFFL,
|
||||||
// LSB bits 0-1 - variant = 4
|
// LSB bits 0-1 - variant = 4
|
||||||
0x8000_0000_0000_0000L
|
0x8000_0000_0000_0000L
|
||||||
// LSB bits 2-15 - counter
|
// LSB bits 2-15 - counter
|
||||||
| ( seq & 0x3FFFL ) << 48
|
| ( state.sequence & 0x3FFFL ) << 48
|
||||||
// LSB bits 16-63 - pseudorandom data
|
// LSB bits 16-63 - pseudorandom data
|
||||||
| Holder.numberGenerator.nextLong() & 0xFFFF_FFFF_FFFFL
|
| randomNode()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getSequence(final Instant currentTimestamp) {
|
private static long randomNode() {
|
||||||
lock.lock();
|
return Holder.numberGenerator.nextLong( 0x1_0000_0000_0000L ) | 0x1000_0000_0000L;
|
||||||
try {
|
|
||||||
if ( this.currentTimestamp.toEpochMilli() < currentTimestamp.toEpochMilli() ) {
|
|
||||||
this.currentTimestamp = currentTimestamp.truncatedTo( MILLIS );
|
|
||||||
clockSequence.updateAndGet( l -> l & 0x1FFFL );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
return clockSequence.getAndIncrement();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue