Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import java.util.UUID;

import org.hibernate.Incubating;
import org.hibernate.id.uuid.UuidVersion6Strategy;
import org.hibernate.id.uuid.UuidVersion7Strategy;
import org.hibernate.id.uuid.UuidValueGenerator;

import static java.lang.annotation.ElementType.FIELD;
Expand Down Expand Up @@ -52,7 +54,19 @@ enum Style {
* @implNote Can be a bottleneck, since synchronization is used when
* incrementing an internal counter as part of the algorithm.
*/
TIME
TIME,
/**
* Use a time-based generation strategy consistent with RFC 4122
* version 6.
* @see UuidVersion6Strategy
*/
VERSION_6,
/**
* Use a time-based generation strategy consistent with RFC 4122
* version 7.
* @see UuidVersion7Strategy
*/
VERSION_7
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

import static org.hibernate.annotations.UuidGenerator.Style.AUTO;
import static org.hibernate.annotations.UuidGenerator.Style.TIME;
import static org.hibernate.annotations.UuidGenerator.Style.VERSION_6;
import static org.hibernate.annotations.UuidGenerator.Style.VERSION_7;
import static org.hibernate.generator.EventTypeSets.INSERT_ONLY;
import static org.hibernate.internal.util.ReflectHelper.getPropertyType;

Expand Down Expand Up @@ -78,9 +80,15 @@ private static UuidValueGenerator determineValueGenerator(
}
return instantiateCustomGenerator( config.algorithm() );
}
else if ( config.style() == TIME ) {
if ( config.style() == TIME ) {
return new CustomVersionOneStrategy();
}
else if ( config.style() == VERSION_6 ) {
return UuidVersion6Strategy.INSTANCE;
}
else if ( config.style() == VERSION_7 ) {
return UuidVersion7Strategy.INSTANCE;
}
}

return StandardRandomStrategy.INSTANCE;
Expand Down
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>
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
*
*
* @apiNote Version 6 is field-compatible with Version 1, with the time bits reordered for improved DB locality.
*

* @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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// UUIDv6 is a field-compatible version of UUIDv1, reordered for improved DB locality

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param session session
*
* @return UUID version 6
* @see UuidValueGenerator#generateUuid(SharedSessionContractImplementor)
* Generates a Version 6 compliant {@linkplain UUID} value.
*
* @return A Version 6 compliant {@linkplain UUID}.
*
* @see UuidValueGenerator#generateUuid

*/
@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>
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
*
*
* @apiNote A Version 7 UUID 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.
*

* @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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/*
* 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.
*/

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param session session
*
* @return UUID version 7
* @see UuidValueGenerator#generateUuid(SharedSessionContractImplementor)
* Generates a Version 7 {@linkplain UUID}.
*
* @return A Version version 7 {@linkplain UUID}.
* @see UuidValueGenerator#generateUuid(SharedSessionContractImplementor)

*/
@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;
}
}
Loading