HHH-18411 - Add ability to specify a custom UuidGenerator.ValueGenerator
This commit is contained in:
parent
55108d0740
commit
fdef3b52eb
|
@ -11,6 +11,9 @@ import java.lang.annotation.RetentionPolicy;
|
|||
import java.lang.annotation.Target;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
import org.hibernate.id.uuid.UuidValueGenerator;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
@ -21,9 +24,10 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||
* <p>
|
||||
* The type of the identifier attribute may be {@link UUID} or {@link String}.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*
|
||||
* @see org.hibernate.id.uuid.UuidGenerator
|
||||
* @since 6.0
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@IdGeneratorType(org.hibernate.id.uuid.UuidGenerator.class)
|
||||
@ValueGenerationType(generatedBy = org.hibernate.id.uuid.UuidGenerator.class)
|
||||
|
@ -58,4 +62,13 @@ public @interface UuidGenerator {
|
|||
* Specifies which {@linkplain Style style} of UUID generation should be used.
|
||||
*/
|
||||
Style style() default Style.AUTO;
|
||||
|
||||
/**
|
||||
* Allows to provide a specific, generally custom, value generation implementation.
|
||||
*
|
||||
* @apiNote If algorithm is specified, it is expected that {@linkplain #style()} be
|
||||
* {@linkplain Style#AUTO}.
|
||||
*/
|
||||
@Incubating
|
||||
Class<? extends UuidValueGenerator> algorithm() default UuidValueGenerator.class;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import org.hibernate.internal.util.BytesHelper;
|
|||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class CustomVersionOneStrategy implements UUIDGenerationStrategy, UuidGenerator.ValueGenerator {
|
||||
public class CustomVersionOneStrategy implements UUIDGenerationStrategy, UuidValueGenerator {
|
||||
@Override
|
||||
public int getGeneratedVersion() {
|
||||
return 1;
|
||||
|
|
|
@ -16,7 +16,7 @@ import org.hibernate.id.UUIDGenerationStrategy;
|
|||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class StandardRandomStrategy implements UUIDGenerationStrategy, UuidGenerator.ValueGenerator {
|
||||
public class StandardRandomStrategy implements UUIDGenerationStrategy, UuidValueGenerator {
|
||||
public static final StandardRandomStrategy INSTANCE = new StandardRandomStrategy();
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,39 +8,56 @@ package org.hibernate.id.uuid;
|
|||
|
||||
import java.lang.reflect.Member;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.Internal;
|
||||
import org.hibernate.MappingException;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.generator.BeforeExecutionGenerator;
|
||||
import org.hibernate.generator.EventType;
|
||||
import org.hibernate.generator.EventTypeSets;
|
||||
import org.hibernate.generator.GeneratorCreationContext;
|
||||
import org.hibernate.id.factory.spi.CustomIdGeneratorCreationContext;
|
||||
import org.hibernate.generator.BeforeExecutionGenerator;
|
||||
import org.hibernate.type.descriptor.java.UUIDJavaType;
|
||||
import org.hibernate.type.descriptor.java.UUIDJavaType.ValueTransformer;
|
||||
|
||||
import static org.hibernate.annotations.UuidGenerator.Style.AUTO;
|
||||
import static org.hibernate.annotations.UuidGenerator.Style.TIME;
|
||||
import static org.hibernate.generator.EventTypeSets.INSERT_ONLY;
|
||||
import static org.hibernate.internal.util.ReflectHelper.getPropertyType;
|
||||
|
||||
/**
|
||||
* Generates {@link UUID}s.
|
||||
* {@linkplain org.hibernate.generator.Generator} for producing {@link UUID} values.
|
||||
* <p/>
|
||||
* Uses a {@linkplain UuidValueGenerator} and {@linkplain ValueTransformer} to
|
||||
* generate the values.
|
||||
*
|
||||
* @see org.hibernate.annotations.UuidGenerator
|
||||
*/
|
||||
public class UuidGenerator implements BeforeExecutionGenerator {
|
||||
interface ValueGenerator {
|
||||
UUID generateUuid(SharedSessionContractImplementor session);
|
||||
}
|
||||
|
||||
private final ValueGenerator generator;
|
||||
private final UuidValueGenerator generator;
|
||||
private final ValueTransformer valueTransformer;
|
||||
|
||||
private UuidGenerator(
|
||||
org.hibernate.annotations.UuidGenerator config,
|
||||
Member idMember) {
|
||||
if ( config.style() == TIME ) {
|
||||
if ( config.algorithm() != UuidValueGenerator.class ) {
|
||||
if ( config.style() != AUTO ) {
|
||||
throw new MappingException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"Style [%s] should not be specified with custom UUID value generator : %s.%s",
|
||||
config.style().name(),
|
||||
idMember.getDeclaringClass().getName(),
|
||||
idMember.getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
generator = instantiateCustomGenerator( config.algorithm() );
|
||||
}
|
||||
else if ( config.style() == TIME ) {
|
||||
generator = new CustomVersionOneStrategy();
|
||||
}
|
||||
else {
|
||||
|
@ -63,6 +80,15 @@ public class UuidGenerator implements BeforeExecutionGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
private static UuidValueGenerator instantiateCustomGenerator(Class<? extends UuidValueGenerator> algorithmClass) {
|
||||
try {
|
||||
return algorithmClass.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new HibernateException( "Unable to instantiate " + algorithmClass.getName(), e );
|
||||
}
|
||||
}
|
||||
|
||||
public UuidGenerator(
|
||||
org.hibernate.annotations.UuidGenerator config,
|
||||
Member idMember,
|
||||
|
@ -89,4 +115,14 @@ public class UuidGenerator implements BeforeExecutionGenerator {
|
|||
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) {
|
||||
return valueTransformer.transform( generator.generateUuid( session ) );
|
||||
}
|
||||
|
||||
@Internal
|
||||
public UuidValueGenerator getValueGenerator() {
|
||||
return generator;
|
||||
}
|
||||
|
||||
@Internal
|
||||
public ValueTransformer getValueTransformer() {
|
||||
return valueTransformer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.util.UUID;
|
||||
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
|
||||
/**
|
||||
* Represents a specific algorithm for producing UUID values. Used in
|
||||
* conjunction with {@linkplain UuidGenerator} and
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface UuidValueGenerator {
|
||||
/**
|
||||
* Generate the UUID value
|
||||
*/
|
||||
UUID generateUuid(SharedSessionContractImplementor session);
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Basic;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Entity
|
||||
public class AnotherEntity {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@UuidGenerator( algorithm = CustomUuidValueGenerator.class )
|
||||
private UUID id;
|
||||
@Basic
|
||||
private String name;
|
||||
|
||||
protected AnotherEntity() {
|
||||
// for Hibernate use
|
||||
}
|
||||
|
||||
public AnotherEntity(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.id.uuid.UuidValueGenerator;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class CustomUuidValueGenerator implements UuidValueGenerator {
|
||||
private long counter = 0;
|
||||
|
||||
@Override
|
||||
public UUID generateUuid(SharedSessionContractImplementor session) {
|
||||
final UUID sessionIdentifier = session.getSessionIdentifier();
|
||||
return new UUID( sessionIdentifier.getMostSignificantBits(), ++counter );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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.boot.model.relational.Database;
|
||||
import org.hibernate.boot.spi.MetadataImplementor;
|
||||
import org.hibernate.id.factory.IdentifierGeneratorFactory;
|
||||
import org.hibernate.id.factory.spi.CustomIdGeneratorCreationContext;
|
||||
import org.hibernate.mapping.PersistentClass;
|
||||
import org.hibernate.mapping.Property;
|
||||
import org.hibernate.mapping.RootClass;
|
||||
import org.hibernate.service.ServiceRegistry;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class IdGeneratorCreationContext implements CustomIdGeneratorCreationContext {
|
||||
private final MetadataImplementor domainModel;
|
||||
private final RootClass entityMapping;
|
||||
|
||||
public IdGeneratorCreationContext(MetadataImplementor domainModel, RootClass entityMapping) {
|
||||
this.domainModel = domainModel;
|
||||
this.entityMapping = entityMapping;
|
||||
|
||||
assert entityMapping.getIdentifierProperty() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentifierGeneratorFactory getIdentifierGeneratorFactory() {
|
||||
return domainModel.getMetadataBuildingOptions().getIdentifierGeneratorFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootClass getRootClass() {
|
||||
return entityMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Database getDatabase() {
|
||||
return domainModel.getDatabase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceRegistry getServiceRegistry() {
|
||||
return domainModel.getMetadataBuildingOptions().getServiceRegistry();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultCatalog() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultSchema() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistentClass getPersistentClass() {
|
||||
return entityMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Property getProperty() {
|
||||
return entityMapping.getIdentifierProperty();
|
||||
}
|
||||
}
|
|
@ -6,34 +6,70 @@
|
|||
*/
|
||||
package org.hibernate.orm.test.id.uuid.annotation;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hibernate.boot.model.relational.Database;
|
||||
import org.hibernate.dialect.SybaseDialect;
|
||||
import org.hibernate.generator.Generator;
|
||||
import org.hibernate.id.factory.IdentifierGeneratorFactory;
|
||||
import org.hibernate.id.factory.spi.CustomIdGeneratorCreationContext;
|
||||
import org.hibernate.id.uuid.StandardRandomStrategy;
|
||||
import org.hibernate.id.uuid.UuidGenerator;
|
||||
import org.hibernate.mapping.BasicValue;
|
||||
import org.hibernate.mapping.PersistentClass;
|
||||
import org.hibernate.mapping.Property;
|
||||
import org.hibernate.mapping.RootClass;
|
||||
import org.hibernate.service.ServiceRegistry;
|
||||
|
||||
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.hibernate.testing.orm.junit.SkipForDialect;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DomainModel( annotatedClasses = {TheEntity.class, TheOtherEntity.class} )
|
||||
@SuppressWarnings("JUnitMalformedDeclaration")
|
||||
@DomainModel( annotatedClasses = {TheEntity.class, TheOtherEntity.class, AnotherEntity.class} )
|
||||
@SessionFactory
|
||||
@SkipForDialect( dialectClass = SybaseDialect.class, matchSubTypes = true,
|
||||
reason = "Skipped for Sybase to avoid problems with UUIDs potentially ending with a trailing 0 byte")
|
||||
public class UuidGeneratorAnnotationTests {
|
||||
@Test
|
||||
public void verifyModel(DomainModelScope scope) {
|
||||
public void verifyRandomUuidGeneratorModel(DomainModelScope scope) {
|
||||
scope.withHierarchy( TheEntity.class, (descriptor) -> {
|
||||
final Property idProperty = descriptor.getIdentifierProperty();
|
||||
final BasicValue value = (BasicValue) idProperty.getValue();
|
||||
|
||||
assertThat( value.getCustomIdGeneratorCreator() ).isNotNull();
|
||||
final Generator generator = value.getCustomIdGeneratorCreator().createGenerator( new IdGeneratorCreationContext(
|
||||
scope.getDomainModel(),
|
||||
descriptor
|
||||
));
|
||||
|
||||
final String strategy = value.getIdentifierGeneratorStrategy();
|
||||
assertThat( strategy ).isEqualTo( "assigned" );
|
||||
assertThat( generator ).isInstanceOf( UuidGenerator.class );
|
||||
final UuidGenerator uuidGenerator = (UuidGenerator) generator;
|
||||
assertThat( uuidGenerator.getValueGenerator() ).isInstanceOf( StandardRandomStrategy.class );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyCustomUuidGeneratorModel(DomainModelScope scope) {
|
||||
scope.withHierarchy( AnotherEntity.class, (descriptor) -> {
|
||||
final Property idProperty = descriptor.getIdentifierProperty();
|
||||
final BasicValue value = (BasicValue) idProperty.getValue();
|
||||
|
||||
assertThat( value.getCustomIdGeneratorCreator() ).isNotNull();
|
||||
final Generator generator = value.getCustomIdGeneratorCreator().createGenerator( new IdGeneratorCreationContext(
|
||||
scope.getDomainModel(),
|
||||
descriptor
|
||||
));
|
||||
|
||||
assertThat( generator ).isInstanceOf( UuidGenerator.class );
|
||||
final UuidGenerator uuidGenerator = (UuidGenerator) generator;
|
||||
assertThat( uuidGenerator.getValueGenerator() ).isInstanceOf( CustomUuidValueGenerator.class );
|
||||
} );
|
||||
}
|
||||
|
||||
|
@ -56,4 +92,29 @@ public class UuidGeneratorAnnotationTests {
|
|||
assertThat( gavin.id ).isNotNull();
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomValueGenerator(SessionFactoryScope sessionFactoryScope) {
|
||||
sessionFactoryScope.inTransaction( (session) -> {
|
||||
final UUID sessionIdentifier = session.getSessionIdentifier();
|
||||
|
||||
final AnotherEntity anotherEntity = new AnotherEntity( "johnny" );
|
||||
session.persist( anotherEntity );
|
||||
assertThat( anotherEntity.getId() ).isNotNull();
|
||||
|
||||
session.flush();
|
||||
assertThat( anotherEntity.getId() ).isNotNull();
|
||||
assertThat( anotherEntity.getId().getMostSignificantBits() ).isEqualTo( sessionIdentifier.getMostSignificantBits() );
|
||||
assertThat( anotherEntity.getId().getLeastSignificantBits() ).isEqualTo( 1L );
|
||||
} );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void dropTestData(SessionFactoryScope sessionFactoryScope) {
|
||||
sessionFactoryScope.inTransaction( (session) -> {
|
||||
session.createMutationQuery( "delete TheEntity" ).executeUpdate();
|
||||
session.createMutationQuery( "delete TheOtherEntity" ).executeUpdate();
|
||||
session.createMutationQuery( "delete AnotherEntity" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue