Skip to content

Commit 2f3a01f

Browse files
committed
HHH-18411 - Add ability to specify a custom UuidGenerator.ValueGenerator
1 parent fdef3b5 commit 2f3a01f

File tree

27 files changed

+1238
-37
lines changed

27 files changed

+1238
-37
lines changed

documentation/src/main/asciidoc/userguide/chapters/domain/identifiers.adoc

Lines changed: 101 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ The most important piece of information here is the specified `jakarta.persisten
306306
`IDENTITY`:: Indicates that database IDENTITY columns will be used for primary key value generation. See <<identifiers-generators-identity>>.
307307
`SEQUENCE`:: Indicates that database sequence should be used for obtaining primary key values. See <<identifiers-generators-sequence>>.
308308
`TABLE`:: Indicates that a database table should be used for obtaining primary key values. See <<identifiers-generators-table>>.
309+
`UUID`:::Indicates that UUID generation should be used. See <<identifiers-generators-uuid>>
309310

310311
[[identifiers-generators-auto]]
311312
==== Interpreting AUTO
@@ -314,7 +315,7 @@ How a persistence provider interprets the AUTO generation type is left up to the
314315

315316
The default behavior is to look at the Java type of the identifier attribute, plus what the underlying database supports.
316317

317-
If the identifier type is UUID, Hibernate is going to use a <<identifiers-generators-uuid, UUID identifier>>.
318+
If the identifier type is UUID, Hibernate is going to use a <<identifiers-generators-uuid, UUID generator>>.
318319

319320
If the identifier type is numeric (e.g. `Long`, `Integer`), then Hibernate will use its `SequenceStyleGenerator` which
320321
resolves to a SEQUENCE generation if the underlying database supports sequences and a table-based generation otherwise.
@@ -500,38 +501,122 @@ include::{extrasdir}/id/identifiers-generators-table-configured-persist-example.
500501
[[identifiers-generators-uuid]]
501502
==== Using UUID generation
502503

503-
As mentioned above, Hibernate supports UUID identifier value generation.
504-
This is supported through its `org.hibernate.id.UUIDGenerator` id generator.
504+
Hibernate offers 2 flavors of support for UUID generation -
505505

506-
NOTE:: `org.hibernate.id.UUIDGenerator` is an example of `@IdGeneratorType` discussed in <<identifiers-generators-IdGeneratorType>>
506+
1. using `org.hibernate.id.uuid.UuidGenerator`, which can be configured using the `org.hibernate.annotations.UuidGenerator` annotation.
507+
2. using `org.hibernate.id.UUIDGenerator`, which can be configured using the `@GenericGenerator` annotation. Note that this approach is deprecated.
507508

509+
For legacy reasons, `org.hibernate.id.UUIDGenerator` is used when the generator is implicit (or explicitly requested via `@GenericGenerator`).
508510

509-
`UUIDGenerator` supports pluggable strategies for exactly how the UUID is generated.
510-
These strategies are defined by the `org.hibernate.id.UUIDGenerationStrategy` contract.
511-
The default strategy is a version 4 (random) strategy according to IETF RFC 4122.
512-
Hibernate does ship with an alternative strategy which is a RFC 4122 version 1 (time-based) strategy (using IP address rather than mac address).
511+
[NOTE]
512+
====
513+
Future versions of Hibernate will drop support for `org.hibernate.id.UUIDGenerator` and the following 3 examples
514+
will then use `org.hibernate.id.uuid.UuidGenerator`.
515+
====
516+
517+
[[example-identifiers-generators-uuid-implicit]]
518+
.Implicit UUID generation
519+
====
520+
[source,java]
521+
----
522+
include::{example-dir-identifier}/uuid/implicit/Book.java[tag=example-identifiers-generators-uuid-implicit, indent=0]
523+
----
524+
====
525+
526+
[[example-identifiers-generators-uuid-implicit2]]
527+
.Another example of implicit UUID generation
528+
====
529+
[source,java]
530+
----
531+
include::{example-dir-identifier}/uuid/implicit2/Book.java[tag=example-identifiers-generators-uuid-implicit, indent=0]
532+
----
533+
====
534+
535+
[[example-identifiers-generators-uuid-implicit3]]
536+
.Implicit UUID generation with String
537+
====
538+
[source,java]
539+
----
540+
include::{example-dir-identifier}/uuid/implicit3/Book.java[tag=example-identifiers-generators-uuid-implicit, indent=0]
541+
----
542+
====
543+
544+
The second approach, using `org.hibernate.id.uuid.UuidGenerator`, is much more flexible and usable
545+
because it builds on top of the <<identifiers-generators-IdGeneratorType,@IdGeneratorType>> support.
546+
547+
To use (and optionally configure) this strategy, use the `org.hibernate.annotations.UuidGenerator` annotation.
548+
549+
By default, Hibernate uses a random (IETF RFC 4122 version 4) generation.
550+
551+
[[example-identifiers-generators-uuid-random]]
552+
.Random UUID generation
553+
====
554+
[source,java]
555+
----
556+
include::{example-dir-identifier}/uuid/random/Book.java[tag=example-identifiers-generators-uuid-implicit, indent=0]
557+
----
558+
====
559+
560+
[[example-identifiers-generators-uuid-random2]]
561+
.Random UUID generation, with explicit style
562+
====
563+
[source,java]
564+
----
565+
include::{example-dir-identifier}/uuid/random2/Book.java[tag=example-identifiers-generators-uuid-implicit, indent=0]
566+
----
567+
====
568+
569+
[[example-identifiers-generators-uuid-random3]]
570+
.Random UUID generation, with String
571+
====
572+
[source,java]
573+
----
574+
include::{example-dir-identifier}/uuid/random3/Book.java[tag=example-identifiers-generators-uuid-implicit, indent=0]
575+
----
576+
====
513577

514-
[[identifiers-generators-uuid-mapping-example]]
515-
.Implicitly using the random UUID strategy
578+
Hibernate also comes with simplified support for a time-based (IETF RFC 4122 version 1, variant2) generation.
579+
580+
[[example-identifiers-generators-uuid-time]]
581+
.Time-based UUID generation
582+
====
583+
[source,java]
584+
----
585+
include::{example-dir-identifier}/uuid/time/Book.java[tag=example-identifiers-generators-uuid-implicit, indent=0]
586+
----
587+
====
588+
589+
[[example-identifiers-generators-uuid-time2]]
590+
.Time-based UUID generation using String
516591
====
517592
[source,java]
518593
----
519-
include::{example-dir-identifier}/UuidGeneratedValueTest.java[tag=identifiers-generators-uuid-mapping-example, indent=0]
594+
include::{example-dir-identifier}/uuid/time2/Book.java[tag=example-identifiers-generators-uuid-implicit, indent=0]
520595
----
521596
====
522597

523-
To specify an alternative generation strategy, we'd have to define some configuration via `@GenericGenerator`.
524-
Here we choose the RFC 4122 version 1 compliant strategy named `org.hibernate.id.uuid.CustomVersionOneStrategy`.
598+
For even more flexibility, Hibernate also offers the ability to plug in custom algorithms for creating the UUID value
599+
by specifying an implementation of `org.hibernate.id.uuid.UuidValueGenerator`.
525600

526-
[[identifiers-generators-custom-uuid-mapping-example]]
527-
.Implicitly using the random UUID strategy
601+
[[example-identifiers-generators-uuid-custom]]
602+
.Custom UUID generation
528603
====
529604
[source,java]
530605
----
531-
include::{example-dir-identifier}/UuidCustomGeneratedValueTest.java[tag=identifiers-generators-custom-uuid-mapping-example, indent=0]
606+
include::{example-dir-identifier}/uuid/custom/Book.java[tag=example-identifiers-generators-uuid-implicit, indent=0]
532607
----
533608
====
534609

610+
[[example-identifiers-generators-uuid-custom2]]
611+
.Custom UUID generation using String
612+
====
613+
[source,java]
614+
----
615+
include::{example-dir-identifier}/uuid/custom2/Book.java[tag=example-identifiers-generators-uuid-implicit, indent=0]
616+
----
617+
====
618+
619+
535620
[[identifiers-generators-optimizer]]
536621
==== Optimizers
537622

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

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ public class UuidGenerator implements BeforeExecutionGenerator {
4040
private final UuidValueGenerator generator;
4141
private final ValueTransformer valueTransformer;
4242

43+
/**
44+
* This form is used when there is no {@code @UuidGenerator} but we know we want this generator
45+
*/
46+
@Internal
47+
public UuidGenerator(Class<?> memberType) {
48+
generator = StandardRandomStrategy.INSTANCE;
49+
valueTransformer = determineProperTransformer( memberType );
50+
}
51+
4352
private UuidGenerator(
4453
org.hibernate.annotations.UuidGenerator config,
4554
Member idMember) {
@@ -65,19 +74,7 @@ else if ( config.style() == TIME ) {
6574
}
6675

6776
final Class<?> propertyType = getPropertyType( idMember );
68-
69-
if ( UUID.class.isAssignableFrom( propertyType ) ) {
70-
valueTransformer = UUIDJavaType.PassThroughTransformer.INSTANCE;
71-
}
72-
else if ( String.class.isAssignableFrom( propertyType ) ) {
73-
valueTransformer = UUIDJavaType.ToStringTransformer.INSTANCE;
74-
}
75-
else if ( byte[].class.isAssignableFrom( propertyType ) ) {
76-
valueTransformer = UUIDJavaType.ToBytesTransformer.INSTANCE;
77-
}
78-
else {
79-
throw new HibernateException( "Unanticipated return type [" + propertyType.getName() + "] for UUID conversion" );
80-
}
77+
this.valueTransformer = determineProperTransformer( propertyType );
8178
}
8279

8380
private static UuidValueGenerator instantiateCustomGenerator(Class<? extends UuidValueGenerator> algorithmClass) {
@@ -89,6 +86,22 @@ private static UuidValueGenerator instantiateCustomGenerator(Class<? extends Uui
8986
}
9087
}
9188

89+
private ValueTransformer determineProperTransformer(Class<?> propertyType) {
90+
if ( UUID.class.isAssignableFrom( propertyType ) ) {
91+
return UUIDJavaType.PassThroughTransformer.INSTANCE;
92+
}
93+
94+
if ( String.class.isAssignableFrom( propertyType ) ) {
95+
return UUIDJavaType.ToStringTransformer.INSTANCE;
96+
}
97+
98+
if ( byte[].class.isAssignableFrom( propertyType ) ) {
99+
return UUIDJavaType.ToBytesTransformer.INSTANCE;
100+
}
101+
102+
throw new HibernateException( "Unanticipated return type [" + propertyType.getName() + "] for UUID conversion" );
103+
}
104+
92105
public UuidGenerator(
93106
org.hibernate.annotations.UuidGenerator config,
94107
Member idMember,

hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/annotation/UuidGeneratorAnnotationTests.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,19 @@
88

99
import java.util.UUID;
1010

11-
import org.hibernate.boot.model.relational.Database;
1211
import org.hibernate.dialect.SybaseDialect;
1312
import org.hibernate.generator.Generator;
14-
import org.hibernate.id.factory.IdentifierGeneratorFactory;
15-
import org.hibernate.id.factory.spi.CustomIdGeneratorCreationContext;
1613
import org.hibernate.id.uuid.StandardRandomStrategy;
1714
import org.hibernate.id.uuid.UuidGenerator;
1815
import org.hibernate.mapping.BasicValue;
19-
import org.hibernate.mapping.PersistentClass;
2016
import org.hibernate.mapping.Property;
21-
import org.hibernate.mapping.RootClass;
22-
import org.hibernate.service.ServiceRegistry;
2317

2418
import org.hibernate.testing.orm.junit.DomainModel;
2519
import org.hibernate.testing.orm.junit.DomainModelScope;
2620
import org.hibernate.testing.orm.junit.SessionFactory;
2721
import org.hibernate.testing.orm.junit.SessionFactoryScope;
2822
import org.hibernate.testing.orm.junit.SkipForDialect;
23+
import org.hibernate.testing.util.uuid.IdGeneratorCreationContext;
2924
import org.junit.jupiter.api.AfterEach;
3025
import org.junit.jupiter.api.Test;
3126

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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.orm.test.mapping.identifier.uuid;
8+
9+
import org.hibernate.generator.Generator;
10+
import org.hibernate.id.uuid.UuidGenerator;
11+
import org.hibernate.id.uuid.UuidValueGenerator;
12+
import org.hibernate.mapping.BasicValue;
13+
import org.hibernate.mapping.Property;
14+
import org.hibernate.mapping.RootClass;
15+
16+
import org.hibernate.testing.orm.junit.DomainModelScope;
17+
import org.hibernate.testing.orm.junit.ServiceRegistryScope;
18+
import org.hibernate.testing.util.uuid.IdGeneratorCreationContext;
19+
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
22+
/**
23+
* @author Steve Ebersole
24+
*/
25+
public class Helper {
26+
public static void verifyAlgorithm(
27+
ServiceRegistryScope registryScope,
28+
DomainModelScope domainModelScope,
29+
RootClass descriptor,
30+
Class<? extends UuidValueGenerator> expectedAlgorithm) {
31+
final Property idProperty = descriptor.getIdentifierProperty();
32+
final BasicValue value = (BasicValue) idProperty.getValue();
33+
34+
assertThat( value.getCustomIdGeneratorCreator() ).isNotNull();
35+
final Generator generator = value.getCustomIdGeneratorCreator().createGenerator( new IdGeneratorCreationContext(
36+
registryScope.getRegistry(),
37+
domainModelScope.getDomainModel(),
38+
descriptor
39+
));
40+
41+
assertThat( generator ).isInstanceOf( UuidGenerator.class );
42+
final UuidGenerator uuidGenerator = (UuidGenerator) generator;
43+
assertThat( uuidGenerator.getValueGenerator() ).isInstanceOf( expectedAlgorithm );
44+
}
45+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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.orm.test.mapping.identifier.uuid.custom;
8+
9+
import java.util.UUID;
10+
11+
import org.hibernate.annotations.UuidGenerator;
12+
13+
import jakarta.persistence.Basic;
14+
import jakarta.persistence.Entity;
15+
import jakarta.persistence.GeneratedValue;
16+
import jakarta.persistence.Id;
17+
18+
import static org.hibernate.annotations.UuidGenerator.Style.TIME;
19+
20+
/**
21+
* @author Steve Ebersole
22+
*/
23+
//tag::example-identifiers-generators-uuid-implicit[]
24+
@Entity
25+
public class Book {
26+
@Id
27+
@GeneratedValue
28+
@UuidGenerator(algorithm = CustomUuidValueCreator.class)
29+
private UUID id;
30+
@Basic
31+
private String name;
32+
33+
//end::example-identifiers-generators-uuid-implicit[]
34+
protected Book() {
35+
// for Hibernate use
36+
}
37+
38+
public Book(String name) {
39+
this.name = name;
40+
}
41+
42+
public UUID getId() {
43+
return id;
44+
}
45+
46+
public String getName() {
47+
return name;
48+
}
49+
50+
public void setName(String name) {
51+
this.name = name;
52+
}
53+
54+
//tag::example-identifiers-generators-uuid-implicit[]
55+
}
56+
//end::example-identifiers-generators-uuid-implicit[]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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.orm.test.mapping.identifier.uuid.custom;
8+
9+
import org.hibernate.orm.test.mapping.identifier.uuid.Helper;
10+
11+
import org.hibernate.testing.orm.junit.DomainModel;
12+
import org.hibernate.testing.orm.junit.DomainModelScope;
13+
import org.hibernate.testing.orm.junit.ServiceRegistry;
14+
import org.hibernate.testing.orm.junit.ServiceRegistryScope;
15+
import org.hibernate.testing.orm.junit.SessionFactory;
16+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
17+
import org.junit.jupiter.api.Test;
18+
19+
/**
20+
* @author Steve Ebersole
21+
*/
22+
@SuppressWarnings("JUnitMalformedDeclaration")
23+
public class CustomUuidGenerationTests {
24+
@Test
25+
@ServiceRegistry
26+
@DomainModel(annotatedClasses = Book.class)
27+
void verifyModel(ServiceRegistryScope registryScope, DomainModelScope domainModelScope) {
28+
domainModelScope.withHierarchy( Book.class, (descriptor) -> {
29+
Helper.verifyAlgorithm( registryScope, domainModelScope, descriptor, CustomUuidValueCreator.class );
30+
} );
31+
}
32+
33+
@Test
34+
@ServiceRegistry
35+
@DomainModel(annotatedClasses = Book.class)
36+
@SessionFactory
37+
void testUsage(SessionFactoryScope scope) {
38+
scope.inTransaction( (session) -> {
39+
session.persist( new Book() );
40+
} );
41+
scope.inTransaction( (session) -> {
42+
session.createMutationQuery( "delete Book" ).executeUpdate();
43+
} );
44+
}
45+
46+
}

0 commit comments

Comments
 (0)