Skip to content

Commit 4e85302

Browse files
cigalysebersole
authored andcommitted
HHH-18377 UUID Version 6 & UUID Version 7 implementations
1 parent df405d3 commit 4e85302

File tree

4 files changed

+285
-2
lines changed

4 files changed

+285
-2
lines changed

hibernate-core/src/main/java/org/hibernate/annotations/UuidGenerator.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import java.util.UUID;
1010

1111
import org.hibernate.Incubating;
12+
import org.hibernate.id.uuid.UuidVersion6Strategy;
13+
import org.hibernate.id.uuid.UuidVersion7Strategy;
1214
import org.hibernate.id.uuid.UuidValueGenerator;
1315

1416
import static java.lang.annotation.ElementType.FIELD;
@@ -52,7 +54,19 @@ enum Style {
5254
* @implNote Can be a bottleneck, since synchronization is used when
5355
* incrementing an internal counter as part of the algorithm.
5456
*/
55-
TIME
57+
TIME,
58+
/**
59+
* Use a time-based generation strategy consistent with RFC 4122
60+
* version 6.
61+
* @see UuidVersion6Strategy
62+
*/
63+
VERSION_6,
64+
/**
65+
* Use a time-based generation strategy consistent with RFC 4122
66+
* version 7.
67+
* @see UuidVersion7Strategy
68+
*/
69+
VERSION_7
5670
}
5771

5872
/**

hibernate-core/src/main/java/org/hibernate/id/uuid/UuidGenerator.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
import static org.hibernate.annotations.UuidGenerator.Style.AUTO;
2525
import static org.hibernate.annotations.UuidGenerator.Style.TIME;
26+
import static org.hibernate.annotations.UuidGenerator.Style.VERSION_6;
27+
import static org.hibernate.annotations.UuidGenerator.Style.VERSION_7;
2628
import static org.hibernate.generator.EventTypeSets.INSERT_ONLY;
2729
import static org.hibernate.internal.util.ReflectHelper.getPropertyType;
2830

@@ -78,9 +80,15 @@ private static UuidValueGenerator determineValueGenerator(
7880
}
7981
return instantiateCustomGenerator( config.algorithm() );
8082
}
81-
else if ( config.style() == TIME ) {
83+
if ( config.style() == TIME ) {
8284
return new CustomVersionOneStrategy();
8385
}
86+
else if ( config.style() == VERSION_6 ) {
87+
return UuidVersion6Strategy.INSTANCE;
88+
}
89+
else if ( config.style() == VERSION_7 ) {
90+
return UuidVersion7Strategy.INSTANCE;
91+
}
8492
}
8593

8694
return StandardRandomStrategy.INSTANCE;
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.id.uuid;
6+
7+
import java.security.SecureRandom;
8+
import java.time.Duration;
9+
import java.time.Instant;
10+
import java.time.LocalDate;
11+
import java.time.ZoneId;
12+
import java.util.UUID;
13+
import java.util.concurrent.atomic.AtomicLong;
14+
import java.util.concurrent.locks.Lock;
15+
import java.util.concurrent.locks.ReentrantLock;
16+
17+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
18+
import org.hibernate.id.UUIDGenerationStrategy;
19+
20+
/**
21+
* 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>.
22+
*
23+
* <ul>
24+
* <li>32 bits - the most significant 32 bits of the 60-bit starting timestamp.</li>
25+
* <li>16 bits - the middle 16 bits of the 60-bit starting timestamp.</li>
26+
* <li>4 bits - version field, set to 0b0110 (6).</li>
27+
* <li>12 bits - the least significant 12 bits from the 60-bit starting timestamp.</li>
28+
* <li>2 bits - variant field, set to 0b10.</li>
29+
* <li>14 bits - the clock sequence, resets to 0 when timestamp changes. </li>
30+
* <li>48 bits - pseudorandom data to provide uniqueness.</li>
31+
* </ul>
32+
*
33+
* @author Cedomir Igaly
34+
*/
35+
public class UuidVersion6Strategy implements UUIDGenerationStrategy, UuidValueGenerator {
36+
37+
private static final Instant EPOCH_1582;
38+
39+
static {
40+
EPOCH_1582 = LocalDate.of( 1582, 10, 15 )
41+
.atStartOfDay( ZoneId.of( "UTC" ) )
42+
.toInstant();
43+
}
44+
45+
private static class Holder {
46+
47+
static final SecureRandom numberGenerator = new SecureRandom();
48+
}
49+
50+
public static final UuidVersion6Strategy INSTANCE = new UuidVersion6Strategy();
51+
52+
private final Lock lock = new ReentrantLock( true );
53+
54+
private long currentTimestamp;
55+
56+
private final AtomicLong clockSequence = new AtomicLong( 0 );
57+
58+
public UuidVersion6Strategy() {
59+
this( getCurrentTimestamp(), 0 );
60+
}
61+
62+
public UuidVersion6Strategy(final long currentTimestamp, final long clockSequence) {
63+
this.currentTimestamp = currentTimestamp;
64+
this.clockSequence.set( clockSequence );
65+
}
66+
67+
/**
68+
* A variant 6
69+
*/
70+
@Override
71+
public int getGeneratedVersion() {
72+
// UUIDv6 is a field-compatible version of UUIDv1, reordered for improved DB locality
73+
return 6;
74+
}
75+
76+
/**
77+
* Delegates to {@link #generateUuid}
78+
*/
79+
@Override
80+
public UUID generateUUID(SharedSessionContractImplementor session) {
81+
return generateUuid( session );
82+
}
83+
84+
85+
/**
86+
* @param session session
87+
*
88+
* @return UUID version 6
89+
* @see UuidValueGenerator#generateUuid(SharedSessionContractImplementor)
90+
*/
91+
@Override
92+
public UUID generateUuid(SharedSessionContractImplementor session) {
93+
final long currentTimestamp = getCurrentTimestamp();
94+
95+
return new UUID(
96+
// MSB bits 0-47 - most significant 32 bits of the 60-bit starting timestamp
97+
currentTimestamp << 4 & 0xFFFF_FFFF_FFFF_0000L
98+
// MSB bits 48-51 - version = 6
99+
| 0x6000L
100+
// MSB bits 52-63 - least significant 12 bits from the 60-bit starting timestamp
101+
| currentTimestamp & 0x0FFFL,
102+
// LSB bits 0-1 - variant = 4
103+
0x8000_0000_0000_0000L
104+
// LSB bits 2-15 - clock sequence
105+
| ( getSequence( currentTimestamp ) & 0x3FFFL ) << 48
106+
// LSB bits 16-63 - pseudorandom data
107+
| Holder.numberGenerator.nextLong() & 0xFFFF_FFFF_FFFFL
108+
);
109+
}
110+
111+
112+
private long getSequence(final long currentTimestamp) {
113+
lock.lock();
114+
try {
115+
if ( this.currentTimestamp > currentTimestamp ) {
116+
this.currentTimestamp = currentTimestamp;
117+
clockSequence.set( 0 );
118+
}
119+
}
120+
finally {
121+
lock.unlock();
122+
}
123+
return clockSequence.getAndIncrement();
124+
}
125+
126+
private static long getCurrentTimestamp() {
127+
final Duration duration = Duration.between( EPOCH_1582, Instant.now() );
128+
return duration.toSeconds() * 10_000_000 + duration.toNanosPart() / 100;
129+
}
130+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.id.uuid;
6+
7+
import java.security.SecureRandom;
8+
import java.time.Duration;
9+
import java.time.Instant;
10+
import java.util.UUID;
11+
import java.util.concurrent.atomic.AtomicLong;
12+
import java.util.concurrent.locks.Lock;
13+
import java.util.concurrent.locks.ReentrantLock;
14+
15+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
16+
import org.hibernate.id.UUIDGenerationStrategy;
17+
18+
import static java.time.Instant.EPOCH;
19+
import static java.time.temporal.ChronoUnit.MILLIS;
20+
21+
/**
22+
* 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>.
23+
*
24+
* <ul>
25+
* <li>48 bits - 48-bit big-endian unsigned number of the Unix Epoch timestamp in milliseconds.</li>
26+
* <li>4 bits - version field, set to 0b0111 (7).</li>
27+
* <li>
28+
* 12 bits - sub-milliseconds part of timestamp (resolution approximately 1/4 of millisecond)
29+
* to guarantee additional monotonicity.
30+
* </li>
31+
* <li>2 bits - variant field, set to 0b10.</li>
32+
* <li>14 bits - counter to guarantee additional monotonicity, resets to 0 when timestamp changes. </li>
33+
* <li>48 bits - pseudorandom data to provide uniqueness.</li>
34+
* </ul>
35+
*
36+
* @author Cedomir Igaly
37+
*/
38+
public class UuidVersion7Strategy implements UUIDGenerationStrategy, UuidValueGenerator {
39+
40+
public static final UuidVersion7Strategy INSTANCE = new UuidVersion7Strategy();
41+
42+
private static class Holder {
43+
44+
static final SecureRandom numberGenerator = new SecureRandom();
45+
}
46+
47+
private final Lock lock = new ReentrantLock( true );
48+
49+
private Duration currentTimestamp;
50+
51+
private final AtomicLong clockSequence;
52+
53+
public UuidVersion7Strategy() {
54+
this( getCurrentTimestamp(), 0 );
55+
}
56+
57+
public UuidVersion7Strategy(final Duration currentTimestamp, final long clockSequence) {
58+
this.currentTimestamp = currentTimestamp;
59+
this.clockSequence = new AtomicLong( clockSequence );
60+
}
61+
62+
/**
63+
* A variant 7
64+
*/
65+
@Override
66+
public int getGeneratedVersion() {
67+
/*
68+
* UUIDv7 features a time-ordered value field derived from the widely implemented and well-
69+
* known Unix Epoch timestamp source, the number of milliseconds since midnight 1 Jan 1970 UTC,
70+
* leap seconds excluded.
71+
*/
72+
return 7;
73+
}
74+
75+
/**
76+
* Delegates to {@link #generateUuid}
77+
*/
78+
@Override
79+
public UUID generateUUID(SharedSessionContractImplementor session) {
80+
return generateUuid( session );
81+
}
82+
83+
/**
84+
* @param session session
85+
*
86+
* @return UUID version 7
87+
* @see UuidValueGenerator#generateUuid(SharedSessionContractImplementor)
88+
*/
89+
@Override
90+
public UUID generateUuid(SharedSessionContractImplementor session) {
91+
final Duration currentTimestamp = getCurrentTimestamp();
92+
93+
final long seq = getSequence( currentTimestamp );
94+
95+
final long millis = currentTimestamp.getSeconds() * 1000 + currentTimestamp.getNano() / 1_000_000;
96+
final long nanosPart = Math.round( ( currentTimestamp.getNano() % 1_000_000L ) * 0.004096 );
97+
98+
return new UUID(
99+
// MSB bits 0-47 - 48-bit big-endian unsigned number of the Unix Epoch timestamp in milliseconds
100+
millis << 16 & 0xFFFF_FFFF_FFFF_0000L
101+
// MSB bits 48-51 - version = 7
102+
| 0x7000L
103+
// MSB bits 52-63 - sub-milliseconds part of timestamp
104+
| nanosPart & 0xFFFL,
105+
// LSB bits 0-1 - variant = 4
106+
0x8000_0000_0000_0000L
107+
// LSB bits 2-15 - counter
108+
| ( seq & 0x3FFFL ) << 48
109+
// LSB bits 16-63 - pseudorandom data
110+
| Holder.numberGenerator.nextLong() & 0xFFFF_FFFF_FFFFL
111+
);
112+
}
113+
114+
private long getSequence(final Duration currentTimestamp) {
115+
lock.lock();
116+
try {
117+
if ( !this.currentTimestamp.equals( currentTimestamp ) ) {
118+
this.currentTimestamp = currentTimestamp;
119+
clockSequence.set( 0 );
120+
}
121+
}
122+
finally {
123+
lock.unlock();
124+
}
125+
return clockSequence.getAndIncrement();
126+
}
127+
128+
private static Duration getCurrentTimestamp() {
129+
return Duration.between( EPOCH, Instant.now() ).truncatedTo( MILLIS );
130+
}
131+
}

0 commit comments

Comments
 (0)