Skip to content

Commit a01b82b

Browse files
committed
HHH-18377 UUID Version 6 & UUID Version 7 implementations
1 parent e9701d9 commit a01b82b

File tree

2 files changed

+249
-0
lines changed

2 files changed

+249
-0
lines changed
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 a <a href="https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-6">UUID Version 6</a> generation strategy as defined by the {@link UUID#randomUUID()} method.
24+
*
25+
* @author Cedomir Igaly
26+
*/
27+
public class UuidV6ValueGenerator implements UUIDGenerationStrategy, UuidValueGenerator {
28+
29+
private static final Instant EPOCH_1582;
30+
31+
static {
32+
EPOCH_1582 = LocalDate.of( 1582, 10, 15 )
33+
.atStartOfDay( ZoneId.of( "UTC" ) )
34+
.toInstant();
35+
}
36+
37+
private static class Holder {
38+
39+
static final SecureRandom numberGenerator = new SecureRandom();
40+
}
41+
42+
public static final UuidV6ValueGenerator INSTANCE = new UuidV6ValueGenerator();
43+
44+
private final Lock lock = new ReentrantLock( true );
45+
46+
private long currentTimestamp;
47+
48+
private final AtomicLong clockSequence = new AtomicLong( 0 );
49+
50+
public UuidV6ValueGenerator() {
51+
this( getCurrentTimestamp(), 0 );
52+
}
53+
54+
public UuidV6ValueGenerator(final long currentTimestamp, final long clockSequence) {
55+
this.currentTimestamp = currentTimestamp;
56+
this.clockSequence.set( clockSequence );
57+
}
58+
59+
/**
60+
* A variant 6
61+
*/
62+
@Override
63+
public int getGeneratedVersion() {
64+
// UUIDv6 is a field-compatible version of UUIDv1, reordered for improved DB locality
65+
return 6;
66+
}
67+
68+
/**
69+
* Delegates to {@link UUID#randomUUID()}
70+
*/
71+
@Override
72+
public UUID generateUUID(SharedSessionContractImplementor session) {
73+
return generateUuid( session );
74+
}
75+
76+
77+
/**
78+
* <ul>
79+
* <li>32 bits - the most significant 32 bits of the 60-bit starting timestamp.</li>
80+
* <li>16 bits - the middle 16 bits of the 60-bit starting timestamp.</li>
81+
* <li>4 bits - version field, set to 0b0110 (6).</li>
82+
* <li>12 bits - the least significant 12 bits from the 60-bit starting timestamp.</li>
83+
* <li>2 bits - variant field, set to 0b10.</li>
84+
* <li>14 bits - the clock sequence, resets to 0 when timestamp changes. </li>
85+
* <li>48 bits - pseudorandom data to provide uniqueness.</li>
86+
* </ul>
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 a <a href="https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-7">UUID Version 7</a> generation strategy as defined by the {@link UUID#randomUUID()} method.
25+
*
26+
* @author Cedomir Igaly
27+
*/
28+
public class UuidV7ValueGenerator implements UUIDGenerationStrategy, UuidValueGenerator {
29+
30+
public static final UuidV7ValueGenerator INSTANCE = new UuidV7ValueGenerator();
31+
32+
private static class Holder {
33+
34+
static final SecureRandom numberGenerator = new SecureRandom();
35+
}
36+
37+
private final Lock lock = new ReentrantLock( true );
38+
39+
private Duration currentTimestamp;
40+
41+
private final AtomicLong clockSequence;
42+
43+
public UuidV7ValueGenerator() {
44+
this( getCurrentTimestamp(), 0 );
45+
}
46+
47+
public UuidV7ValueGenerator(final Duration currentTimestamp, final long clockSequence) {
48+
this.currentTimestamp = currentTimestamp;
49+
this.clockSequence = new AtomicLong( clockSequence );
50+
}
51+
52+
/**
53+
* A variant 7
54+
*/
55+
@Override
56+
public int getGeneratedVersion() {
57+
/*
58+
* UUIDv7 features a time-ordered value field derived from the widely implemented and well-
59+
* known Unix Epoch timestamp source, the number of milliseconds since midnight 1 Jan 1970 UTC,
60+
* leap seconds excluded.
61+
*/
62+
return 7;
63+
}
64+
65+
/**
66+
* Delegates to {@link UUID#randomUUID()}
67+
*/
68+
@Override
69+
public UUID generateUUID(SharedSessionContractImplementor session) {
70+
return generateUuid( session );
71+
}
72+
73+
/**
74+
* <ul>
75+
* <li>48 bits - 48-bit big-endian unsigned number of the Unix Epoch timestamp in milliseconds.</li>
76+
* <li>4 bits - version field, set to 0b0111 (7).</li>
77+
* <li>
78+
* 12 bits - sub-milliseconds part of timestamp (resolution approximately 1/4 of millisecond)
79+
* to guarantee additional monotonicity.
80+
* </li>
81+
* <li>2 bits - variant field, set to 0b10.</li>
82+
* <li>14 bits - counter to guarantee additional monotonicity, resets to 0 when timestamp changes. </li>
83+
* <li>48 bits - pseudorandom data to provide uniqueness.</li>
84+
* </ul>
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)