diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/UuidGenerator.java b/hibernate-core/src/main/java/org/hibernate/annotations/UuidGenerator.java new file mode 100644 index 0000000000..1b32a6c216 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/annotations/UuidGenerator.java @@ -0,0 +1,45 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.UUID; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; + +@IdGeneratorType( org.hibernate.id.uuid.UuidGenerator.class ) +@Retention(RetentionPolicy.RUNTIME) +@Target({ FIELD, METHOD }) +public @interface UuidGenerator { + + enum Style { + /** + * Defaults to {@link #RANDOM} + */ + AUTO, + /** + * Uses {@link UUID#randomUUID()} to generate values + */ + RANDOM, + /** + * Applies a time-based generation strategy consistent with IETF RFC 4122. Uses + * IP address rather than mac address. + * + * NOTE : Can be a bottleneck due to the need to synchronize in order to increment an + * internal count as part of the algorithm. + */ + TIME + } + + /** + * Which style of generation should be used + */ + Style style() default Style.AUTO; +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index c62f501df0..8c8ab60314 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -8,6 +8,7 @@ package org.hibernate.cfg; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -116,6 +117,7 @@ import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Component; import org.hibernate.mapping.Constraint; import org.hibernate.mapping.DependantValue; +import org.hibernate.mapping.IdentifierGeneratorCreator; import org.hibernate.mapping.Join; import org.hibernate.mapping.JoinedSubclass; import org.hibernate.mapping.KeyValue; @@ -2545,38 +2547,16 @@ public final class AnnotationBinder { XClass entityXClass = inferredData.getClassOrElement(); XProperty idXProperty = inferredData.getProperty(); - //manage composite related metadata - //guess if its a component and find id data access (property, field etc) - final boolean isComponent = entityXClass.isAnnotationPresent( Embeddable.class ) - || idXProperty.isAnnotationPresent( EmbeddedId.class ); - final Annotation generatorAnnotation = HCANNHelper.findContainingAnnotation( idXProperty, IdGeneratorType.class, buildingContext ); if ( generatorAnnotation != null ) { - final IdGeneratorType idGeneratorType = generatorAnnotation.annotationType().getAnnotation( IdGeneratorType.class ); - assert idGeneratorType != null; - - idValue.setCustomIdGeneratorCreator( (context) -> { - final Class generatorClass = idGeneratorType.value(); - try { - return generatorClass - .getConstructor( generatorAnnotation.annotationType(), CustomIdGeneratorCreationContext.class ) - .newInstance( generatorAnnotation, context ); - } - catch (NoSuchMethodException e) { - throw new HibernateException( - "Unable to find appropriate constructor for @IdGeneratorType handling : " + generatorClass.getName(), - e - ); - } - catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { - throw new HibernateException( - "Unable to invoke constructor for @IdGeneratorType handling : " + generatorClass.getName(), - e - ); - } - } ); + idValue.setCustomIdGeneratorCreator( new CustomIdGeneratorCreator( generatorAnnotation, idXProperty ) ); } else { + //manage composite related metadata + //guess if its a component and find id data access (property, field etc) + final boolean isComponent = entityXClass.isAnnotationPresent( Embeddable.class ) + || idXProperty.isAnnotationPresent( EmbeddedId.class ); + GeneratedValue generatedValue = idXProperty.getAnnotation( GeneratedValue.class ); String generatorType = generatedValue != null @@ -3751,4 +3731,39 @@ public final class AnnotationBinder { LOG.ignoreNotFoundWithFetchTypeLazy( entity, association ); } } + + private static class CustomIdGeneratorCreator implements IdentifierGeneratorCreator { + private final Annotation generatorAnnotation; + private final Member underlyingMember; + + public CustomIdGeneratorCreator(Annotation generatorAnnotation, XProperty idXProperty) { + this.generatorAnnotation = generatorAnnotation; + this.underlyingMember = HCANNHelper.getUnderlyingMember( idXProperty ); + } + + @Override + public IdentifierGenerator createGenerator(CustomIdGeneratorCreationContext context) { + final IdGeneratorType idGeneratorType = generatorAnnotation.annotationType().getAnnotation( IdGeneratorType.class ); + assert idGeneratorType != null; + + final Class generatorClass = idGeneratorType.value(); + try { + return generatorClass + .getConstructor( generatorAnnotation.annotationType(), Member.class, CustomIdGeneratorCreationContext.class ) + .newInstance( generatorAnnotation, underlyingMember, context ); + } + catch (NoSuchMethodException e) { + throw new HibernateException( + "Unable to find appropriate constructor for @IdGeneratorType handling : " + generatorClass.getName(), + e + ); + } + catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new HibernateException( + "Unable to invoke constructor for @IdGeneratorType handling : " + generatorClass.getName(), + e + ); + } + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/UUIDGenerationStrategy.java b/hibernate-core/src/main/java/org/hibernate/id/UUIDGenerationStrategy.java index 06450b89d1..5fd32bfc6f 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/UUIDGenerationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/id/UUIDGenerationStrategy.java @@ -14,8 +14,9 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; /** * A strategy for generating a variant 2 {@link UUID} value. * - * @author Steve Ebersole + * @deprecated see {@link UUIDGenerator} */ +@Deprecated public interface UUIDGenerationStrategy extends Serializable { /** * Which variant, according to IETF RFC 4122, of UUID does this strategy generate? RFC 4122 defines diff --git a/hibernate-core/src/main/java/org/hibernate/id/UUIDGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/UUIDGenerator.java index 0fd0c3443d..be17a2ec5c 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/UUIDGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/UUIDGenerator.java @@ -36,8 +36,10 @@ import org.hibernate.type.descriptor.java.UUIDJavaTypeDescriptor; *
  • {@link org.hibernate.id.uuid.CustomVersionOneStrategy}
  • * * - * @author Steve Ebersole + * @deprecated (since 6.0) - use {@link org.hibernate.id.uuid.UuidGenerator} and + * {@link org.hibernate.annotations.UuidGenerator} instead */ +@Deprecated public class UUIDGenerator implements IdentifierGenerator { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( UUIDGenerator.class ); diff --git a/hibernate-core/src/main/java/org/hibernate/id/uuid/CustomVersionOneStrategy.java b/hibernate-core/src/main/java/org/hibernate/id/uuid/CustomVersionOneStrategy.java index 4293b5cc0b..e8a5f2444e 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/uuid/CustomVersionOneStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/id/uuid/CustomVersionOneStrategy.java @@ -23,7 +23,7 @@ import org.hibernate.internal.util.BytesHelper; * * @author Steve Ebersole */ -public class CustomVersionOneStrategy implements UUIDGenerationStrategy { +public class CustomVersionOneStrategy implements UUIDGenerationStrategy, UuidGenerator.ValueGenerator { @Override public int getGeneratedVersion() { return 1; @@ -44,12 +44,18 @@ public class CustomVersionOneStrategy implements UUIDGenerationStrategy { mostSignificantBits = BytesHelper.asLong( hiBits ); } + @Override - public UUID generateUUID(SharedSessionContractImplementor session) { + public UUID generateUuid(SharedSessionContractImplementor session) { long leastSignificantBits = generateLeastSignificantBits( System.currentTimeMillis() ); return new UUID( mostSignificantBits, leastSignificantBits ); } + @Override + public UUID generateUUID(SharedSessionContractImplementor session) { + return generateUuid( session ); + } + public long getMostSignificantBits() { return mostSignificantBits; } diff --git a/hibernate-core/src/main/java/org/hibernate/id/uuid/StandardRandomStrategy.java b/hibernate-core/src/main/java/org/hibernate/id/uuid/StandardRandomStrategy.java index c4ba650b39..24536568db 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/uuid/StandardRandomStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/id/uuid/StandardRandomStrategy.java @@ -16,7 +16,7 @@ import org.hibernate.id.UUIDGenerationStrategy; * * @author Steve Ebersole */ -public class StandardRandomStrategy implements UUIDGenerationStrategy { +public class StandardRandomStrategy implements UUIDGenerationStrategy, UuidGenerator.ValueGenerator { public static final StandardRandomStrategy INSTANCE = new StandardRandomStrategy(); /** @@ -33,6 +33,11 @@ public class StandardRandomStrategy implements UUIDGenerationStrategy { */ @Override public UUID generateUUID(SharedSessionContractImplementor session) { + return generateUuid( session ); + } + + @Override + public UUID generateUuid(SharedSessionContractImplementor session) { return UUID.randomUUID(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidGenerator.java new file mode 100644 index 0000000000..57ed7fa1ff --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidGenerator.java @@ -0,0 +1,72 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.id.uuid; + +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.UUID; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.id.IdentifierGenerator; +import org.hibernate.id.factory.spi.CustomIdGeneratorCreationContext; +import org.hibernate.type.descriptor.java.UUIDJavaTypeDescriptor; +import org.hibernate.type.descriptor.java.UUIDJavaTypeDescriptor.ValueTransformer; + +import static org.hibernate.annotations.UuidGenerator.Style.TIME; + +/** + * UUID-based IdentifierGenerator + * + * @see org.hibernate.annotations.UuidGenerator + */ +public class UuidGenerator implements IdentifierGenerator { + interface ValueGenerator { + UUID generateUuid(SharedSessionContractImplementor session); + } + + private final ValueGenerator generator; + private final ValueTransformer valueTransformer; + + public UuidGenerator( + org.hibernate.annotations.UuidGenerator config, + Member idMember, + CustomIdGeneratorCreationContext creationContext) { + if ( config.style() == TIME ) { + generator = new CustomVersionOneStrategy(); + } + else { + generator = StandardRandomStrategy.INSTANCE; + } + + final Class propertyType; + if ( idMember instanceof Method ) { + propertyType = ( (Method) idMember ).getReturnType(); + } + else { + propertyType = ( (Field) idMember ).getType(); + } + + if ( UUID.class.isAssignableFrom( propertyType ) ) { + valueTransformer = UUIDJavaTypeDescriptor.PassThroughTransformer.INSTANCE; + } + else if ( String.class.isAssignableFrom( propertyType ) ) { + valueTransformer = UUIDJavaTypeDescriptor.ToStringTransformer.INSTANCE; + } + else if ( byte[].class.isAssignableFrom( propertyType ) ) { + valueTransformer = UUIDJavaTypeDescriptor.ToBytesTransformer.INSTANCE; + } + else { + throw new HibernateException( "Unanticipated return type [" + propertyType.getName() + "] for UUID conversion" ); + } + } + + public Object generate(SharedSessionContractImplementor session, Object object) throws HibernateException { + return valueTransformer.transform( generator.generateUuid( session ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/custom/CustomGeneratorTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/custom/CustomGeneratorTests.java index 879122fc34..7d9d5a2657 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/custom/CustomGeneratorTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/custom/CustomGeneratorTests.java @@ -35,8 +35,12 @@ public class CustomGeneratorTests { @Test public void basicUseTest(SessionFactoryScope scope) { + assertThat( SimpleSequenceGenerator.generationCount ).isEqualTo( 0 ); + scope.inTransaction( (session) -> { session.persist( new TheEntity( "steve" ) ); } ); + + assertThat( SimpleSequenceGenerator.generationCount ).isEqualTo( 1 ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/custom/SimpleSequenceGenerator.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/custom/SimpleSequenceGenerator.java index 3771d5534f..ee2318e31d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/custom/SimpleSequenceGenerator.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/custom/SimpleSequenceGenerator.java @@ -6,6 +6,7 @@ */ package org.hibernate.orm.test.id.custom; +import java.lang.reflect.Member; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -17,11 +18,16 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.factory.spi.CustomIdGeneratorCreationContext; +/** + * An example custom generator. + */ public class SimpleSequenceGenerator implements IdentifierGenerator { + public static int generationCount = 0; + private final Identifier sequenceName; private final String sqlSelectFrag; - public SimpleSequenceGenerator(Sequence config, CustomIdGeneratorCreationContext context) { + public SimpleSequenceGenerator(Sequence config, Member annotatedMember, CustomIdGeneratorCreationContext context) { final String name = config.name(); // ignore the other config for now... @@ -50,6 +56,7 @@ public class SimpleSequenceGenerator implements IdentifierGenerator { @Override public Object generate(SharedSessionContractImplementor session, Object object) { + generationCount++; try { final PreparedStatement st = session.getJdbcCoordinator().getStatementPreparer().prepareStatement( sqlSelectFrag ); try { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/annotation/TheEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/annotation/TheEntity.java new file mode 100644 index 0000000000..621ebd2d0c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/annotation/TheEntity.java @@ -0,0 +1,46 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.id.uuid.annotation; + +import java.util.UUID; + +import org.hibernate.annotations.UuidGenerator; + +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Id; +import jakarta.persistence.Basic; + +@Entity(name = "TheEntity") +@Table(name = "TheEntity") +public class TheEntity { + @Id + @UuidGenerator + public UUID id; + @Basic + public String name; + + private TheEntity() { + // for Hibernate use + } + + public TheEntity(String name) { + this.name = name; + } + + public UUID getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/annotation/UuidGeneratorAnnotationTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/annotation/UuidGeneratorAnnotationTests.java new file mode 100644 index 0000000000..bd41bee061 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/annotation/UuidGeneratorAnnotationTests.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.id.uuid.annotation; + +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.Property; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@DomainModel( annotatedClasses = TheEntity.class ) +@SessionFactory +public class UuidGeneratorAnnotationTests { + @Test + public void verifyModel(DomainModelScope scope) { + scope.withHierarchy( TheEntity.class, (descriptor) -> { + final Property idProperty = descriptor.getIdentifierProperty(); + final BasicValue value = (BasicValue) idProperty.getValue(); + + assertThat( value.getCustomIdGeneratorCreator() ).isNotNull(); + + final String strategy = value.getIdentifierGeneratorStrategy(); + assertThat( strategy ).isEqualTo( "assigned" ); + } ); + } + + @Test + public void basicUseTest(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TheEntity( "steve" ) ); + } ); + } +}