HHH-14497 - Drop legacy id-generator settings;

HHH-14718 - Drop deprecated generator implementations;
HHH-14959 - Drop IdentifierGeneratorFactory as a Service;
HHH-14960 - Add @GeneratorType for better custom generator config
This commit is contained in:
Steve Ebersole 2021-12-07 19:21:05 -06:00
parent c394261508
commit ce4f22f400
10 changed files with 427 additions and 55 deletions

View File

@ -0,0 +1,27 @@
/*
* 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.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.hibernate.id.IdentifierGenerator;
/**
* Meta-annotation used to mark another annotation as providing configuration
* for a custom {@link org.hibernate.id.IdentifierGenerator}.
*/
@Target( value = ElementType.ANNOTATION_TYPE )
@Retention( RetentionPolicy.RUNTIME )
public @interface IdGeneratorType {
/**
* The IdentifierGenerator being configured
*/
Class<? extends IdentifierGenerator> value();
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.cfg;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -22,6 +24,7 @@ import java.util.Set;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.FetchMode;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Cache;
@ -46,6 +49,7 @@ import org.hibernate.annotations.ForeignKey;
import org.hibernate.annotations.Formula;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.GenericGenerators;
import org.hibernate.annotations.IdGeneratorType;
import org.hibernate.annotations.Index;
import org.hibernate.annotations.JavaTypeRegistration;
import org.hibernate.annotations.JavaTypeRegistrations;
@ -100,6 +104,8 @@ import org.hibernate.cfg.annotations.TableBinder;
import org.hibernate.cfg.internal.NullableDiscriminatorColumnSecondPass;
import org.hibernate.engine.OptimisticLockStyle;
import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.factory.spi.CustomIdGeneratorCreationContext;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.jpa.event.internal.CallbackDefinitionResolverLegacyImpl;
@ -2544,46 +2550,75 @@ public final class AnnotationBinder {
final boolean isComponent = entityXClass.isAnnotationPresent( Embeddable.class )
|| idXProperty.isAnnotationPresent( EmbeddedId.class );
GeneratedValue generatedValue = idXProperty.getAnnotation( GeneratedValue.class );
String generatorType = generatedValue != null
? generatorType( generatedValue, buildingContext, entityXClass )
: "assigned";
String generatorName = generatedValue != null
? generatedValue.generator()
: BinderHelper.ANNOTATION_STRING_DEFAULT;
if ( isComponent ) {
//a component must not have any generator
generatorType = "assigned";
}
final Annotation generatorAnnotation = HCANNHelper.findContainingAnnotation( idXProperty, IdGeneratorType.class, buildingContext );
if ( generatorAnnotation != null ) {
final IdGeneratorType idGeneratorType = generatorAnnotation.annotationType().getAnnotation( IdGeneratorType.class );
assert idGeneratorType != null;
if ( isGlobalGeneratorNameGlobal( buildingContext ) ) {
buildGenerators( idXProperty, buildingContext );
SecondPass secondPass = new IdGeneratorResolverSecondPass(
idValue,
idXProperty,
generatorType,
generatorName,
buildingContext
);
buildingContext.getMetadataCollector().addSecondPass( secondPass );
idValue.setCustomIdGeneratorCreator( (context) -> {
final Class<? extends IdentifierGenerator> 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
);
}
} );
}
else {
//clone classGenerator and override with local values
HashMap<String, IdentifierGeneratorDefinition> localGenerators = (HashMap<String, IdentifierGeneratorDefinition>) classGenerators
.clone();
localGenerators.putAll( buildGenerators( idXProperty, buildingContext ) );
BinderHelper.makeIdGenerator(
idValue,
idXProperty,
generatorType,
generatorName,
buildingContext,
localGenerators
);
}
GeneratedValue generatedValue = idXProperty.getAnnotation( GeneratedValue.class );
if ( LOG.isTraceEnabled() ) {
LOG.tracev( "Bind {0} on {1}", ( isComponent ? "@EmbeddedId" : "@Id" ), inferredData.getPropertyName() );
String generatorType = generatedValue != null
? generatorType( generatedValue, buildingContext, entityXClass )
: "assigned";
String generatorName = generatedValue != null
? generatedValue.generator()
: BinderHelper.ANNOTATION_STRING_DEFAULT;
if ( isComponent ) {
//a component must not have any generator
generatorType = "assigned";
}
if ( isGlobalGeneratorNameGlobal( buildingContext ) ) {
buildGenerators( idXProperty, buildingContext );
SecondPass secondPass = new IdGeneratorResolverSecondPass(
idValue,
idXProperty,
generatorType,
generatorName,
buildingContext
);
buildingContext.getMetadataCollector().addSecondPass( secondPass );
}
else {
//clone classGenerator and override with local values
HashMap<String, IdentifierGeneratorDefinition> localGenerators = (HashMap<String, IdentifierGeneratorDefinition>) classGenerators
.clone();
localGenerators.putAll( buildGenerators( idXProperty, buildingContext ) );
BinderHelper.makeIdGenerator(
idValue,
idXProperty,
generatorType,
generatorName,
buildingContext,
localGenerators
);
}
if ( LOG.isTraceEnabled() ) {
LOG.tracev( "Bind {0} on {1}", ( isComponent ? "@EmbeddedId" : "@Id" ), inferredData.getPropertyName() );
}
}
}

