55package org .hibernate .id .uuid ;
66
77import java .security .SecureRandom ;
8- import java .time .Duration ;
98import java .time .Instant ;
109import java .time .LocalDate ;
1110import java .time .ZoneId ;
1211import java .util .UUID ;
13- import java .util .concurrent .atomic .AtomicLong ;
14- import java .util .concurrent .locks .Lock ;
15- import java .util .concurrent .locks .ReentrantLock ;
12+ import java .util .concurrent .atomic .AtomicReference ;
1613
1714import org .hibernate .Internal ;
1815import org .hibernate .engine .spi .SharedSessionContractImplementor ;
3128 * <li>48 bits - pseudorandom data to provide uniqueness.</li>
3229 * </ul>
3330 *
34- * @apiNote This strategy is field-compatible with Version 1, with the time bits reordered for improved DB locality.
35- *
3631 * @author Cedomir Igaly
32+ * @apiNote This strategy is field-compatible with Version 1, with the time bits reordered for improved DB locality.
3733 */
3834public class UuidVersion6Strategy implements UUIDGenerationStrategy , UuidValueGenerator {
3935 public static final UuidVersion6Strategy INSTANCE = new UuidVersion6Strategy ();
4036
4137 private static class Holder {
4238 static final SecureRandom numberGenerator = new SecureRandom ();
43- static final Instant EPOCH_1582 = LocalDate .of ( 1582 , 10 , 15 )
39+ static final long EPOCH_1582_SECONDS = LocalDate .of ( 1582 , 10 , 15 )
4440 .atStartOfDay ( ZoneId .of ( "UTC" ) )
45- .toInstant ();
41+ .toInstant ().getEpochSecond ();
42+
4643 }
4744
48- private final Lock lock = new ReentrantLock ( true );
49- private final AtomicLong clockSequence = new AtomicLong ( 0 );
50- private long currentTimestamp ;
45+ private record State (long timestamp , int sequence ) {
46+ public State getNextState () {
47+ final long now = instantToTimestamp ();
48+ if ( this .timestamp < now ) {
49+ return new State (
50+ now ,
51+ randomSequence ()
52+ );
53+ }
54+ else if ( sequence == 0x3FFF ) {
55+ return new State (
56+ this .timestamp + 1 ,
57+ randomSequence ()
58+ );
59+ }
60+ else {
61+ return new State ( timestamp , sequence + 1 );
62+ }
63+ }
5164
65+ private static int randomSequence () {
66+ return Holder .numberGenerator .nextInt ( 1 << 14 );
67+ }
68+
69+ private static long instantToTimestamp () {
70+ final Instant instant = Instant .now ();
71+ final long seconds = instant .getEpochSecond () - Holder .EPOCH_1582_SECONDS ;
72+ return seconds * 10_000_000 + instant .getNano () / 100 ;
73+ }
74+ }
75+
76+ private final AtomicReference <State > lastState ;
5277
5378 @ Internal
5479 public UuidVersion6Strategy () {
55- this ( getCurrentTimestamp (), 0 );
80+ this ( Long . MIN_VALUE , Integer . MIN_VALUE );
5681 }
5782
5883 @ Internal
59- public UuidVersion6Strategy (final long currentTimestamp , final long clockSequence ) {
60- this .currentTimestamp = currentTimestamp ;
61- this .clockSequence .set ( clockSequence );
84+ public UuidVersion6Strategy (final long initialTimestamp , final int initialSequence ) {
85+ this .lastState = new AtomicReference <>( new State ( initialTimestamp , initialSequence ) );
6286 }
6387
6488 /**
@@ -70,47 +94,31 @@ public int getGeneratedVersion() {
7094 }
7195
7296 @ Override
73- public UUID generateUUID (SharedSessionContractImplementor session ) {
97+ public UUID generateUUID (final SharedSessionContractImplementor session ) {
7498 return generateUuid ( session );
7599 }
76100
77101 @ Override
78- public UUID generateUuid (SharedSessionContractImplementor session ) {
79- final long currentTimestamp = getCurrentTimestamp ( );
102+ public UUID generateUuid (final SharedSessionContractImplementor session ) {
103+ final State state = lastState . updateAndGet ( State :: getNextState );
80104
81105 return new UUID (
82106 // MSB bits 0-47 - most significant 32 bits of the 60-bit starting timestamp
83- currentTimestamp << 4 & 0xFFFF_FFFF_FFFF_0000L
107+ state . timestamp << 4 & 0xFFFF_FFFF_FFFF_0000L
84108 // MSB bits 48-51 - version = 6
85109 | 0x6000L
86110 // MSB bits 52-63 - least significant 12 bits from the 60-bit starting timestamp
87- | currentTimestamp & 0x0FFFL ,
111+ | state . timestamp & 0x0FFFL ,
88112 // LSB bits 0-1 - variant = 4
89113 0x8000_0000_0000_0000L
90114 // LSB bits 2-15 - clock sequence
91- | ( getSequence ( currentTimestamp ) & 0x3FFFL ) << 48
92- // LSB bits 16-63 - pseudorandom data
93- | Holder . numberGenerator . nextLong () & 0xFFFF_FFFF_FFFFL
115+ | ( state . sequence & 0x3FFFL ) << 48
116+ // LSB bits 16-63 - pseudorandom data, least significant bit of the first octet is set to 1
117+ | randomNode ()
94118 );
95119 }
96120
97-
98- private long getSequence (final long currentTimestamp ) {
99- lock .lock ();
100- try {
101- if ( this .currentTimestamp < currentTimestamp ) {
102- this .currentTimestamp = currentTimestamp ;
103- clockSequence .updateAndGet ( l -> l & 0x1FFFL );
104- }
105- }
106- finally {
107- lock .unlock ();
108- }
109- return clockSequence .getAndIncrement ();
110- }
111-
112- private static long getCurrentTimestamp () {
113- final Duration duration = Duration .between ( Holder .EPOCH_1582 , Instant .now () );
114- return duration .toSeconds () * 10_000_000 + duration .toNanosPart () / 100 ;
121+ private static long randomNode () {
122+ return Holder .numberGenerator .nextLong ( 0x1_0000_0000_0000L ) | 0x1000_0000_0000L ;
115123 }
116124}
0 commit comments