-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
HHH-18377 Support for uuid v6 and v7 generated ids #8719
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,130 @@ | ||||||||||||||||||||
| /* | ||||||||||||||||||||
| * SPDX-License-Identifier: LGPL-2.1-or-later | ||||||||||||||||||||
| * Copyright Red Hat Inc. and Hibernate Authors | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| package org.hibernate.id.uuid; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import java.security.SecureRandom; | ||||||||||||||||||||
| import java.time.Duration; | ||||||||||||||||||||
| import java.time.Instant; | ||||||||||||||||||||
| import java.time.LocalDate; | ||||||||||||||||||||
| import java.time.ZoneId; | ||||||||||||||||||||
| import java.util.UUID; | ||||||||||||||||||||
| import java.util.concurrent.atomic.AtomicLong; | ||||||||||||||||||||
| import java.util.concurrent.locks.Lock; | ||||||||||||||||||||
| import java.util.concurrent.locks.ReentrantLock; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import org.hibernate.engine.spi.SharedSessionContractImplementor; | ||||||||||||||||||||
| import org.hibernate.id.UUIDGenerationStrategy; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * 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>. | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * <ul> | ||||||||||||||||||||
| * <li>32 bits - the most significant 32 bits of the 60-bit starting timestamp.</li> | ||||||||||||||||||||
| * <li>16 bits - the middle 16 bits of the 60-bit starting timestamp.</li> | ||||||||||||||||||||
| * <li>4 bits - version field, set to 0b0110 (6).</li> | ||||||||||||||||||||
| * <li>12 bits - the least significant 12 bits from the 60-bit starting timestamp.</li> | ||||||||||||||||||||
| * <li>2 bits - variant field, set to 0b10.</li> | ||||||||||||||||||||
| * <li>14 bits - the clock sequence, resets to 0 when timestamp changes. </li> | ||||||||||||||||||||
| * <li>48 bits - pseudorandom data to provide uniqueness.</li> | ||||||||||||||||||||
| * </ul> | ||||||||||||||||||||
| * | ||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||
| * @author Cedomir Igaly | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| public class UuidVersion6Strategy implements UUIDGenerationStrategy, UuidValueGenerator { | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private static final Instant EPOCH_1582; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| static { | ||||||||||||||||||||
| EPOCH_1582 = LocalDate.of( 1582, 10, 15 ) | ||||||||||||||||||||
| .atStartOfDay( ZoneId.of( "UTC" ) ) | ||||||||||||||||||||
| .toInstant(); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private static class Holder { | ||||||||||||||||||||
|
|
||||||||||||||||||||
| static final SecureRandom numberGenerator = new SecureRandom(); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public static final UuidVersion6Strategy INSTANCE = new UuidVersion6Strategy(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private final Lock lock = new ReentrantLock( true ); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private long currentTimestamp; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private final AtomicLong clockSequence = new AtomicLong( 0 ); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public UuidVersion6Strategy() { | ||||||||||||||||||||
| this( getCurrentTimestamp(), 0 ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public UuidVersion6Strategy(final long currentTimestamp, final long clockSequence) { | ||||||||||||||||||||
| this.currentTimestamp = currentTimestamp; | ||||||||||||||||||||
| this.clockSequence.set( clockSequence ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * A variant 6 | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| @Override | ||||||||||||||||||||
| public int getGeneratedVersion() { | ||||||||||||||||||||
| // UUIDv6 is a field-compatible version of UUIDv1, reordered for improved DB locality | ||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||
| return 6; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * Delegates to {@link #generateUuid} | ||||||||||||||||||||
| */ | ||||||||||||||||||||
| @Override | ||||||||||||||||||||
| public UUID generateUUID(SharedSessionContractImplementor session) { | ||||||||||||||||||||
| return generateUuid( session ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| /** | ||||||||||||||||||||
| * @param session session | ||||||||||||||||||||
| * | ||||||||||||||||||||
| * @return UUID version 6 | ||||||||||||||||||||
| * @see UuidValueGenerator#generateUuid(SharedSessionContractImplementor) | ||||||||||||||||||||
|
Comment on lines
+86
to
+89
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||
| */ | ||||||||||||||||||||
| @Override | ||||||||||||||||||||
| public UUID generateUuid(SharedSessionContractImplementor session) { | ||||||||||||||||||||
| final long currentTimestamp = getCurrentTimestamp(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return new UUID( | ||||||||||||||||||||
| // MSB bits 0-47 - most significant 32 bits of the 60-bit starting timestamp | ||||||||||||||||||||
| currentTimestamp << 4 & 0xFFFF_FFFF_FFFF_0000L | ||||||||||||||||||||
| // MSB bits 48-51 - version = 6 | ||||||||||||||||||||
| | 0x6000L | ||||||||||||||||||||
| // MSB bits 52-63 - least significant 12 bits from the 60-bit starting timestamp | ||||||||||||||||||||
| | currentTimestamp & 0x0FFFL, | ||||||||||||||||||||
| // LSB bits 0-1 - variant = 4 | ||||||||||||||||||||
| 0x8000_0000_0000_0000L | ||||||||||||||||||||
| // LSB bits 2-15 - clock sequence | ||||||||||||||||||||
| | ( getSequence( currentTimestamp ) & 0x3FFFL ) << 48 | ||||||||||||||||||||
| // LSB bits 16-63 - pseudorandom data | ||||||||||||||||||||
| | Holder.numberGenerator.nextLong() & 0xFFFF_FFFF_FFFFL | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| private long getSequence(final long currentTimestamp) { | ||||||||||||||||||||
| lock.lock(); | ||||||||||||||||||||
| try { | ||||||||||||||||||||
| if ( this.currentTimestamp > currentTimestamp ) { | ||||||||||||||||||||
| this.currentTimestamp = currentTimestamp; | ||||||||||||||||||||
| clockSequence.set( 0 ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| finally { | ||||||||||||||||||||
| lock.unlock(); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| return clockSequence.getAndIncrement(); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private static long getCurrentTimestamp() { | ||||||||||||||||||||
| final Duration duration = Duration.between( EPOCH_1582, Instant.now() ); | ||||||||||||||||||||
| return duration.toSeconds() * 10_000_000 + duration.toNanosPart() / 100; | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,131 @@ | ||||||||||||||||||
| /* | ||||||||||||||||||
| * SPDX-License-Identifier: LGPL-2.1-or-later | ||||||||||||||||||
| * Copyright Red Hat Inc. and Hibernate Authors | ||||||||||||||||||
| */ | ||||||||||||||||||
| package org.hibernate.id.uuid; | ||||||||||||||||||
|
|
||||||||||||||||||
| import java.security.SecureRandom; | ||||||||||||||||||
| import java.time.Duration; | ||||||||||||||||||
| import java.time.Instant; | ||||||||||||||||||
| import java.util.UUID; | ||||||||||||||||||
| import java.util.concurrent.atomic.AtomicLong; | ||||||||||||||||||
| import java.util.concurrent.locks.Lock; | ||||||||||||||||||
| import java.util.concurrent.locks.ReentrantLock; | ||||||||||||||||||
|
|
||||||||||||||||||
| import org.hibernate.engine.spi.SharedSessionContractImplementor; | ||||||||||||||||||
| import org.hibernate.id.UUIDGenerationStrategy; | ||||||||||||||||||
|
|
||||||||||||||||||
| import static java.time.Instant.EPOCH; | ||||||||||||||||||
| import static java.time.temporal.ChronoUnit.MILLIS; | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * 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>. | ||||||||||||||||||
| * | ||||||||||||||||||
| * <ul> | ||||||||||||||||||
| * <li>48 bits - 48-bit big-endian unsigned number of the Unix Epoch timestamp in milliseconds.</li> | ||||||||||||||||||
| * <li>4 bits - version field, set to 0b0111 (7).</li> | ||||||||||||||||||
| * <li> | ||||||||||||||||||
| * 12 bits - sub-milliseconds part of timestamp (resolution approximately 1/4 of millisecond) | ||||||||||||||||||
| * to guarantee additional monotonicity. | ||||||||||||||||||
| * </li> | ||||||||||||||||||
| * <li>2 bits - variant field, set to 0b10.</li> | ||||||||||||||||||
| * <li>14 bits - counter to guarantee additional monotonicity, resets to 0 when timestamp changes. </li> | ||||||||||||||||||
| * <li>48 bits - pseudorandom data to provide uniqueness.</li> | ||||||||||||||||||
| * </ul> | ||||||||||||||||||
| * | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
| * @author Cedomir Igaly | ||||||||||||||||||
| */ | ||||||||||||||||||
| public class UuidVersion7Strategy implements UUIDGenerationStrategy, UuidValueGenerator { | ||||||||||||||||||
|
|
||||||||||||||||||
| public static final UuidVersion7Strategy INSTANCE = new UuidVersion7Strategy(); | ||||||||||||||||||
|
|
||||||||||||||||||
| private static class Holder { | ||||||||||||||||||
|
|
||||||||||||||||||
| static final SecureRandom numberGenerator = new SecureRandom(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| private final Lock lock = new ReentrantLock( true ); | ||||||||||||||||||
|
|
||||||||||||||||||
| private Duration currentTimestamp; | ||||||||||||||||||
|
|
||||||||||||||||||
| private final AtomicLong clockSequence; | ||||||||||||||||||
|
|
||||||||||||||||||
| public UuidVersion7Strategy() { | ||||||||||||||||||
| this( getCurrentTimestamp(), 0 ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| public UuidVersion7Strategy(final Duration currentTimestamp, final long clockSequence) { | ||||||||||||||||||
| this.currentTimestamp = currentTimestamp; | ||||||||||||||||||
| this.clockSequence = new AtomicLong( clockSequence ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * A variant 7 | ||||||||||||||||||
| */ | ||||||||||||||||||
| @Override | ||||||||||||||||||
| public int getGeneratedVersion() { | ||||||||||||||||||
| /* | ||||||||||||||||||
| * UUIDv7 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, | ||||||||||||||||||
| * leap seconds excluded. | ||||||||||||||||||
| */ | ||||||||||||||||||
|
Comment on lines
+67
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
| return 7; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * Delegates to {@link #generateUuid} | ||||||||||||||||||
| */ | ||||||||||||||||||
| @Override | ||||||||||||||||||
| public UUID generateUUID(SharedSessionContractImplementor session) { | ||||||||||||||||||
| return generateUuid( session ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * @param session session | ||||||||||||||||||
| * | ||||||||||||||||||
| * @return UUID version 7 | ||||||||||||||||||
| * @see UuidValueGenerator#generateUuid(SharedSessionContractImplementor) | ||||||||||||||||||
|
Comment on lines
+84
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
| */ | ||||||||||||||||||
| @Override | ||||||||||||||||||
| public UUID generateUuid(SharedSessionContractImplementor session) { | ||||||||||||||||||
| final Duration currentTimestamp = getCurrentTimestamp(); | ||||||||||||||||||
|
|
||||||||||||||||||
| final long seq = getSequence( currentTimestamp ); | ||||||||||||||||||
|
|
||||||||||||||||||
| final long millis = currentTimestamp.getSeconds() * 1000 + currentTimestamp.getNano() / 1_000_000; | ||||||||||||||||||
| final long nanosPart = Math.round( ( currentTimestamp.getNano() % 1_000_000L ) * 0.004096 ); | ||||||||||||||||||
|
|
||||||||||||||||||
| return new UUID( | ||||||||||||||||||
| // MSB bits 0-47 - 48-bit big-endian unsigned number of the Unix Epoch timestamp in milliseconds | ||||||||||||||||||
| millis << 16 & 0xFFFF_FFFF_FFFF_0000L | ||||||||||||||||||
| // MSB bits 48-51 - version = 7 | ||||||||||||||||||
| | 0x7000L | ||||||||||||||||||
| // MSB bits 52-63 - sub-milliseconds part of timestamp | ||||||||||||||||||
| | nanosPart & 0xFFFL, | ||||||||||||||||||
| // LSB bits 0-1 - variant = 4 | ||||||||||||||||||
| 0x8000_0000_0000_0000L | ||||||||||||||||||
| // LSB bits 2-15 - counter | ||||||||||||||||||
| | ( seq & 0x3FFFL ) << 48 | ||||||||||||||||||
| // LSB bits 16-63 - pseudorandom data | ||||||||||||||||||
| | Holder.numberGenerator.nextLong() & 0xFFFF_FFFF_FFFFL | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| private long getSequence(final Duration currentTimestamp) { | ||||||||||||||||||
| lock.lock(); | ||||||||||||||||||
| try { | ||||||||||||||||||
| if ( !this.currentTimestamp.equals( currentTimestamp ) ) { | ||||||||||||||||||
| this.currentTimestamp = currentTimestamp; | ||||||||||||||||||
| clockSequence.set( 0 ); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| finally { | ||||||||||||||||||
| lock.unlock(); | ||||||||||||||||||
| } | ||||||||||||||||||
| return clockSequence.getAndIncrement(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| private static Duration getCurrentTimestamp() { | ||||||||||||||||||
| return Duration.between( EPOCH, Instant.now() ).truncatedTo( MILLIS ); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| /* | ||
| * SPDX-License-Identifier: LGPL-2.1-or-later | ||
| * Copyright Red Hat Inc. and Hibernate Authors | ||
| */ | ||
| package org.hibernate.orm.test.id.uuid.rfc9562; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| import org.hibernate.annotations.UuidGenerator; | ||
| import org.hibernate.id.uuid.UuidVersion7Strategy; | ||
|
|
||
| import jakarta.persistence.Basic; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Table; | ||
|
|
||
| @Entity(name = "EntitySeven") | ||
| @Table(name = "entity_seven") | ||
| public class EntitySeven { | ||
| @Id | ||
| @UuidGenerator(algorithm = UuidVersion7Strategy.class) | ||
| public UUID id; | ||
| @Basic | ||
| public String name; | ||
|
|
||
| private EntitySeven() { | ||
| // for Hibernate use | ||
| } | ||
|
|
||
| public EntitySeven(String name) { | ||
| this.name = name; | ||
| } | ||
|
|
||
| public UUID getId() { | ||
| return id; | ||
| } | ||
|
|
||
| public String getName() { | ||
| return name; | ||
| } | ||
|
|
||
| public void setName(String name) { | ||
| this.name = name; | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.