View File

@ -13,6 +13,7 @@ import org.hibernate.Internal;
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.annotations.common.reflection.java.JavaXMember;
import org.hibernate.boot.spi.MetadataBuildingContext;
/**
* Manage the various fun-ness of dealing with HCANN...
@ -57,7 +58,7 @@ public final class HCANNHelper {
*
* @implNote Searches only one level deep
*/
static <T extends Annotation> T findAnnotation(XAnnotatedElement xAnnotatedElement, Class<T> annotationType) {
public static <T extends Annotation> T findAnnotation(XAnnotatedElement xAnnotatedElement, Class<T> annotationType) {
// first, see if we can find it directly...
final T direct = xAnnotatedElement.getAnnotation( annotationType );
if ( direct != null ) {
@ -82,4 +83,30 @@ public final class HCANNHelper {
return null;
}
/**
* Locate the annotation, relative to `xAnnotatedElement`, which contains
* the passed type of annotation.
*
* @implNote Searches only one level deep
*/
public static <A extends Annotation, T extends Annotation> A findContainingAnnotation(
XAnnotatedElement xAnnotatedElement,
Class<T> annotationType,
MetadataBuildingContext context) {
// xAnnotatedElement = id-prop
for ( int i = 0; i < xAnnotatedElement.getAnnotations().length; i++ ) {
final Annotation annotation = xAnnotatedElement.getAnnotations()[ i ];
// annotation = @Sequence
final T metaAnn = annotation.annotationType().getAnnotation( annotationType );
if ( metaAnn != null ) {
//noinspection unchecked
return (A) annotation;
}
}
return null;
}
}

View File

@ -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.id.factory.spi;
import org.hibernate.Incubating;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.mapping.RootClass;
import org.hibernate.service.ServiceRegistry;
@Incubating
public interface CustomIdGeneratorCreationContext {
IdentifierGeneratorFactory getIdentifierGeneratorFactory();
Database getDatabase();
ServiceRegistry getServiceRegistry();
String getDefaultCatalog();
String getDefaultSchema();
RootClass getRootClass();
}

View File

@ -0,0 +1,15 @@
/*
* 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.mapping;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.factory.spi.CustomIdGeneratorCreationContext;
@FunctionalInterface
public interface IdentifierGeneratorCreator {
IdentifierGenerator createGenerator(CustomIdGeneratorCreationContext context);
}

View File

@ -16,7 +16,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import jakarta.persistence.AttributeConverter;
import org.hibernate.FetchMode;
import org.hibernate.MappingException;
@ -25,6 +24,7 @@ import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.boot.spi.InFlightMetadataCollector;
@ -33,13 +33,13 @@ import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.IdentityGenerator;
import org.hibernate.id.OptimizableGenerator;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.id.factory.spi.CustomIdGeneratorCreationContext;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper;
@ -59,6 +59,8 @@ import org.hibernate.type.descriptor.jdbc.NationalizedTypeMappings;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.DynamicParameterizedType;
import jakarta.persistence.AttributeConverter;
/**
* Any value that maps to columns.
* @author Gavin King
@ -84,6 +86,7 @@ public abstract class SimpleValue implements KeyValue {
private Properties identifierGeneratorProperties;
private String identifierGeneratorStrategy = DEFAULT_ID_GEN_STRATEGY;
private String nullValue;
private Table table;
private String foreignKeyName;
private String foreignKeyDefinition;
@ -288,6 +291,7 @@ public abstract class SimpleValue implements KeyValue {
}
}
private IdentifierGeneratorCreator customIdGeneratorCreator;
private IdentifierGenerator identifierGenerator;
/**
@ -301,6 +305,14 @@ public abstract class SimpleValue implements KeyValue {
return identifierGenerator;
}
public void setCustomIdGeneratorCreator(IdentifierGeneratorCreator customIdGeneratorCreator) {
this.customIdGeneratorCreator = customIdGeneratorCreator;
}
public IdentifierGeneratorCreator getCustomIdGeneratorCreator() {
return customIdGeneratorCreator;
}
@Override
public IdentifierGenerator createIdentifierGenerator(
IdentifierGeneratorFactory identifierGeneratorFactory,
@ -316,11 +328,47 @@ public abstract class SimpleValue implements KeyValue {
String defaultCatalog,
String defaultSchema,
RootClass rootClass) throws MappingException {
if ( identifierGenerator != null ) {
return identifierGenerator;
}
if ( customIdGeneratorCreator != null ) {
final CustomIdGeneratorCreationContext creationContext = new CustomIdGeneratorCreationContext() {
@Override
public IdentifierGeneratorFactory getIdentifierGeneratorFactory() {
return identifierGeneratorFactory;
}
@Override
public Database getDatabase() {
return buildingContext.getMetadataCollector().getDatabase();
}
@Override
public ServiceRegistry getServiceRegistry() {
return buildingContext.getBootstrapContext().getServiceRegistry();
}
@Override
public String getDefaultCatalog() {
return defaultCatalog;
}
@Override
public String getDefaultSchema() {
return defaultSchema;
}
@Override
public RootClass getRootClass() {
return rootClass;
}
};
identifierGenerator = customIdGeneratorCreator.createGenerator( creationContext );
return identifierGenerator;
}
final Properties params = new Properties();
// This is for backwards compatibility only;
@ -407,14 +455,6 @@ public abstract class SimpleValue implements KeyValue {
return FetchMode.SELECT;
}
public Properties getIdentifierGeneratorProperties() {
return identifierGeneratorProperties;
}
public String getNullValue() {
return nullValue;
}
public Table getTable() {
return table;
}
@ -426,11 +466,23 @@ public abstract class SimpleValue implements KeyValue {
public String getIdentifierGeneratorStrategy() {
return identifierGeneratorStrategy;
}
/**
* Sets the identifierGeneratorStrategy.
* @param identifierGeneratorStrategy The identifierGeneratorStrategy to set
*/
public void setIdentifierGeneratorStrategy(String identifierGeneratorStrategy) {
this.identifierGeneratorStrategy = identifierGeneratorStrategy;
}
public boolean isIdentityColumn(IdentifierGeneratorFactory identifierGeneratorFactory, Dialect dialect) {
return IdentityGenerator.class.isAssignableFrom(identifierGeneratorFactory.getIdentifierGeneratorClass( identifierGeneratorStrategy ));
}
public Properties getIdentifierGeneratorProperties() {
return identifierGeneratorProperties;
}
/**
* Sets the identifierGeneratorProperties.
* @param identifierGeneratorProperties The identifierGeneratorProperties to set
@ -451,12 +503,8 @@ public abstract class SimpleValue implements KeyValue {
}
}
/**
* Sets the identifierGeneratorStrategy.
* @param identifierGeneratorStrategy The identifierGeneratorStrategy to set
*/
public void setIdentifierGeneratorStrategy(String identifierGeneratorStrategy) {
this.identifierGeneratorStrategy = identifierGeneratorStrategy;
public String getNullValue() {
return nullValue;
}
/**

View File

@ -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.custom;
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 CustomGeneratorTests {
@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" ) );
} );
}
}

View File

@ -0,0 +1,27 @@
/*
* 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.custom;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.hibernate.annotations.IdGeneratorType;
import org.hibernate.id.enhanced.Optimizer;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@IdGeneratorType( SimpleSequenceGenerator.class )
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface Sequence {
String name();
int startWith() default 1;
int incrementBy() default 50;
Class<? extends Optimizer> optimizerStrategy() default Optimizer.class;
}

View File

@ -0,0 +1,84 @@
/*
* 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.custom;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.factory.spi.CustomIdGeneratorCreationContext;
public class SimpleSequenceGenerator implements IdentifierGenerator {
private final Identifier sequenceName;
private final String sqlSelectFrag;
public SimpleSequenceGenerator(Sequence config, CustomIdGeneratorCreationContext context) {
final String name = config.name();
// ignore the other config for now...
final Database database = context.getDatabase();
final IdentifierHelper identifierHelper = database.getJdbcEnvironment().getIdentifierHelper();
final org.hibernate.boot.model.relational.Sequence sequence = database.getDefaultNamespace().createSequence(
identifierHelper.toIdentifier( name ),
(physicalName) -> new org.hibernate.boot.model.relational.Sequence(
null,
database.getDefaultNamespace().getPhysicalName().getCatalog(),
database.getDefaultNamespace().getPhysicalName().getSchema(),
physicalName,
1,
50
)
);
this.sequenceName = sequence.getName().getSequenceName();
this.sqlSelectFrag = database
.getDialect()
.getSequenceSupport()
.getSequenceNextValString( sequenceName.render( database.getDialect() ) );
}
@Override
public Object generate(SharedSessionContractImplementor session, Object object) {
try {
final PreparedStatement st = session.getJdbcCoordinator().getStatementPreparer().prepareStatement( sqlSelectFrag );
try {
final ResultSet rs = session.getJdbcCoordinator().getResultSetReturn().extract( st );
try {
rs.next();
return rs.getInt( 1 );
}
finally {
try {
session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( rs, st );
}
catch( Throwable ignore ) {
// intentionally empty
}
}
}
finally {
session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( st );
session.getJdbcCoordinator().afterStatementExecution();
}
}
catch ( SQLException sqle) {
throw session.getJdbcServices().getSqlExceptionHelper().convert(
sqle,
"could not get next sequence value",
sqlSelectFrag
);
}
}
}

View File

@ -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.custom;
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
@Sequence( name = "seq1" )
public Integer id;
@Basic
public String name;
private TheEntity() {
// for Hibernate use
}
public TheEntity(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}