Skip to content

Commit 5053fc7

Browse files
committed
HHH-18377 UUID Version 6 & UUID Version 7 implementations
1 parent 0e5846b commit 5053fc7

File tree

4 files changed

+272
-1
lines changed

4 files changed

+272
-1
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: 8 additions & 0 deletions
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

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

0 commit comments

Comments
 (0)