From 4b6822a8bcb946d876ef03ab08d0ba222c788840 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 12 Sep 2024 13:08:40 -0500 Subject: [PATCH] HHH-18498 - Support for unnamed generators HHH-18593 - Enforce GeneratedValue GenerationType HHH-18609 - Use UuidGenerator for GenerationType.UUID --- .../InFlightMetadataCollectorImpl.java | 16 +- .../boot/model/internal/AnnotationBinder.java | 7 +- .../boot/model/internal/CollectionBinder.java | 26 +- .../boot/model/internal/EmbeddableBinder.java | 2 - .../boot/model/internal/EntityBinder.java | 7 +- .../internal/GeneratorAnnotationHelper.java | 252 ++++++++ .../boot/model/internal/GeneratorBinder.java | 314 ++++++++-- .../model/internal/GeneratorParameters.java | 103 ++- .../model/internal/GeneratorStrategies.java | 42 ++ .../boot/model/internal/IdBagBinder.java | 3 +- .../IdBagIdGeneratorResolverSecondPass.java | 303 +++++++++ .../model/internal/IdGeneratorResolver.java | 17 + .../IdGeneratorResolverSecondPass.java | 588 ++++++++++++++++-- .../boot/model/internal/PropertyBinder.java | 9 - .../StrictIdGeneratorResolverSecondPass.java | 365 +++++++++++ .../SequenceGeneratorJpaAnnotation.java | 13 +- .../internal/TableGeneratorJpaAnnotation.java | 13 +- .../DomainModelCategorizationCollector.java | 4 +- .../internal/GlobalRegistrationsImpl.java | 144 ++++- .../boot/models/spi/GlobalRegistrar.java | 16 + .../boot/models/spi/GlobalRegistrations.java | 2 + .../id/enhanced/SequenceStyleGenerator.java | 30 +- .../hibernate/id/enhanced/TableGenerator.java | 36 ++ .../org/hibernate/id/uuid/UuidGenerator.java | 44 +- .../util/config/ConfigurationHelper.java | 12 + .../id/IdClassAndAssociationsTest.java | 6 +- .../NewGeneratorMappingsTest.java | 5 +- .../{ => sub}/DedicatedSequenceEntity1.java | 4 +- .../{ => sub}/DedicatedSequenceEntity2.java | 4 +- .../{ => sub}/package-info.java | 4 +- .../id/generators/SimpleIdTests.java | 141 +++++ .../id/generators/UnnamedGeneratorTests.java | 177 ++++++ .../entity/ClassLevelGeneratorTest.java | 32 +- ...DefaultedGeneratorWithGlobalScopeTest.java | 58 ++ .../entity/FieldLevelGeneratorTest.java | 10 +- .../pkg/PackageLevelGeneratorTest.java | 44 +- .../test/annotations/id/sequences/IdTest.java | 4 +- .../DefaultCatalogAndSchemaTest.java | 12 +- .../orm/test/id/uuid/GeneratedValueTest.java | 6 +- .../auto/AutoGenerationTypeTests.java | 10 +- .../HiLoSequenceMismatchStrategyTest.java | 2 +- .../implicit/ImplicitUuidGenerationTests.java | 1 - .../Implicit2UuidGenerationTests.java | 2 - .../Implicit3UuidGenerationTests.java | 1 - local-build-plugins/settings.gradle | 1 - 45 files changed, 2644 insertions(+), 248 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorAnnotationHelper.java create mode 100644 hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdBagIdGeneratorResolverSecondPass.java create mode 100644 hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdGeneratorResolver.java create mode 100644 hibernate-core/src/main/java/org/hibernate/boot/model/internal/StrictIdGeneratorResolverSecondPass.java create mode 100644 hibernate-core/src/main/java/org/hibernate/boot/models/spi/GlobalRegistrar.java rename hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/{ => sub}/DedicatedSequenceEntity1.java (88%) rename hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/{ => sub}/DedicatedSequenceEntity2.java (88%) rename hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/{ => sub}/package-info.java (86%) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/SimpleIdTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/UnnamedGeneratorTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/entity/DefaultedGeneratorWithGlobalScopeTest.java delete mode 100644 local-build-plugins/settings.gradle diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java index 9cba36ed20..6ffd69ac20 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java @@ -46,7 +46,8 @@ import org.hibernate.boot.model.internal.AggregateComponentSecondPass; import org.hibernate.boot.model.internal.AnnotatedClassType; import org.hibernate.boot.model.internal.CreateKeySecondPass; import org.hibernate.boot.model.internal.FkSecondPass; -import org.hibernate.boot.model.internal.IdGeneratorResolverSecondPass; +import org.hibernate.boot.model.internal.IdBagIdGeneratorResolverSecondPass; +import org.hibernate.boot.model.internal.IdGeneratorResolver; import org.hibernate.boot.model.internal.ImplicitToOneJoinTableSecondPass; import org.hibernate.boot.model.internal.OptionalDeterminationSecondPass; import org.hibernate.boot.model.internal.QuerySecondPass; @@ -714,9 +715,14 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, throw new IllegalArgumentException( "ID generator object or name is null." ); } + if ( generator.getName().isEmpty() ) { + return; + } + if ( defaultIdentifierGeneratorNames.contains( generator.getName() ) ) { return; } + final IdentifierGeneratorDefinition old = idGeneratorDefinitionMap.put( generator.getName(), generator ); if ( old != null && !old.equals( generator ) ) { if ( bootstrapContext.getJpaCompliance().isGlobalGeneratorScopeEnabled() ) { @@ -1675,7 +1681,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, } } - private ArrayList idGeneratorResolverSecondPassList; + private ArrayList idGeneratorResolverSecondPassList; private ArrayList setBasicValueTypeSecondPassList; private ArrayList aggregateComponentSecondPassList; private ArrayList fkSecondPassList; @@ -1696,8 +1702,8 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, @Override public void addSecondPass(SecondPass secondPass, boolean onTopOfTheQueue) { - if ( secondPass instanceof IdGeneratorResolverSecondPass ) { - addIdGeneratorResolverSecondPass( (IdGeneratorResolverSecondPass) secondPass, onTopOfTheQueue ); + if ( secondPass instanceof IdGeneratorResolver ) { + addIdGeneratorResolverSecondPass( (IdGeneratorResolver) secondPass, onTopOfTheQueue ); } else if ( secondPass instanceof SetBasicValueTypeSecondPass ) { addSetBasicValueTypeSecondPass( (SetBasicValueTypeSecondPass) secondPass, onTopOfTheQueue ); @@ -1764,7 +1770,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, addSecondPass( secondPass, aggregateComponentSecondPassList, onTopOfTheQueue ); } - private void addIdGeneratorResolverSecondPass(IdGeneratorResolverSecondPass secondPass, boolean onTopOfTheQueue) { + private void addIdGeneratorResolverSecondPass(IdGeneratorResolver secondPass, boolean onTopOfTheQueue) { if ( idGeneratorResolverSecondPassList == null ) { idGeneratorResolverSecondPassList = new ArrayList<>(); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationBinder.java index eca5c01d55..1861eb0729 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotationBinder.java @@ -49,7 +49,6 @@ import jakarta.persistence.Table; import static org.hibernate.boot.model.internal.AnnotatedClassType.EMBEDDABLE; import static org.hibernate.boot.model.internal.AnnotatedClassType.ENTITY; import static org.hibernate.boot.model.internal.FilterDefBinder.bindFilterDefs; -import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators; import static org.hibernate.boot.model.internal.GeneratorParameters.interpretSequenceGenerator; import static org.hibernate.boot.model.internal.GeneratorParameters.interpretTableGenerator; import static org.hibernate.boot.model.internal.InheritanceState.getInheritanceStateOfSuperEntity; @@ -132,7 +131,7 @@ public final class AnnotationBinder { sourceContext( context ).getClassDetailsRegistry() .resolveClassDetails( pack.getName() + ".package-info" ); - buildGenerators( packageInfoClassDetails, context ); + GeneratorBinder.registerGlobalGenerators( packageInfoClassDetails, context ); bindTypeDescriptorRegistrations( packageInfoClassDetails, context ); bindEmbeddableInstantiatorRegistrations( packageInfoClassDetails, context ); @@ -218,9 +217,9 @@ public final class AnnotationBinder { bindConverterRegistrations( classDetails, context ); // try to find class level generators - final Map generators = buildGenerators( classDetails, context ); +// GeneratorBinder.registerGlobalGenerators( classDetails, context ); if ( context.getMetadataCollector().getClassType( classDetails ) == ENTITY ) { - EntityBinder.bindEntityClass( classDetails, inheritanceStatePerClass, generators, context ); + EntityBinder.bindEntityClass( classDetails, inheritanceStatePerClass, context ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java index 9f8419a644..061906a1bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java @@ -169,7 +169,7 @@ import static org.hibernate.boot.model.internal.BinderHelper.isDefault; import static org.hibernate.boot.model.internal.BinderHelper.isPrimitive; import static org.hibernate.boot.model.internal.DialectOverridesAnnotationHelper.getOverridableAnnotation; import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable; -import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators; +import static org.hibernate.boot.model.internal.GeneratorBinder.visitIdGeneratorDefinitions; import static org.hibernate.boot.model.internal.PropertyHolderBuilder.buildPropertyHolder; import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle; import static org.hibernate.internal.util.ReflectHelper.getDefaultSupplier; @@ -263,7 +263,6 @@ public abstract class CollectionBinder { PropertyHolder propertyHolder, Nullability nullability, PropertyData inferredData, - Map classGenerators, EntityBinder entityBinder, boolean isIdentifierMapper, MetadataBuildingContext context, @@ -358,9 +357,26 @@ public abstract class CollectionBinder { if ( property.hasAnnotationUsage( CollectionId.class, sourceModelContext ) ) { //do not compute the generators unless necessary - final HashMap localGenerators = new HashMap<>(classGenerators); - localGenerators.putAll( buildGenerators( property, context ) ); - collectionBinder.setLocalGenerators( localGenerators ); + final HashMap availableGenerators = new HashMap<>(); + visitIdGeneratorDefinitions( + property.getDeclaringType(), + definition -> { + if ( !definition.getName().isEmpty() ) { + availableGenerators.put( definition.getName(), definition ); + } + }, + context + ); + visitIdGeneratorDefinitions( + property, + definition -> { + if ( !definition.getName().isEmpty() ) { + availableGenerators.put( definition.getName(), definition ); + } + }, + context + ); + collectionBinder.setLocalGenerators( availableGenerators ); } collectionBinder.bind(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java index d20ca7634b..7841ff1f51 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java @@ -461,7 +461,6 @@ public class EmbeddableBinder { ? Nullability.FORCED_NULL : ( isNullable ? Nullability.NO_CONSTRAINT : Nullability.FORCED_NOT_NULL ), propertyAnnotatedElement, - Map.of(), entityBinder, isIdentifierMapper, isComponentEmbedded, @@ -480,7 +479,6 @@ public class EmbeddableBinder { subholder, propertyAnnotatedElement, value, - Map.of(), context ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java index b90c13ebd4..08d98cb50c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java @@ -213,7 +213,6 @@ public class EntityBinder { public static void bindEntityClass( ClassDetails clazzToProcess, Map inheritanceStates, - Map generators, MetadataBuildingContext context) { if ( LOG.isDebugEnabled() ) { LOG.debugf( "Binding entity from annotated class: %s", clazzToProcess.getName() ); @@ -242,7 +241,7 @@ public class EntityBinder { inheritanceStates ); entityBinder.handleInheritance( inheritanceState, superEntity, holder ); - entityBinder.handleIdentifier( holder, inheritanceStates, generators, inheritanceState ); + entityBinder.handleIdentifier( holder, inheritanceStates, inheritanceState ); final InFlightMetadataCollector collector = context.getMetadataCollector(); if ( persistentClass instanceof RootClass rootClass ) { @@ -396,7 +395,6 @@ public class EntityBinder { private void handleIdentifier( PropertyHolder propertyHolder, Map inheritanceStates, - Map generators, InheritanceState inheritanceState) { final ElementsToProcess elementsToProcess = inheritanceState.postProcess( persistentClass, this ); final Set idPropertiesIfIdClass = handleIdClass( @@ -412,7 +410,6 @@ public class EntityBinder { inheritanceState, context, propertyHolder, - generators, idPropertiesIfIdClass, elementsToProcess, inheritanceStates @@ -1032,7 +1029,6 @@ public class EntityBinder { InheritanceState inheritanceState, MetadataBuildingContext context, PropertyHolder propertyHolder, - Map generators, Set idPropertiesIfIdClass, ElementsToProcess elementsToProcess, Map inheritanceStates) { @@ -1063,7 +1059,6 @@ public class EntityBinder { ? Nullability.FORCED_NULL : Nullability.NO_CONSTRAINT, propertyAnnotatedElement, - generators, this, false, false, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorAnnotationHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorAnnotationHelper.java new file mode 100644 index 0000000000..16bde81500 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorAnnotationHelper.java @@ -0,0 +1,252 @@ +/* + * 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.boot.model.internal; + +import java.lang.annotation.Annotation; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.hibernate.boot.model.IdentifierGeneratorDefinition; +import org.hibernate.boot.models.HibernateAnnotations; +import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.generator.Generator; +import org.hibernate.id.IdentifierGenerator; +import org.hibernate.id.IdentityGenerator; +import org.hibernate.id.PersistentIdentifierGenerator; +import org.hibernate.id.uuid.UuidGenerator; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.RootClass; +import org.hibernate.mapping.SimpleValue; +import org.hibernate.models.spi.AnnotationDescriptor; +import org.hibernate.models.spi.ClassDetails; +import org.hibernate.models.spi.MemberDetails; +import org.hibernate.models.spi.SourceModelBuildingContext; + +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.TableGenerator; + +import static org.hibernate.id.IdentifierGenerator.GENERATOR_NAME; +import static org.hibernate.internal.util.config.ConfigurationHelper.setIfNotEmpty; + +/** + * Helper for dealing with generators defined via annotations + * + * @author Steve Ebersole + */ +public class GeneratorAnnotationHelper { + public static A findLocalizedMatch( + AnnotationDescriptor generatorAnnotationType, + MemberDetails idMember, + Function nameExtractor, + String matchName, + MetadataBuildingContext context) { + final SourceModelBuildingContext sourceModelContext = context.getMetadataCollector().getSourceModelBuildingContext(); + + A possibleMatch = null; + + // first we look on the member + for ( A generatorAnnotation : idMember.getRepeatedAnnotationUsages( generatorAnnotationType, sourceModelContext ) ) { + if ( nameExtractor != null ) { + final String registrationName = nameExtractor.apply( generatorAnnotation ); + if ( registrationName.isEmpty() ) { + possibleMatch = generatorAnnotation; + continue; + } + + if ( registrationName.equals( matchName ) ) { + return generatorAnnotation; + } + } + else { + return generatorAnnotation; + } + } + + // next, on the class + for ( A generatorAnnotation : idMember.getDeclaringType().getRepeatedAnnotationUsages( generatorAnnotationType, sourceModelContext ) ) { + if ( nameExtractor != null ) { + final String registrationName = nameExtractor.apply( generatorAnnotation ); + if ( registrationName.isEmpty() ) { + if ( possibleMatch == null ) { + possibleMatch = generatorAnnotation; + } + continue; + } + + if ( registrationName.equals( matchName ) ) { + return generatorAnnotation; + } + } + else { + return generatorAnnotation; + } + } + + // lastly, on the package + final String packageInfoFqn = StringHelper.qualifier( idMember.getDeclaringType().getClassName() ) + ".package-info"; + try { + final ClassDetails packageInfo = context.getMetadataCollector() + .getSourceModelBuildingContext() + .getClassDetailsRegistry() + .resolveClassDetails( packageInfoFqn ); + for ( A generatorAnnotation : packageInfo.getRepeatedAnnotationUsages( generatorAnnotationType, sourceModelContext ) ) { + if ( nameExtractor != null ) { + final String registrationName = nameExtractor.apply( generatorAnnotation ); + if ( registrationName.isEmpty() ) { + if ( possibleMatch == null ) { + possibleMatch = generatorAnnotation; + } + continue; + } + + if ( registrationName.equals( matchName ) ) { + return generatorAnnotation; + } + } + else { + return generatorAnnotation; + } + } + } + catch (ClassLoadingException e) { + // means there is no package-info + } + + return possibleMatch; + } + + public static void handleUuidStrategy( + SimpleValue idValue, + MemberDetails idMember, + MetadataBuildingContext context) { + final org.hibernate.annotations.UuidGenerator generatorConfig = findLocalizedMatch( + HibernateAnnotations.UUID_GENERATOR, + idMember, + null, + null, + context + ); + idValue.setCustomIdGeneratorCreator( (creationContext) -> new UuidGenerator( generatorConfig, idMember ) ); + } + + public static void handleIdentityStrategy( + SimpleValue idValue, + MemberDetails idMember, + MetadataBuildingContext context) { + idValue.setCustomIdGeneratorCreator( (creationContext) -> new IdentityGenerator() ); + idValue.setColumnToIdentity(); + } + + public static void applyBaselineConfiguration( + SequenceGenerator generatorConfig, + SimpleValue idValue, + RootClass rootClass, + MetadataBuildingContext context, + BiConsumer configurationCollector) { + if ( generatorConfig != null && !generatorConfig.name().isEmpty() ) { + configurationCollector.accept( GENERATOR_NAME, generatorConfig.name() ); + } + + GeneratorParameters.collectParameters( + idValue, + context.getMetadataCollector().getDatabase().getDialect(), + rootClass, + configurationCollector + ); + + } + + static void applyBaselineConfiguration( + TableGenerator generatorConfig, + SimpleValue idValue, + RootClass rootClass, + MetadataBuildingContext context, + BiConsumer configurationCollector) { + if ( !generatorConfig.name().isEmpty() ) { + configurationCollector.accept( GENERATOR_NAME, generatorConfig.name() ); + } + + GeneratorParameters.collectParameters( + idValue, + context.getMetadataCollector().getDatabase().getDialect(), + rootClass, + configurationCollector + ); + + } + + public static void handleGenericGenerator( + String generatorName, + GenericGenerator generatorConfig, + PersistentClass entityMapping, + SimpleValue idValue, + MemberDetails idMember, + MetadataBuildingContext context) { + //generator settings + final Map configuration = new HashMap<>(); + setIfNotEmpty( generatorConfig.name(), IdentifierGenerator.GENERATOR_NAME, configuration ); + configuration.put( IdentifierGenerator.ENTITY_NAME, entityMapping.getEntityName() ); + configuration.put( IdentifierGenerator.JPA_ENTITY_NAME, entityMapping.getJpaEntityName() ); + + applyAnnotationParameters( generatorConfig, configuration ); + + configuration.put( PersistentIdentifierGenerator.TABLE, idValue.getTable().getName() ); + if ( idValue.getColumnSpan() == 1 ) { + configuration.put( PersistentIdentifierGenerator.PK, idValue.getColumns().get(0).getName() ); + } + + GeneratorBinder.createGeneratorFrom( + new IdentifierGeneratorDefinition( generatorName, determineStrategyName( generatorConfig ), configuration ), + idMember, + idValue, + entityMapping, + context + ); + } + + private static String determineStrategyName(GenericGenerator generatorConfig) { + final Class type = generatorConfig.type(); + if ( !Objects.equals( type, Generator.class ) ) { + return type.getName(); + } + return generatorConfig.strategy(); + } + + private static void applyAnnotationParameters(GenericGenerator generatorConfig, Map configuration) { + for ( Parameter parameter : generatorConfig.parameters() ) { + configuration.put( parameter.name(), parameter.value() ); + } + } + + public static void handleTableGenerator( + String generatorName, + TableGenerator generatorConfig, + PersistentClass entityMapping, + SimpleValue idValue, + MemberDetails idMember, + MetadataBuildingContext context) { + final Map configuration = new HashMap<>(); + applyBaselineConfiguration( generatorConfig, idValue, entityMapping.getRootClass(), context, configuration::put ); + org.hibernate.id.enhanced.TableGenerator.applyConfiguration( generatorConfig, idValue, configuration::put ); + + GeneratorBinder.createGeneratorFrom( + new IdentifierGeneratorDefinition( generatorName, org.hibernate.id.enhanced.TableGenerator.class.getName(), configuration ), + idMember, + idValue, + entityMapping, + context + ); + + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java index d9be5dbd66..cdfdfc2ea2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java @@ -6,11 +6,18 @@ */ package org.hibernate.boot.model.internal; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.SequenceGenerator; -import jakarta.persistence.TableGenerator; -import jakarta.persistence.Version; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.MappingException; @@ -20,6 +27,7 @@ import org.hibernate.annotations.ValueGenerationType; import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.relational.ExportableProducer; import org.hibernate.boot.model.source.internal.hbm.MappingDocument; +import org.hibernate.boot.models.spi.GlobalRegistrar; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.PropertyData; @@ -33,10 +41,14 @@ import org.hibernate.id.Configurable; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.IdentityGenerator; import org.hibernate.id.PersistentIdentifierGenerator; +import org.hibernate.id.enhanced.SequenceStyleGenerator; +import org.hibernate.id.uuid.UuidValueGenerator; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.mapping.GeneratorCreator; +import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.SimpleValue; import org.hibernate.models.spi.AnnotationTarget; import org.hibernate.models.spi.MemberDetails; @@ -46,15 +58,13 @@ import org.hibernate.resource.beans.spi.BeanInstanceProducer; import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.service.ServiceRegistry; -import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Member; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Properties; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.TableGenerator; +import jakarta.persistence.Version; +import static jakarta.persistence.GenerationType.AUTO; import static java.util.Collections.emptyMap; import static org.hibernate.boot.model.internal.AnnotationHelper.extractParameterMap; import static org.hibernate.boot.model.internal.BinderHelper.getPath; @@ -63,7 +73,7 @@ import static org.hibernate.boot.model.internal.GeneratorParameters.collectParam import static org.hibernate.boot.model.internal.GeneratorParameters.interpretSequenceGenerator; import static org.hibernate.boot.model.internal.GeneratorParameters.interpretTableGenerator; import static org.hibernate.boot.model.internal.GeneratorStrategies.generatorClass; -import static org.hibernate.boot.model.internal.GeneratorStrategies.generatorStrategy; +import static org.hibernate.id.IdentifierGenerator.GENERATOR_NAME; import static org.hibernate.internal.util.NullnessUtil.castNonNull; import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.resource.beans.internal.Helper.allowExtensionsInCdi; @@ -94,7 +104,7 @@ public class GeneratorBinder { * Create a generator, based on a {@link GeneratedValue} annotation. */ public static void makeIdGenerator( - SimpleValue id, + SimpleValue identifierValue, MemberDetails idAttributeMember, String generatorType, String generatorName, @@ -103,15 +113,105 @@ public class GeneratorBinder { //generator settings final Map configuration = new HashMap<>(); - configuration.put( IdentifierGenerator.GENERATOR_NAME, generatorName ); - configuration.put( PersistentIdentifierGenerator.TABLE, id.getTable().getName() ); - if ( id.getColumnSpan() == 1 ) { - configuration.put( PersistentIdentifierGenerator.PK, id.getColumns().get(0).getName() ); + configuration.put( GENERATOR_NAME, generatorName ); + configuration.put( PersistentIdentifierGenerator.TABLE, identifierValue.getTable().getName() ); + if ( identifierValue.getColumnSpan() == 1 ) { + configuration.put( PersistentIdentifierGenerator.PK, identifierValue.getColumns().get(0).getName() ); } - final String generatorStrategy = - determineStrategy( idAttributeMember, generatorType, generatorName, context, localGenerators, configuration ); - setGeneratorCreator( id, configuration, generatorStrategy, context ); + if ( generatorName.isEmpty() ) { + final GeneratedValue generatedValue = idAttributeMember.getDirectAnnotationUsage( GeneratedValue.class ); + if ( generatedValue != null ) { + // The mapping used @GeneratedValue but specified no name. This is a special case added in JPA 3.2. + // Look for a matching "implied generator" based on the GenerationType + + final GenerationType strategy = generatedValue.strategy(); + final String strategyGeneratorClassName = correspondingGeneratorName( strategy ); + + final IdentifierGeneratorDefinition impliedGenerator = determineImpliedGenerator( + strategy, + strategyGeneratorClassName, + localGenerators + ); + + if ( impliedGenerator != null ) { + configuration.putAll( impliedGenerator.getParameters() ); + + final BeanContainer beanContainer = beanContainer( context ); + identifierValue.setCustomIdGeneratorCreator( creationContext -> { + final Generator identifierGenerator = instantiateGenerator( + beanContainer, + generatorClass( strategyGeneratorClassName, identifierValue ) + ); + callConfigure( creationContext, identifierGenerator, configuration, identifierValue ); + if ( identifierGenerator instanceof IdentityGenerator) { + identifierValue.setColumnToIdentity(); + } + return identifierGenerator; + } ); + + return; + } + } + } + + final String generatorStrategy = determineStrategy( + idAttributeMember, + generatorType, + generatorName, + context, + localGenerators, + configuration + ); + setGeneratorCreator( identifierValue, configuration, generatorStrategy, context ); + } + + private static IdentifierGeneratorDefinition determineImpliedGenerator( + GenerationType strategy, + String strategyGeneratorClassName, + Map localGenerators) { + if ( localGenerators == null ) { + return null; + } + + if ( localGenerators.size() == 1 ) { + final IdentifierGeneratorDefinition generatorDefinition = localGenerators.entrySet().iterator().next().getValue(); + // NOTE : a little bit of a special rule here for the case of just one - + // we consider it a match, based on strategy, if the strategy is AUTO or matches... + if ( strategy == AUTO + || isImpliedGenerator( strategy, strategyGeneratorClassName, generatorDefinition ) ) { + return generatorDefinition; + } + } + + IdentifierGeneratorDefinition matching = null; + for ( Map.Entry localGeneratorEntry : localGenerators.entrySet() ) { + if ( isImpliedGenerator( strategy, strategyGeneratorClassName, localGeneratorEntry.getValue() ) ) { + if ( matching != null ) { + // we found multiple matching generators + return null; + } + matching = localGeneratorEntry.getValue(); + } + } + return matching; + } + + private static boolean isImpliedGenerator( + GenerationType strategy, + String strategyGeneratorClassName, + IdentifierGeneratorDefinition generatorDefinition) { + return generatorDefinition.getStrategy().equals( strategyGeneratorClassName ); + } + + private static String correspondingGeneratorName(GenerationType strategy) { + return switch ( strategy ) { +// case UUID -> org.hibernate.id.uuid.UuidGenerator.class.getName(); + case UUID -> UuidValueGenerator.class.getName(); + case TABLE -> org.hibernate.id.enhanced.TableGenerator.class.getName(); + case IDENTITY -> null; + default -> SequenceStyleGenerator.class.getName(); + }; } private static String determineStrategy( @@ -182,39 +282,66 @@ public class GeneratorBinder { private static GenerationType interpretGenerationType(GeneratedValue generatedValueAnn) { // todo (jpa32) : when can this ever be null? final GenerationType strategy = generatedValueAnn.strategy(); - return strategy == null ? GenerationType.AUTO : strategy; + return strategy == null ? AUTO : strategy; } /** - * Build any generators declared using {@link TableGenerator}, {@link SequenceGenerator}, - * and {@link GenericGenerator} annotations of the given program element. + * Collects definition objects for all generators defined using any of {@link TableGenerator}, + * {@link SequenceGenerator}, and {@link GenericGenerator} on the given annotated element. */ - public static Map buildGenerators( + public static List collectIdGeneratorDefinitions( AnnotationTarget annotatedElement, MetadataBuildingContext context) { + final ArrayList definitions = new ArrayList<>(); + visitIdGeneratorDefinitions( + annotatedElement, + definitions::add, + context + ); + return definitions; + } + + public static void visitIdGeneratorDefinitions( + AnnotationTarget annotatedElement, + Consumer consumer, + MetadataBuildingContext context) { final InFlightMetadataCollector metadataCollector = context.getMetadataCollector(); final SourceModelBuildingContext sourceModelContext = metadataCollector.getSourceModelBuildingContext(); - final Map generators = new HashMap<>(); annotatedElement.forEachAnnotationUsage( TableGenerator.class, sourceModelContext, (usage) -> { - IdentifierGeneratorDefinition idGenerator = buildTableIdGenerator( usage ); - generators.put( idGenerator.getName(), idGenerator ); - metadataCollector.addIdentifierGenerator( idGenerator ); + final IdentifierGeneratorDefinition idGenerator = buildTableIdGenerator( usage ); + consumer.accept( idGenerator ); } ); annotatedElement.forEachAnnotationUsage( SequenceGenerator.class, sourceModelContext, (usage) -> { - IdentifierGeneratorDefinition idGenerator = buildSequenceIdGenerator( usage ); - generators.put( idGenerator.getName(), idGenerator ); - metadataCollector.addIdentifierGenerator( idGenerator ); + final IdentifierGeneratorDefinition idGenerator = buildSequenceIdGenerator( usage ); + consumer.accept( idGenerator ); } ); annotatedElement.forEachAnnotationUsage( GenericGenerator.class, sourceModelContext, (usage) -> { final IdentifierGeneratorDefinition idGenerator = buildIdGenerator( usage ); - generators.put( idGenerator.getName(), idGenerator ); - metadataCollector.addIdentifierGenerator( idGenerator ); + consumer.accept( idGenerator ); } ); - return generators; + } + + public static void registerGlobalGenerators( + AnnotationTarget annotatedElement, + MetadataBuildingContext context) { + if ( !context.getBootstrapContext().getJpaCompliance().isGlobalGeneratorScopeEnabled() ) { + return; + } + + final InFlightMetadataCollector metadataCollector = context.getMetadataCollector(); + visitIdGeneratorDefinitions( + annotatedElement, + (definition) -> { + if ( !definition.getName().isEmpty() ) { + metadataCollector.addIdentifierGenerator( definition ); + } + }, + context + ); } private static IdentifierGeneratorDefinition buildIdGenerator(GenericGenerator generatorAnnotation) { @@ -502,7 +629,7 @@ public class GeneratorBinder { * @param beanContainer an optional {@code BeanContainer} * @param generatorClass a class which implements {@code Generator} */ - private static Generator instantiateGenerator( + public static Generator instantiateGenerator( BeanContainer beanContainer, Class generatorClass) { if ( beanContainer != null ) { @@ -563,7 +690,7 @@ public class GeneratorBinder { * call its {@link Configurable#configure(GeneratorCreationContext, Properties) * configure()} method. */ - private static void callConfigure( + public static void callConfigure( GeneratorCreationContext creationContext, Generator generator, Map configuration, @@ -594,35 +721,93 @@ public class GeneratorBinder { * Create a generator, based on a {@link GeneratedValue} annotation. */ private static void createIdGenerator( + MemberDetails idMember, SimpleValue idValue, - Map classGenerators, - MetadataBuildingContext context, - MemberDetails idMember) { - //manage composite related metadata - //guess if its a component and find id data access (property, field etc) + PersistentClass persistentClass, + MetadataBuildingContext context) { + // NOTE: `generatedValue` is never null here final GeneratedValue generatedValue = castNonNull( idMember.getDirectAnnotationUsage( GeneratedValue.class ) ); - final String generatorType = - generatorStrategy( generatedValue.strategy(), generatedValue.generator(), idMember.getType() ); - final String generatorName = generatedValue.generator(); + if ( isGlobalGeneratorNameGlobal( context ) ) { - buildGenerators( idMember, context ); - context.getMetadataCollector() - .addSecondPass( new IdGeneratorResolverSecondPass( - idValue, - idMember, - generatorType, - generatorName, - context - ) ); + // process and register any generators defined on the member. + // according to JPA these are also global. + context.getMetadataCollector().getGlobalRegistrations().as( GlobalRegistrar.class ).collectIdGenerators( idMember ); + context.getMetadataCollector().addSecondPass( new StrictIdGeneratorResolverSecondPass( + persistentClass, + idValue, + idMember, + context + ) ); } else { - //clone classGenerator and override with local values - final Map generators = new HashMap<>( classGenerators ); - generators.putAll( buildGenerators( idMember, context ) ); - makeIdGenerator( idValue, idMember, generatorType, generatorName, context, generators ); + context.getMetadataCollector().addSecondPass( new IdGeneratorResolverSecondPass( + persistentClass, + idValue, + idMember, + generatedValue, + context + ) ); } } + public static void createGeneratorFrom( + IdentifierGeneratorDefinition defaultedGenerator, + MemberDetails idMember, + SimpleValue idValue, + Map configuration, + MetadataBuildingContext context) { + configuration.putAll( defaultedGenerator.getParameters() ); + + final BeanContainer beanContainer = beanContainer( context ); + idValue.setCustomIdGeneratorCreator( creationContext -> { + final Generator identifierGenerator = instantiateGenerator( + beanContainer, + generatorClass( defaultedGenerator.getStrategy(), idValue ) + ); + callConfigure( creationContext, identifierGenerator, configuration, idValue ); + if ( identifierGenerator instanceof IdentityGenerator) { + idValue.setColumnToIdentity(); + } + + if ( identifierGenerator instanceof ExportableProducer exportableProducer ) { + exportableProducer.registerExportables( creationContext.getDatabase() ); + } + + return identifierGenerator; + } ); + } + + + public static void createGeneratorFrom( + IdentifierGeneratorDefinition defaultedGenerator, + MemberDetails idMember, + SimpleValue idValue, + PersistentClass persistentClass, + MetadataBuildingContext context) { + createGeneratorFrom( + defaultedGenerator, + idMember, + idValue, + buildConfigurationMap( defaultedGenerator, idValue, persistentClass ), + context + ); + } + + public static Map buildConfigurationMap( + IdentifierGeneratorDefinition defaultedGenerator, + SimpleValue idValue, + PersistentClass persistentClass) { + final Map configuration = new HashMap<>(); + + configuration.put( PersistentIdentifierGenerator.TABLE, idValue.getTable().getName() ); + + if ( idValue.getColumnSpan() == 1 ) { + configuration.put( PersistentIdentifierGenerator.PK, idValue.getColumns().get( 0).getName() ); + } + + return configuration; + } + /** * Set up the identifier generator for an id defined in a {@code hbm.xml} mapping. * @@ -658,7 +843,7 @@ public class GeneratorBinder { /** * Obtain a {@link BeanContainer} to be used for instantiating generators. */ - private static BeanContainer beanContainer(MetadataBuildingContext buildingContext) { + public static BeanContainer beanContainer(MetadataBuildingContext buildingContext) { final ServiceRegistry serviceRegistry = buildingContext.getBootstrapContext().getServiceRegistry(); return allowExtensionsInCdi( serviceRegistry ) ? serviceRegistry.requireService( ManagedBeanRegistry.class ).getBeanContainer() @@ -701,7 +886,6 @@ public class GeneratorBinder { PropertyHolder propertyHolder, PropertyData inferredData, SimpleValue idValue, - Map classGenerators, MetadataBuildingContext context) { final SourceModelBuildingContext sourceModelContext = @@ -745,7 +929,7 @@ public class GeneratorBinder { ) ); } else if ( idAttributeMember.hasDirectAnnotationUsage( GeneratedValue.class ) ) { - createIdGenerator( idValue, classGenerators, context, idAttributeMember ); + createIdGenerator( idAttributeMember, idValue, propertyHolder.getPersistentClass(), context ); } } @@ -769,4 +953,10 @@ public class GeneratorBinder { + "' has too many generator annotations: " + generatorAnnotations ); } } + + public static void applyIfNotEmpty(String name, String value, BiConsumer consumer) { + if ( StringHelper.isNotEmpty( value ) ) { + consumer.accept( name, value ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorParameters.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorParameters.java index 22734c6215..59565f0f95 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorParameters.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorParameters.java @@ -11,6 +11,8 @@ import jakarta.persistence.TableGenerator; import jakarta.persistence.UniqueConstraint; import org.hibernate.Internal; import org.hibernate.boot.model.IdentifierGeneratorDefinition; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; import org.hibernate.engine.config.spi.ConfigurationService; @@ -36,6 +38,18 @@ import org.hibernate.type.Type; import java.util.Map; import java.util.Properties; +import java.util.function.BiConsumer; + +import static org.hibernate.id.IdentifierGenerator.CONTRIBUTOR_NAME; +import static org.hibernate.id.IdentifierGenerator.ENTITY_NAME; +import static org.hibernate.id.IdentifierGenerator.JPA_ENTITY_NAME; +import static org.hibernate.id.OptimizableGenerator.DEFAULT_INITIAL_VALUE; +import static org.hibernate.id.OptimizableGenerator.IMPLICIT_NAME_BASE; +import static org.hibernate.id.OptimizableGenerator.INCREMENT_PARAM; +import static org.hibernate.id.OptimizableGenerator.INITIAL_PARAM; +import static org.hibernate.id.PersistentIdentifierGenerator.PK; +import static org.hibernate.id.PersistentIdentifierGenerator.TABLE; +import static org.hibernate.id.PersistentIdentifierGenerator.TABLES; /** * Responsible for setting up the parameters which are passed to @@ -50,6 +64,19 @@ public class GeneratorParameters { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( GeneratorBinder.class ); + /** + * Collect the parameters which should be passed to + * {@link Configurable#configure(GeneratorCreationContext, Properties)}. + */ + public static Properties collectParameters( + SimpleValue identifierValue, + Dialect dialect, + RootClass rootClass) { + final Properties params = new Properties(); + collectParameters( identifierValue, dialect, rootClass, params::put ); + return params; + } + /** * Collect the parameters which should be passed to * {@link Configurable#configure(GeneratorCreationContext, Properties)}. @@ -59,63 +86,73 @@ public class GeneratorParameters { Dialect dialect, RootClass rootClass, Map configuration) { + final Properties params = collectParameters( identifierValue, dialect, rootClass ); + if ( configuration != null ) { + params.putAll( configuration ); + } + return params; + } + + public static void collectParameters( + SimpleValue identifierValue, + Dialect dialect, + RootClass rootClass, + BiConsumer parameterCollector) { + final ConfigurationService configService = identifierValue.getMetadata().getMetadataBuildingOptions().getServiceRegistry() .requireService( ConfigurationService.class ); - final Properties params = new Properties(); // default initial value and allocation size per-JPA defaults - params.setProperty( OptimizableGenerator.INITIAL_PARAM, - String.valueOf( OptimizableGenerator.DEFAULT_INITIAL_VALUE ) ); + parameterCollector.accept( INITIAL_PARAM, String.valueOf( DEFAULT_INITIAL_VALUE ) ); + parameterCollector.accept( INCREMENT_PARAM, String.valueOf( defaultIncrement( configService ) ) ); - params.setProperty( OptimizableGenerator.INCREMENT_PARAM, - String.valueOf( defaultIncrement( configService ) ) ); //init the table here instead of earlier, so that we can get a quoted table name //TODO: would it be better to simply pass the qualified table name, instead of // splitting it up into schema/catalog/table names final String tableName = identifierValue.getTable().getQuotedName( dialect ); - params.setProperty( PersistentIdentifierGenerator.TABLE, tableName ); + parameterCollector.accept( TABLE, tableName ); //pass the column name (a generated id almost always has a single column) - final Column column = (Column) identifierValue.getSelectables().get(0); - final String columnName = column.getQuotedName( dialect ); - params.setProperty( PersistentIdentifierGenerator.PK, columnName ); + if ( identifierValue.getColumnSpan() == 1 ) { + final Column column = (Column) identifierValue.getSelectables().get( 0 ); + final String columnName = column.getQuotedName( dialect ); + parameterCollector.accept( PK, columnName ); + } //pass the entity-name, if not a collection-id if ( rootClass != null ) { - params.setProperty( IdentifierGenerator.ENTITY_NAME, rootClass.getEntityName() ); - params.setProperty( IdentifierGenerator.JPA_ENTITY_NAME, rootClass.getJpaEntityName() ); + parameterCollector.accept( ENTITY_NAME, rootClass.getEntityName() ); + parameterCollector.accept( JPA_ENTITY_NAME, rootClass.getJpaEntityName() ); // The table name is not really a good default for subselect entities, // so use the JPA entity name which is short - params.setProperty( OptimizableGenerator.IMPLICIT_NAME_BASE, + parameterCollector.accept( + IMPLICIT_NAME_BASE, identifierValue.getTable().isSubselect() ? rootClass.getJpaEntityName() - : identifierValue.getTable().getName() ); + : identifierValue.getTable().getName() + ); - params.setProperty( PersistentIdentifierGenerator.TABLES, - identityTablesString( dialect, rootClass ) ); + parameterCollector.accept( TABLES, identityTablesString( dialect, rootClass ) ); } else { - params.setProperty( PersistentIdentifierGenerator.TABLES, tableName ); - params.setProperty( OptimizableGenerator.IMPLICIT_NAME_BASE, tableName ); + parameterCollector.accept( TABLES, tableName ); + parameterCollector.accept( IMPLICIT_NAME_BASE, tableName ); } - params.put( IdentifierGenerator.CONTRIBUTOR_NAME, - identifierValue.getBuildingContext().getCurrentContributorName() ); + parameterCollector.accept( CONTRIBUTOR_NAME, identifierValue.getBuildingContext().getCurrentContributorName() ); final Map settings = configService.getSettings(); if ( settings.containsKey( AvailableSettings.PREFERRED_POOLED_OPTIMIZER ) ) { - params.put( AvailableSettings.PREFERRED_POOLED_OPTIMIZER, - settings.get( AvailableSettings.PREFERRED_POOLED_OPTIMIZER ) ); + parameterCollector.accept( + AvailableSettings.PREFERRED_POOLED_OPTIMIZER, + (String) settings.get( AvailableSettings.PREFERRED_POOLED_OPTIMIZER ) + ); } - - params.putAll( configuration ); - - return params; } - private static String identityTablesString(Dialect dialect, RootClass rootClass) { + public static String identityTablesString(Dialect dialect, RootClass rootClass) { final StringBuilder tables = new StringBuilder(); for ( Table table : rootClass.getIdentityTables() ) { tables.append( table.getQuotedName( dialect ) ); @@ -126,7 +163,11 @@ public class GeneratorParameters { return tables.toString(); } - private static int defaultIncrement(ConfigurationService configService) { + public static int defaultIncrement(MetadataImplementor metadata) { + return defaultIncrement( metadata.getMetadataBuildingOptions().getServiceRegistry().requireService( ConfigurationService.class ) ); + } + + public static int defaultIncrement(ConfigurationService configService) { final String idNamingStrategy = configService.getSetting( AvailableSettings.ID_DB_STRUCTURE_NAMING_STRATEGY, StandardConverters.STRING, null ); @@ -198,13 +239,13 @@ public class GeneratorParameters { } definitionBuilder.addParam( - org.hibernate.id.enhanced.TableGenerator.INCREMENT_PARAM, + INCREMENT_PARAM, String.valueOf( tableGeneratorAnnotation.allocationSize() ) ); // See comment on HHH-4884 wrt initialValue. Basically initialValue is really the stated value + 1 definitionBuilder.addParam( - org.hibernate.id.enhanced.TableGenerator.INITIAL_PARAM, + INITIAL_PARAM, String.valueOf( tableGeneratorAnnotation.initialValue() + 1 ) ); @@ -238,11 +279,11 @@ public class GeneratorParameters { } definitionBuilder.addParam( - SequenceStyleGenerator.INCREMENT_PARAM, + INCREMENT_PARAM, String.valueOf( sequenceGeneratorAnnotation.allocationSize() ) ); definitionBuilder.addParam( - SequenceStyleGenerator.INITIAL_PARAM, + INITIAL_PARAM, String.valueOf( sequenceGeneratorAnnotation.initialValue() ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorStrategies.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorStrategies.java index e4f4c7b306..a52e2293b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorStrategies.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorStrategies.java @@ -9,6 +9,8 @@ package org.hibernate.boot.model.internal; import jakarta.persistence.GenerationType; import org.hibernate.MappingException; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.dialect.Dialect; import org.hibernate.generator.Generator; import org.hibernate.id.ForeignGenerator; import org.hibernate.id.GUIDGenerator; @@ -113,4 +115,44 @@ public class GeneratorStrategies { return clazz; } + public static Class mapLegacyNamedGenerator(String strategy, Dialect dialect) { + if ( "native".equals(strategy) ) { + strategy = dialect.getNativeIdentifierGeneratorStrategy(); + } + switch (strategy) { + case "assigned": + return org.hibernate.id.Assigned.class; + case "enhanced-sequence": + case "sequence": + return SequenceStyleGenerator.class; + case "enhanced-table": + case "table": + return org.hibernate.id.enhanced.TableGenerator.class; + case "identity": + return IdentityGenerator.class; + case "increment": + return IncrementGenerator.class; + case "foreign": + return ForeignGenerator.class; + case "uuid": + case "uuid.hex": + return UUIDHexGenerator.class; + case "uuid2": + return UUIDGenerator.class; + case "select": + return SelectGenerator.class; + case "guid": + return GUIDGenerator.class; + } + + return null; + } + + public static Class mapLegacyNamedGenerator(String strategy, MetadataBuildingContext buildingContext) { + return mapLegacyNamedGenerator( strategy, buildingContext.getMetadataCollector().getDatabase().getDialect() ); + } + + public static Class mapLegacyNamedGenerator(String strategy, SimpleValue idValue) { + return mapLegacyNamedGenerator( strategy, idValue.getMetadata().getDatabase().getDialect() ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdBagBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdBagBinder.java index 76ee26e514..23ac0b7e0d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdBagBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdBagBinder.java @@ -133,7 +133,8 @@ public class IdBagBinder extends BagBinder { } if ( isGlobalGeneratorNameGlobal( buildingContext ) ) { - SecondPass secondPass = new IdGeneratorResolverSecondPass( + SecondPass secondPass = new IdBagIdGeneratorResolverSecondPass( + (IdentifierBag) collection, id, property, generatorType, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdBagIdGeneratorResolverSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdBagIdGeneratorResolverSecondPass.java new file mode 100644 index 0000000000..972e994ec0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdBagIdGeneratorResolverSecondPass.java @@ -0,0 +1,303 @@ +/* + * 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.boot.model.internal; + +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.MappingException; +import org.hibernate.boot.model.IdentifierGeneratorDefinition; +import org.hibernate.boot.models.JpaAnnotations; +import org.hibernate.boot.models.annotations.internal.SequenceGeneratorJpaAnnotation; +import org.hibernate.boot.models.annotations.internal.TableGeneratorJpaAnnotation; +import org.hibernate.boot.models.spi.GlobalRegistrations; +import org.hibernate.boot.models.spi.SequenceGeneratorRegistration; +import org.hibernate.boot.models.spi.TableGeneratorRegistration; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.generator.Generator; +import org.hibernate.id.PersistentIdentifierGenerator; +import org.hibernate.id.enhanced.SequenceStyleGenerator; +import org.hibernate.mapping.IdentifierBag; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.SimpleValue; +import org.hibernate.models.spi.MemberDetails; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.TableGenerator; + +import static org.hibernate.boot.model.internal.GeneratorAnnotationHelper.applyBaselineConfiguration; +import static org.hibernate.boot.model.internal.GeneratorBinder.createGeneratorFrom; +import static org.hibernate.boot.model.internal.GeneratorBinder.makeIdGenerator; + +/** + * IdGeneratorResolver for handling generators assigned to id-bag mappings + * + * @author Andrea Boriero + */ +public class IdBagIdGeneratorResolverSecondPass implements IdGeneratorResolver { + private final PersistentClass entityMapping; + private final SimpleValue idValue; + private final MemberDetails idAttributeMember; + private final String generatorType; + private final String generatorName; + private final MetadataBuildingContext buildingContext; + private final Map configuration; + + public IdBagIdGeneratorResolverSecondPass( + IdentifierBag idBagMapping, + SimpleValue idValue, + MemberDetails idAttributeMember, + String generatorType, + String generatorName, + MetadataBuildingContext buildingContext) { + this.entityMapping = null; + this.idValue = idValue; + this.idAttributeMember = idAttributeMember; + this.generatorType = generatorType; + this.generatorName = generatorName; + this.buildingContext = buildingContext; + + this.configuration = new HashMap<>(); + } + + @Override + public void doSecondPass(Map idGeneratorDefinitionMap) throws MappingException { + final GeneratedValue generatedValue = idAttributeMember.getDirectAnnotationUsage( GeneratedValue.class ); + switch ( generatedValue.strategy() ) { + case UUID -> GeneratorAnnotationHelper.handleUuidStrategy( idValue, idAttributeMember, buildingContext ); + case IDENTITY -> GeneratorAnnotationHelper.handleIdentityStrategy( idValue, idAttributeMember, buildingContext ); + case SEQUENCE -> handleSequenceStrategy( + generatorName, + idValue, + idAttributeMember, + buildingContext + ); + case TABLE -> handleTableStrategy( + generatorName, + entityMapping, + idValue, + idAttributeMember, + generatedValue, + buildingContext + ); + case AUTO -> handleAutoStrategy( + generatorName, + entityMapping, + idValue, + idAttributeMember, + generatedValue, + buildingContext + ); + } + } + + private void handleTableStrategy( + String generatorName, + PersistentClass entityMapping, + SimpleValue idValue, + MemberDetails idAttributeMember, + GeneratedValue generatedValue, + MetadataBuildingContext buildingContext) { + final GlobalRegistrations globalRegistrations = buildingContext.getMetadataCollector().getGlobalRegistrations(); + final TableGeneratorRegistration globalTableGenerator = globalRegistrations.getTableGeneratorRegistrations().get( generatorName ); + if ( globalTableGenerator != null ) { + handleTableGenerator( + generatorName, + globalTableGenerator.configuration(), + configuration, + idValue, + idAttributeMember, + buildingContext + ); + return; + } + + final TableGenerator localizedTableMatch = GeneratorAnnotationHelper.findLocalizedMatch( + JpaAnnotations.TABLE_GENERATOR, + idAttributeMember, + TableGenerator::name, + generatorName, + buildingContext + ); + if ( localizedTableMatch != null ) { + handleTableGenerator( generatorName, localizedTableMatch, configuration, idValue, idAttributeMember, buildingContext ); + return; + } + + GeneratorAnnotationHelper.handleTableGenerator( + generatorName, + new TableGeneratorJpaAnnotation( buildingContext.getMetadataCollector().getSourceModelBuildingContext() ), + entityMapping, + idValue, + idAttributeMember, + buildingContext + ); + } + + private void handleSequenceStrategy( + String generatorName, + SimpleValue idValue, + MemberDetails idAttributeMember, + MetadataBuildingContext buildingContext) { + final GlobalRegistrations globalRegistrations = buildingContext.getMetadataCollector().getGlobalRegistrations(); + + final SequenceGeneratorRegistration globalSequenceGenerator = globalRegistrations.getSequenceGeneratorRegistrations().get( generatorName ); + if ( globalSequenceGenerator != null ) { + handleSequenceGenerator( + generatorName, + globalSequenceGenerator.configuration(), + configuration, + idValue, + idAttributeMember, + buildingContext + ); + return; + } + + final SequenceGenerator localizedSequencedMatch = GeneratorAnnotationHelper.findLocalizedMatch( + JpaAnnotations.SEQUENCE_GENERATOR, + idAttributeMember, + SequenceGenerator::name, + generatorName, + buildingContext + ); + if ( localizedSequencedMatch != null ) { + handleSequenceGenerator( generatorName, localizedSequencedMatch, configuration, idValue, idAttributeMember, buildingContext ); + return; + } + + handleSequenceGenerator( + generatorName, + new SequenceGeneratorJpaAnnotation( buildingContext.getMetadataCollector().getSourceModelBuildingContext() ), + configuration, + idValue, + idAttributeMember, + buildingContext + ); + } + + private void handleAutoStrategy( + String generatorName, + PersistentClass entityMapping, + SimpleValue idValue, + MemberDetails idAttributeMember, + GeneratedValue generatedValue, + MetadataBuildingContext buildingContext) { + final GlobalRegistrations globalRegistrations = buildingContext.getMetadataCollector().getGlobalRegistrations(); + + final SequenceGeneratorRegistration globalSequenceGenerator = globalRegistrations.getSequenceGeneratorRegistrations().get( generatorName ); + if ( globalSequenceGenerator != null ) { + handleSequenceGenerator( + generatorName, + globalSequenceGenerator.configuration(), + configuration, + idValue, + idAttributeMember, + buildingContext + ); + return; + } + + final TableGeneratorRegistration globalTableGenerator = globalRegistrations.getTableGeneratorRegistrations().get( generatorName ); + if ( globalTableGenerator != null ) { + handleTableGenerator( + generatorName, + globalTableGenerator.configuration(), + configuration, + idValue, + idAttributeMember, + buildingContext + ); + return; + } + + + final Class legacyNamedGenerator = GeneratorStrategies.mapLegacyNamedGenerator( generatorName, idValue ); + if ( legacyNamedGenerator != null ) { + //generator settings + if ( idValue.getColumnSpan() == 1 ) { + configuration.put( PersistentIdentifierGenerator.PK, idValue.getColumns().get(0).getName() ); + } + createGeneratorFrom( + new IdentifierGeneratorDefinition( generatorName, legacyNamedGenerator.getName(), configuration ), + idAttributeMember, + idValue, + (Map) configuration, + buildingContext + ); + return; + } + + + final SequenceGenerator localizedSequencedMatch = GeneratorAnnotationHelper.findLocalizedMatch( + JpaAnnotations.SEQUENCE_GENERATOR, + idAttributeMember, + SequenceGenerator::name, + generatorName, + buildingContext + ); + if ( localizedSequencedMatch != null ) { + handleSequenceGenerator( generatorName, localizedSequencedMatch, configuration, idValue, idAttributeMember, buildingContext ); + return; + } + + final TableGenerator localizedTableMatch = GeneratorAnnotationHelper.findLocalizedMatch( + JpaAnnotations.TABLE_GENERATOR, + idAttributeMember, + TableGenerator::name, + generatorName, + buildingContext + ); + if ( localizedTableMatch != null ) { + handleTableGenerator( generatorName, localizedTableMatch, configuration, idValue, idAttributeMember, buildingContext ); + return; + } + + makeIdGenerator( idValue, idAttributeMember, generatorType, generatorName, buildingContext, null ); + } + + public static void handleSequenceGenerator( + String generatorName, + SequenceGenerator generatorConfig, + Map configuration, + SimpleValue idValue, + MemberDetails idMember, + MetadataBuildingContext context) { + applyBaselineConfiguration( generatorConfig, idValue, null, context, configuration::put ); + SequenceStyleGenerator.applyConfiguration( generatorConfig, idValue, configuration::put ); + + createGeneratorFrom( + new IdentifierGeneratorDefinition( generatorName, SequenceStyleGenerator.class.getName(), configuration ), + idMember, + idValue, + (Map) configuration, + context + ); + + } + + public static void handleTableGenerator( + String generatorName, + TableGenerator generatorConfig, + Map configuration, + SimpleValue idValue, + MemberDetails idMember, + MetadataBuildingContext context) { + GeneratorAnnotationHelper.applyBaselineConfiguration( generatorConfig, idValue, null, context, configuration::put ); + org.hibernate.id.enhanced.TableGenerator.applyConfiguration( generatorConfig, idValue, configuration::put ); + + createGeneratorFrom( + new IdentifierGeneratorDefinition( generatorName, org.hibernate.id.enhanced.TableGenerator.class.getName(), configuration ), + idMember, + idValue, + (Map) configuration, + context + ); + + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdGeneratorResolver.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdGeneratorResolver.java new file mode 100644 index 0000000000..a9c21e7513 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdGeneratorResolver.java @@ -0,0 +1,17 @@ +/* + * 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.boot.model.internal; + +import org.hibernate.boot.spi.SecondPass; + +/** + * Marker interface for second passes which bind id generators + * + * @author Steve Ebersole + */ +public interface IdGeneratorResolver extends SecondPass { +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdGeneratorResolverSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdGeneratorResolverSecondPass.java index 7278c23bae..d0ec2d9b1d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdGeneratorResolverSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/IdGeneratorResolverSecondPass.java @@ -6,68 +6,570 @@ */ package org.hibernate.boot.model.internal; +import java.lang.annotation.Annotation; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.UUID; import org.hibernate.MappingException; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.IdGeneratorType; +import org.hibernate.boot.model.IdentifierGeneratorDefinition; +import org.hibernate.boot.model.relational.ExportableProducer; +import org.hibernate.boot.models.HibernateAnnotations; +import org.hibernate.boot.models.JpaAnnotations; +import org.hibernate.boot.models.spi.GenericGeneratorRegistration; +import org.hibernate.boot.models.spi.SequenceGeneratorRegistration; +import org.hibernate.boot.models.spi.TableGeneratorRegistration; +import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.spi.MetadataBuildingContext; -import org.hibernate.boot.spi.SecondPass; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.engine.config.spi.StandardConverters; +import org.hibernate.generator.Generator; +import org.hibernate.id.IdentityGenerator; +import org.hibernate.id.OptimizableGenerator; +import org.hibernate.id.enhanced.LegacyNamingStrategy; +import org.hibernate.id.enhanced.SequenceStyleGenerator; +import org.hibernate.id.enhanced.SingleNamingStrategy; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.SimpleValue; import org.hibernate.models.spi.MemberDetails; +import org.hibernate.resource.beans.container.spi.BeanContainer; -import static org.hibernate.boot.model.internal.GeneratorBinder.makeIdGenerator; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.TableGenerator; + +import static org.hibernate.boot.model.internal.GeneratorBinder.callConfigure; +import static org.hibernate.boot.model.internal.GeneratorStrategies.mapLegacyNamedGenerator; +import static org.hibernate.id.IdentifierGenerator.GENERATOR_NAME; +import static org.hibernate.id.OptimizableGenerator.INCREMENT_PARAM; /** - * @author Andrea Boriero + * SecondPass implementing delayed resolution of id-generators associated with an entity. + * + * @see StrictIdGeneratorResolverSecondPass + * + * @author Steve Ebersole */ -public class IdGeneratorResolverSecondPass implements SecondPass { - private final SimpleValue id; - private final MemberDetails idAttributeMember; - private final String generatorType; - private final String generatorName; +public class IdGeneratorResolverSecondPass implements IdGeneratorResolver { + private final PersistentClass entityMapping; + private final SimpleValue idValue; + private final MemberDetails idMember; + private final GeneratedValue generatedValue; private final MetadataBuildingContext buildingContext; -// private IdentifierGeneratorDefinition localIdentifierGeneratorDefinition; public IdGeneratorResolverSecondPass( - SimpleValue id, - MemberDetails idAttributeMember, - String generatorType, - String generatorName, + PersistentClass entityMapping, + SimpleValue idValue, + MemberDetails idMember, + GeneratedValue generatedValue, MetadataBuildingContext buildingContext) { - this.id = id; - this.idAttributeMember = idAttributeMember; - this.generatorType = generatorType; - this.generatorName = generatorName; + this.entityMapping = entityMapping; + this.idValue = idValue; + this.idMember = idMember; + this.generatedValue = generatedValue; this.buildingContext = buildingContext; } -// public IdGeneratorResolverSecondPass( -// SimpleValue id, -// MemberDetails idAttributeMember, -// String generatorType, -// String generatorName, -// MetadataBuildingContext buildingContext, -// IdentifierGeneratorDefinition localIdentifierGeneratorDefinition) { -// this( id, idAttributeMember, generatorType, generatorName, buildingContext ); -// this.localIdentifierGeneratorDefinition = localIdentifierGeneratorDefinition; -// } - @Override - public void doSecondPass(Map idGeneratorDefinitionMap) throws MappingException { -// makeIdGenerator( id, idAttributeMember, generatorType, generatorName, buildingContext, localIdentifierGeneratorDefinition ); - makeIdGenerator( id, idAttributeMember, generatorType, generatorName, buildingContext, null ); + public void doSecondPass(Map persistentClasses) throws MappingException { + switch ( generatedValue.strategy() ) { + case UUID -> GeneratorAnnotationHelper.handleUuidStrategy( idValue, idMember, buildingContext ); + case IDENTITY -> GeneratorAnnotationHelper.handleIdentityStrategy( idValue, idMember, buildingContext ); + case SEQUENCE -> handleSequenceStrategy(); + case TABLE -> handleTableStrategy(); + case AUTO -> handleAutoStrategy(); + } } -// public static void makeIdGenerator( -// SimpleValue id, -// MemberDetails idAttributeMember, -// String generatorType, -// String generatorName, -// MetadataBuildingContext buildingContext, -// IdentifierGeneratorDefinition foreignKGeneratorDefinition) { -// makeIdGenerator( id, idAttributeMember, generatorType, generatorName, buildingContext, -// foreignKGeneratorDefinition != null -// ? Map.of( foreignKGeneratorDefinition.getName(), foreignKGeneratorDefinition ) -// : null ); -// } + private void handleSequenceStrategy() { + if ( generatedValue.generator().isEmpty() ) { + handleUnnamedSequenceGenerator(); + } + else { + handleNamedSequenceGenerator(); + } + } + + private void handleUnnamedSequenceGenerator() { + // todo (7.0) : null or entityMapping.getJpaEntityName() for "name from GeneratedValue"? + + final SequenceGenerator localizedMatch = GeneratorAnnotationHelper.findLocalizedMatch( + JpaAnnotations.SEQUENCE_GENERATOR, + idMember, + null, + null, + buildingContext + ); + if ( localizedMatch != null ) { + handleSequenceGenerator( null, localizedMatch ); + return; + } + + handleSequenceGenerator( null, null ); + } + + private void handleNamedSequenceGenerator() { + final SequenceGenerator localizedMatch = GeneratorAnnotationHelper.findLocalizedMatch( + JpaAnnotations.SEQUENCE_GENERATOR, + idMember, + SequenceGenerator::name, + generatedValue.generator(), + buildingContext + ); + if ( localizedMatch != null ) { + handleSequenceGenerator( generatedValue.generator(), localizedMatch ); + return; + } + + // look for the matching global registration, if one. + final SequenceGeneratorRegistration globalMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getSequenceGeneratorRegistrations() + .get( generatedValue.generator() ); + if ( globalMatch != null ) { + handleSequenceGenerator( generatedValue.generator(), globalMatch.configuration() ); + return; + } + + validateSequenceGeneration(); + + handleSequenceGenerator( generatedValue.generator(), null ); + } + + private void validateSequenceGeneration() { + // basically, make sure there is neither a TableGenerator nor GenericGenerator with this name + + final TableGeneratorRegistration globalTableMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getTableGeneratorRegistrations() + .get( generatedValue.generator() ); + if ( globalTableMatch != null ) { + throw new MappingException( + String.format( + Locale.ROOT, + "@GeneratedValue for %s (%s) specified SEQUENCE generation, but referred to a @TableGenerator", + entityMapping.getEntityName(), + generatedValue.generator() + ) + ); + } + + final GenericGeneratorRegistration globalGenericMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getGenericGeneratorRegistrations() + .get( generatedValue.generator() ); + if ( globalGenericMatch != null ) { + throw new MappingException( + String.format( + Locale.ROOT, + "@GeneratedValue for %s (%s) specified SEQUENCE generation, but referred to a @GenericGenerator", + entityMapping.getEntityName(), + generatedValue.generator() + ) + ); + } + } + + private void handleTableStrategy() { + if ( generatedValue.generator().isEmpty() ) { + handleUnnamedTableGenerator(); + } + else { + handleNamedTableGenerator(); + } + } + + private void handleUnnamedTableGenerator() { + // todo (7.0) : null or entityMapping.getJpaEntityName() for "name from GeneratedValue"? + + final TableGenerator localizedMatch = GeneratorAnnotationHelper.findLocalizedMatch( + JpaAnnotations.TABLE_GENERATOR, + idMember, + null, + null, + buildingContext + ); + if ( localizedMatch != null ) { + handleTableGenerator( null, localizedMatch ); + return; + } + + handleTableGenerator( null, null ); + } + + private void handleNamedTableGenerator() { + final TableGenerator localizedTableMatch = GeneratorAnnotationHelper.findLocalizedMatch( + JpaAnnotations.TABLE_GENERATOR, + idMember, + TableGenerator::name, + generatedValue.generator(), + buildingContext + ); + if ( localizedTableMatch != null ) { + handleTableGenerator( generatedValue.generator(), localizedTableMatch ); + return; + } + + // look for the matching global registration, if one. + final TableGeneratorRegistration globalMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getTableGeneratorRegistrations() + .get( generatedValue.generator() ); + if ( globalMatch != null ) { + handleTableGenerator( generatedValue.generator(), globalMatch.configuration() ); + return; + } + + validateTableGeneration(); + + handleTableGenerator( generatedValue.generator(), null ); + } + + private void validateTableGeneration() { + // basically, make sure there is neither a SequenceGenerator nor a GenericGenerator with this name + + final SequenceGeneratorRegistration globalSequenceMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getSequenceGeneratorRegistrations() + .get( generatedValue.generator() ); + if ( globalSequenceMatch != null ) { + throw new MappingException( + String.format( + Locale.ROOT, + "@GeneratedValue for %s (%s) specified TABLE generation, but referred to a @SequenceGenerator", + entityMapping.getEntityName(), + generatedValue.generator() + ) + ); + } + + final GenericGeneratorRegistration globalGenericMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getGenericGeneratorRegistrations() + .get( generatedValue.generator() ); + if ( globalGenericMatch != null ) { + throw new MappingException( + String.format( + Locale.ROOT, + "@GeneratedValue for %s (%s) specified TABLE generation, but referred to a @GenericGenerator", + entityMapping.getEntityName(), + generatedValue.generator() + ) + ); + } + } + + private void handleAutoStrategy() { + if ( generatedValue.generator().isEmpty() ) { + handleUnnamedAutoGenerator(); + } + else { + handleNamedAutoGenerator(); + } + } + + private void handleUnnamedAutoGenerator() { + // todo (7.0) : null or entityMapping.getJpaEntityName() for "name from GeneratedValue"? + + final SequenceGenerator localizedSequenceMatch = GeneratorAnnotationHelper.findLocalizedMatch( + JpaAnnotations.SEQUENCE_GENERATOR, + idMember, + null, + null, + buildingContext + ); + if ( localizedSequenceMatch != null ) { + handleSequenceGenerator( null, localizedSequenceMatch ); + return; + } + + final TableGenerator localizedTableMatch = GeneratorAnnotationHelper.findLocalizedMatch( + JpaAnnotations.TABLE_GENERATOR, + idMember, + null, + null, + buildingContext + ); + if ( localizedTableMatch != null ) { + handleTableGenerator( null, localizedTableMatch ); + return; + } + + final GenericGenerator localizedGenericMatch = GeneratorAnnotationHelper.findLocalizedMatch( + HibernateAnnotations.GENERIC_GENERATOR, + idMember, + null, + null, + buildingContext + ); + if ( localizedGenericMatch != null ) { + GeneratorAnnotationHelper.handleGenericGenerator( + entityMapping.getJpaEntityName(), + localizedGenericMatch, + entityMapping, + idValue, + idMember, + buildingContext + ); + return; + } + + if ( idMember.getType().isImplementor( UUID.class ) + || idMember.getType().isImplementor( String.class ) ) { + GeneratorAnnotationHelper.handleUuidStrategy( idValue, idMember, buildingContext ); + return; + } + + handleSequenceGenerator( null, null ); + } + + private void handleNamedAutoGenerator() { + if ( handleAsLocalAutoGenerator() ) { + return; + } + + if ( handleAsNamedGlobalAutoGenerator() ) { + return; + } + + final Class legacyNamedGenerator = mapLegacyNamedGenerator( generatedValue.generator(), buildingContext ); + if ( legacyNamedGenerator != null ) { + //generator settings + GeneratorBinder.createGeneratorFrom( + new IdentifierGeneratorDefinition( generatedValue.generator(), legacyNamedGenerator.getName() ), + idMember, + idValue, + entityMapping, + buildingContext + ); + return; + } + + final List metaAnnotated = idMember.getMetaAnnotated( IdGeneratorType.class, buildingContext.getMetadataCollector().getSourceModelBuildingContext() ); + if ( CollectionHelper.size( metaAnnotated ) > 0 ) { + final Annotation generatorAnnotation = metaAnnotated.get( 0 ); + final IdGeneratorType markerAnnotation = generatorAnnotation.annotationType().getAnnotation( IdGeneratorType.class ); + idValue.setCustomIdGeneratorCreator( (creationContext) -> { + + final BeanContainer beanContainer = GeneratorBinder.beanContainer( buildingContext ); + final Generator identifierGenerator = GeneratorBinder.instantiateGenerator( + beanContainer, + markerAnnotation.value() + ); + final Map configuration = new HashMap<>(); + GeneratorParameters.collectParameters( + idValue, + buildingContext.getMetadataCollector().getDatabase().getDialect(), + entityMapping.getRootClass(), + configuration::put + ); + callConfigure( creationContext, identifierGenerator, configuration, idValue ); + return identifierGenerator; + } ); + return; + } + + if ( idMember.getType().isImplementor( UUID.class ) + || idMember.getType().isImplementor( String.class ) ) { + GeneratorAnnotationHelper.handleUuidStrategy( idValue, idMember, buildingContext ); + return; + } + + handleSequenceGenerator( generatedValue.generator(), null ); + } + + private boolean handleAsLocalAutoGenerator() { + assert !generatedValue.generator().isEmpty(); + + final SequenceGenerator localizedSequenceMatch = GeneratorAnnotationHelper.findLocalizedMatch( + JpaAnnotations.SEQUENCE_GENERATOR, + idMember, + SequenceGenerator::name, + generatedValue.generator(), + buildingContext + ); + if ( localizedSequenceMatch != null ) { + handleSequenceGenerator( generatedValue.generator(), localizedSequenceMatch ); + return true; + } + + final TableGenerator localizedTableMatch = GeneratorAnnotationHelper.findLocalizedMatch( + JpaAnnotations.TABLE_GENERATOR, + idMember, + TableGenerator::name, + generatedValue.generator(), + buildingContext + ); + if ( localizedTableMatch != null ) { + handleTableGenerator( generatedValue.generator(), localizedTableMatch ); + return true; + } + + final GenericGenerator localizedGenericMatch = GeneratorAnnotationHelper.findLocalizedMatch( + HibernateAnnotations.GENERIC_GENERATOR, + idMember, + GenericGenerator::name, + generatedValue.generator(), + buildingContext + ); + if ( localizedGenericMatch != null ) { + GeneratorAnnotationHelper.handleGenericGenerator( + generatedValue.generator(), + localizedGenericMatch, + entityMapping, + idValue, + idMember, + buildingContext + ); + return true; + } + + return false; + } + + private boolean handleAsNamedGlobalAutoGenerator() { + final SequenceGeneratorRegistration globalSequenceMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getSequenceGeneratorRegistrations() + .get( generatedValue.generator() ); + if ( globalSequenceMatch != null ) { + handleSequenceGenerator( generatedValue.generator(), globalSequenceMatch.configuration() ); + return true; + } + + final TableGeneratorRegistration globalTableMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getTableGeneratorRegistrations() + .get( generatedValue.generator() ); + if ( globalTableMatch != null ) { + handleTableGenerator( generatedValue.generator(), globalTableMatch.configuration() ); + return true; + } + + final GenericGeneratorRegistration globalGenericMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getGenericGeneratorRegistrations() + .get( generatedValue.generator() ); + if ( globalGenericMatch != null ) { + GeneratorAnnotationHelper.handleGenericGenerator( + generatedValue.generator(), + globalGenericMatch.configuration(), + entityMapping, + idValue, + idMember, + buildingContext + ); + return true; + } + + return false; + } + + private void handleSequenceGenerator(String nameFromGeneratedValue, SequenceGenerator generator) { + final Map configuration = extractConfiguration( nameFromGeneratedValue, generator ); + createGeneratorFrom( SequenceStyleGenerator.class, configuration ); + } + + private Map extractConfiguration(String nameFromGenerated, SequenceGenerator generator) { + final Map configuration = new HashMap<>(); + if ( generator != null ) { + configuration.put( GENERATOR_NAME, generator.name() ); + } + else if ( nameFromGenerated != null ) { + configuration.put( GENERATOR_NAME, nameFromGenerated ); + } + + applyCommonConfiguration( configuration, generator ); + + if ( generator != null ) { + SequenceStyleGenerator.applyConfiguration( generator, idValue, configuration::put ); + } + + return configuration; + } + + private void applyCommonConfiguration(Map configuration, Annotation generatorAnnotation) { + GeneratorParameters.collectParameters( + idValue, + buildingContext.getMetadataCollector().getDatabase().getDialect(), + entityMapping.getRootClass(), + configuration::put + ); + + // we need to better handle default allocation-size here... + configuration.put( INCREMENT_PARAM, fallbackAllocationSize( buildingContext, generatorAnnotation ) ); + } + + private static int fallbackAllocationSize(MetadataBuildingContext buildingContext, Annotation generatorAnnotation) { + if ( generatorAnnotation == null ) { + // Special case where we have no matching SequenceGenerator/TableGenerator annotation. + // Historically we interpreted such cases using a default of 1, but JPA says the default + // here should be 50. As a migration aid, under the assumption that one of the legacy + // naming-strategies are used in such cases, we revert to the old default; otherwise we + // use the compliant value. + final StandardServiceRegistry serviceRegistry = buildingContext.getBootstrapContext().getServiceRegistry(); + final ConfigurationService configService = serviceRegistry.requireService( ConfigurationService.class ); + final String idNamingStrategy = configService.getSetting( + AvailableSettings.ID_DB_STRUCTURE_NAMING_STRATEGY, + StandardConverters.STRING, + null + ); + if ( LegacyNamingStrategy.STRATEGY_NAME.equals( idNamingStrategy ) + || LegacyNamingStrategy.class.getName().equals( idNamingStrategy ) + || SingleNamingStrategy.STRATEGY_NAME.equals( idNamingStrategy ) + || SingleNamingStrategy.class.getName().equals( idNamingStrategy ) ) { + return 1; + } + } + + return OptimizableGenerator.DEFAULT_INCREMENT_SIZE; + } + + private void handleTableGenerator(String nameFromGeneratedValue, TableGenerator generator) { + final Map configuration = extractConfiguration( nameFromGeneratedValue, generator ); + createGeneratorFrom( org.hibernate.id.enhanced.TableGenerator.class, configuration ); + } + + private Map extractConfiguration(String nameFromGenerated, TableGenerator generator) { + final Map configuration = new HashMap<>(); + if ( generator != null ) { + configuration.put( GENERATOR_NAME, generator.name() ); + } + else if ( nameFromGenerated != null ) { + configuration.put( GENERATOR_NAME, nameFromGenerated ); + } + + applyCommonConfiguration( configuration, generator ); + + if ( generator != null ) { + org.hibernate.id.enhanced.TableGenerator.applyConfiguration( generator, idValue, configuration::put ); + } + + return configuration; + } + + private void createGeneratorFrom( + Class generatorClass, + Map configuration) { + final BeanContainer beanContainer = GeneratorBinder.beanContainer( buildingContext ); + idValue.setCustomIdGeneratorCreator( (creationContext) -> { + final Generator identifierGenerator = GeneratorBinder.instantiateGenerator( beanContainer, generatorClass ); + callConfigure( creationContext, identifierGenerator, configuration, idValue ); + if ( identifierGenerator instanceof IdentityGenerator ) { + idValue.setColumnToIdentity(); + } + + // if we get here we have either a sequence or table generator, + // both of which are ExportableProducers + ( (ExportableProducer) identifierGenerator ).registerExportables( creationContext.getDatabase() ); + + return identifierGenerator; + } ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java index ed15f34139..bad9c3bc9c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java @@ -683,7 +683,6 @@ public class PropertyBinder { PropertyHolder propertyHolder, Nullability nullability, PropertyData inferredData, - Map classGenerators, EntityBinder entityBinder, boolean isIdentifierMapper, boolean isComponentEmbedded, @@ -721,7 +720,6 @@ public class PropertyBinder { propertyHolder, nullability, inferredData, - classGenerators, entityBinder, isIdentifierMapper, isComponentEmbedded, @@ -753,7 +751,6 @@ public class PropertyBinder { PropertyHolder propertyHolder, Nullability nullability, PropertyData inferredData, - Map classGenerators, EntityBinder entityBinder, boolean isIdentifierMapper, boolean isComponentEmbedded, @@ -797,7 +794,6 @@ public class PropertyBinder { propertyHolder, nullability, inferredData, - classGenerators, entityBinder, isIdentifierMapper, isComponentEmbedded, @@ -816,7 +812,6 @@ public class PropertyBinder { PropertyHolder propertyHolder, Nullability nullability, PropertyData inferredData, - Map classGenerators, EntityBinder entityBinder, boolean isIdentifierMapper, boolean isComponentEmbedded, @@ -879,7 +874,6 @@ public class PropertyBinder { propertyHolder, nullability, inferredData, - classGenerators, entityBinder, isIdentifierMapper, context, @@ -895,7 +889,6 @@ public class PropertyBinder { propertyHolder, nullability, inferredData, - classGenerators, entityBinder, isIdentifierMapper, isComponentEmbedded, @@ -996,7 +989,6 @@ public class PropertyBinder { PropertyHolder propertyHolder, Nullability nullability, PropertyData inferredData, - Map classGenerators, EntityBinder entityBinder, boolean isIdentifierMapper, boolean isComponentEmbedded, @@ -1145,7 +1137,6 @@ public class PropertyBinder { propertyHolder, inferredData, (SimpleValue) propertyBinder.getValue(), - classGenerators, context ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/StrictIdGeneratorResolverSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/StrictIdGeneratorResolverSecondPass.java new file mode 100644 index 0000000000..73e80bf416 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/StrictIdGeneratorResolverSecondPass.java @@ -0,0 +1,365 @@ +/* + * 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.boot.model.internal; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.hibernate.MappingException; +import org.hibernate.boot.model.IdentifierGeneratorDefinition; +import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.models.annotations.internal.SequenceGeneratorJpaAnnotation; +import org.hibernate.boot.models.annotations.internal.TableGeneratorJpaAnnotation; +import org.hibernate.boot.models.spi.GenericGeneratorRegistration; +import org.hibernate.boot.models.spi.SequenceGeneratorRegistration; +import org.hibernate.boot.models.spi.TableGeneratorRegistration; +import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.dialect.Dialect; +import org.hibernate.generator.Generator; +import org.hibernate.id.enhanced.SequenceStyleGenerator; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.SimpleValue; +import org.hibernate.models.spi.MemberDetails; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.SequenceGenerator; + +import static org.hibernate.boot.model.internal.GeneratorAnnotationHelper.*; +import static org.hibernate.boot.model.internal.GeneratorAnnotationHelper.handleUuidStrategy; +import static org.hibernate.boot.model.internal.GeneratorParameters.identityTablesString; +import static org.hibernate.id.IdentifierGenerator.ENTITY_NAME; +import static org.hibernate.id.IdentifierGenerator.GENERATOR_NAME; +import static org.hibernate.id.IdentifierGenerator.JPA_ENTITY_NAME; +import static org.hibernate.id.OptimizableGenerator.IMPLICIT_NAME_BASE; +import static org.hibernate.id.PersistentIdentifierGenerator.PK; +import static org.hibernate.id.PersistentIdentifierGenerator.TABLE; +import static org.hibernate.id.PersistentIdentifierGenerator.TABLES; + +/** + * SecondPass implementing delayed resolution of id-generators associated with an entity + * using strict JPA resolution - based mainly on global resolution of generator names, + * along with support for UUID and String member types with AUTO. We also account for + * legacy (un-configurable) named generators ({@code increment}, {@code uuid.hex}, etc.). + * + * @implNote For unnamed generators defined on the entity class or on the id member, this + * strategy will register a global registration using the entity's name and later look it + * up by that name. This more strictly follows the JPA specification where all generator + * names should be considered global and resolved globally. + * + * @see IdGeneratorResolverSecondPass + * + * @author Steve Ebersole + */ +public class StrictIdGeneratorResolverSecondPass implements IdGeneratorResolver { + private final PersistentClass entityMapping; + private final SimpleValue idValue; + private final MemberDetails idMember; + + private final MetadataBuildingContext buildingContext; + + public StrictIdGeneratorResolverSecondPass( + PersistentClass entityMapping, + SimpleValue idValue, + MemberDetails idMember, + MetadataBuildingContext buildingContext) { + this.entityMapping = entityMapping; + this.idValue = idValue; + this.idMember = idMember; + this.buildingContext = buildingContext; + } + + @Override + public void doSecondPass(Map persistentClasses) throws MappingException { + final GeneratedValue generatedValue = idMember.getDirectAnnotationUsage( GeneratedValue.class ); + switch ( generatedValue.strategy() ) { + case UUID -> handleUuidStrategy( idValue, idMember, buildingContext ); + case IDENTITY -> handleIdentityStrategy( idValue, idMember, buildingContext ); + case SEQUENCE -> handleSequenceStrategy( generatedValue ); + case TABLE -> handleTableStrategy( generatedValue ); + case AUTO -> handleAutoStrategy( generatedValue ); + } + } + + private void handleSequenceStrategy(GeneratedValue generatedValue) { + if ( generatedValue.generator().isEmpty() ) { + handleUnnamedSequenceGenerator(); + } + else { + handleNamedSequenceGenerator( generatedValue ); + } + } + + private void handleUnnamedSequenceGenerator() { + // according to the spec, this should locate a generator with the same name as the entity-name + final SequenceGeneratorRegistration globalMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getSequenceGeneratorRegistrations() + .get( entityMapping.getJpaEntityName() ); + if ( globalMatch != null ) { + handleSequenceGenerator( + entityMapping.getJpaEntityName(), + globalMatch.configuration(), + entityMapping, + idValue, + idMember, + buildingContext + ); + return; + } + + handleSequenceGenerator( + entityMapping.getJpaEntityName(), + new SequenceGeneratorJpaAnnotation( buildingContext.getMetadataCollector().getSourceModelBuildingContext() ), + entityMapping, + idValue, + idMember, + buildingContext + ); + } + + private void handleNamedSequenceGenerator(GeneratedValue generatedValue) { + final SequenceGeneratorRegistration globalMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getSequenceGeneratorRegistrations() + .get( generatedValue.generator() ); + if ( globalMatch != null ) { + handleSequenceGenerator( + generatedValue.generator(), + globalMatch.configuration(), + entityMapping, + idValue, + idMember, + buildingContext + ); + return; + } + + handleSequenceGenerator( + generatedValue.generator(), + new SequenceGeneratorJpaAnnotation( generatedValue.generator(), buildingContext.getMetadataCollector().getSourceModelBuildingContext() ), + entityMapping, + idValue, + idMember, + buildingContext + ); + } + + private void handleTableStrategy(GeneratedValue generatedValue) { + if ( generatedValue.generator().isEmpty() ) { + handleUnnamedTableGenerator(); + } + else { + handleNamedTableGenerator( generatedValue ); + } + } + + private void handleUnnamedTableGenerator() { + final TableGeneratorRegistration globalMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getTableGeneratorRegistrations() + .get( entityMapping.getJpaEntityName() ); + if ( globalMatch != null ) { + handleTableGenerator( + entityMapping.getJpaEntityName(), + globalMatch.configuration(), + entityMapping, + idValue, + idMember, + buildingContext + ); + return; + } + + handleTableGenerator( + entityMapping.getJpaEntityName(), + new TableGeneratorJpaAnnotation( buildingContext.getMetadataCollector().getSourceModelBuildingContext() ), + entityMapping, + idValue, + idMember, + buildingContext + ); + } + + private void handleNamedTableGenerator(GeneratedValue generatedValue) { + final TableGeneratorRegistration globalMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getTableGeneratorRegistrations() + .get( generatedValue.generator() ); + if ( globalMatch != null ) { + handleTableGenerator( + generatedValue.generator(), + globalMatch.configuration(), + entityMapping, + idValue, + idMember, + buildingContext + ); + + return; + } + + handleTableGenerator( + generatedValue.generator(), + new TableGeneratorJpaAnnotation( generatedValue.generator(), buildingContext.getMetadataCollector().getSourceModelBuildingContext() ), + entityMapping, + idValue, + idMember, + buildingContext + ); + } + + private void handleAutoStrategy(GeneratedValue generatedValue) { + final String globalRegistrationName; + if ( generatedValue.generator().isEmpty() ) { + globalRegistrationName = entityMapping.getJpaEntityName(); + } + else { + globalRegistrationName = generatedValue.generator(); + } + + final SequenceGeneratorRegistration globalSequenceMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getSequenceGeneratorRegistrations() + .get( globalRegistrationName ); + if ( globalSequenceMatch != null ) { + handleSequenceGenerator( + globalRegistrationName, + globalSequenceMatch.configuration(), + entityMapping, + idValue, + idMember, + buildingContext + ); + return; + } + + final TableGeneratorRegistration globalTableMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getTableGeneratorRegistrations() + .get( globalRegistrationName ); + if ( globalTableMatch != null ) { + handleTableGenerator( + globalRegistrationName, + globalTableMatch.configuration(), + entityMapping, + idValue, + idMember, + buildingContext + ); + return; + } + + final GenericGeneratorRegistration globalGenericMatch = buildingContext.getMetadataCollector() + .getGlobalRegistrations() + .getGenericGeneratorRegistrations() + .get( globalRegistrationName ); + if ( globalGenericMatch != null ) { + handleGenericGenerator( + globalRegistrationName, + globalGenericMatch.configuration(), + entityMapping, + idValue, + idMember, + buildingContext + ); + return; + } + + // Implicit handling of UUID generation + if ( idMember.getType().isImplementor( UUID.class ) + || idMember.getType().isImplementor( String.class )) { + handleUuidStrategy( idValue, idMember, buildingContext ); + return; + } + + + // Handle a few legacy Hibernate generators... + if ( !generatedValue.generator().isEmpty() ) { + final Class legacyNamedGenerator = GeneratorStrategies.mapLegacyNamedGenerator( generatedValue.generator(), idValue ); + if ( legacyNamedGenerator != null ) { + final Map configuration = buildLegacyGeneratorConfig(); + //noinspection unchecked,rawtypes + GeneratorBinder.createGeneratorFrom( + new IdentifierGeneratorDefinition( generatedValue.generator(), legacyNamedGenerator.getName(), configuration ), + idMember, + idValue, + (Map) configuration, + buildingContext + ); + return; + } + } + + handleSequenceGenerator( + globalRegistrationName, + new SequenceGeneratorJpaAnnotation( generatedValue.generator(), buildingContext.getMetadataCollector().getSourceModelBuildingContext() ), + entityMapping, + idValue, + idMember, + buildingContext + ); + } + + private HashMap buildLegacyGeneratorConfig() { + final Database database = buildingContext.getMetadataCollector().getDatabase(); + final Dialect dialect = database.getDialect(); + + final HashMap configuration = new HashMap<>(); + + final String tableName = idValue.getTable().getQuotedName( dialect ); + configuration.put( TABLE, tableName ); + + final Column idColumn = (Column) idValue.getSelectables().get( 0); + final String idColumnName = idColumn.getQuotedName( dialect ); + configuration.put( PK, idColumnName ); + + configuration.put( ENTITY_NAME, entityMapping.getEntityName() ); + configuration.put( JPA_ENTITY_NAME, entityMapping.getJpaEntityName() ); + + // The table name is not really a good default for subselect entities, + // so use the JPA entity name which is short + configuration.put( + IMPLICIT_NAME_BASE, + idValue.getTable().isSubselect() + ? entityMapping.getJpaEntityName() + : idValue.getTable().getName() + ); + + configuration.put( TABLES, identityTablesString( dialect, entityMapping.getRootClass() ) ); + + return configuration; + } + + public static void handleSequenceGenerator( + String generatorName, + SequenceGenerator generatorConfig, + PersistentClass entityMapping, + SimpleValue idValue, + MemberDetails idMember, + MetadataBuildingContext context) { + //generator settings + final Map configuration = new HashMap<>(); + applyBaselineConfiguration( generatorConfig, idValue, entityMapping.getRootClass(), context, configuration::put ); + if ( generatorConfig == null ) { + configuration.put( GENERATOR_NAME, generatorName ); + } + else { + SequenceStyleGenerator.applyConfiguration( generatorConfig, idValue, configuration::put ); + } + + GeneratorBinder.createGeneratorFrom( + new IdentifierGeneratorDefinition( generatorName, SequenceStyleGenerator.class.getName(), configuration ), + idMember, + idValue, + entityMapping, + context + ); + + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/SequenceGeneratorJpaAnnotation.java b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/SequenceGeneratorJpaAnnotation.java index 9ddf6050c6..41cb91dc56 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/SequenceGeneratorJpaAnnotation.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/SequenceGeneratorJpaAnnotation.java @@ -28,7 +28,18 @@ public class SequenceGeneratorJpaAnnotation implements SequenceGenerator { * Used in creating dynamic annotation instances (e.g. from XML) */ public SequenceGeneratorJpaAnnotation(SourceModelBuildingContext modelContext) { - this.name = ""; + this( "", modelContext ); + } + + /** + * Used in creating named, defaulted annotation instances. Generally this + * is a situation where we have:
    + *
  1. {@linkplain GeneratedValue#strategy()} set to {@linkplain jakarta.persistence.GenerationType#SEQUENCE}
  2. + *
  3. {@linkplain GeneratedValue#generator()} set to a non-empty String, but with no matching {@linkplain SequenceGenerator}
  4. + *
+ */ + public SequenceGeneratorJpaAnnotation(String name, SourceModelBuildingContext modelContext) { + this.name = name; this.sequenceName = ""; this.catalog = ""; this.schema = ""; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/TableGeneratorJpaAnnotation.java b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/TableGeneratorJpaAnnotation.java index 6bf4492ef2..9f8f4e142d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/TableGeneratorJpaAnnotation.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/TableGeneratorJpaAnnotation.java @@ -43,7 +43,18 @@ public class TableGeneratorJpaAnnotation implements TableGenerator, UniqueConstr * Used in creating dynamic annotation instances (e.g. from XML) */ public TableGeneratorJpaAnnotation(SourceModelBuildingContext modelContext) { - this.name = ""; + this( "", modelContext ); + } + + /** + * Used in creating named, defaulted annotation instances. Generally this + * is a situation where we have:
    + *
  1. {@linkplain GeneratedValue#strategy()} set to {@linkplain jakarta.persistence.GenerationType#TABLE}
  2. + *
  3. {@linkplain GeneratedValue#generator()} set to a non-empty String, but with no matching {@linkplain TableGenerator}
  4. + *
+ */ + public TableGeneratorJpaAnnotation(String name, SourceModelBuildingContext modelContext) { + this.name = name; this.table = ""; this.catalog = ""; this.schema = ""; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/internal/DomainModelCategorizationCollector.java b/hibernate-core/src/main/java/org/hibernate/boot/models/internal/DomainModelCategorizationCollector.java index 41370cbe6b..709092b255 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/internal/DomainModelCategorizationCollector.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/internal/DomainModelCategorizationCollector.java @@ -117,9 +117,7 @@ public class DomainModelCategorizationCollector { getGlobalRegistrations().collectEmbeddableInstantiatorRegistrations( classDetails ); getGlobalRegistrations().collectFilterDefinitions( classDetails ); - if ( areIdGeneratorsGlobal ) { - getGlobalRegistrations().collectIdGenerators( classDetails ); - } + getGlobalRegistrations().collectIdGenerators( classDetails ); getGlobalRegistrations().collectImportRename( classDetails ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/internal/GlobalRegistrationsImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/models/internal/GlobalRegistrationsImpl.java index 26fb326382..146a7f4af3 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/internal/GlobalRegistrationsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/internal/GlobalRegistrationsImpl.java @@ -11,10 +11,12 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.Supplier; +import org.hibernate.MappingException; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Imported; import org.hibernate.annotations.ParamDef; @@ -56,6 +58,7 @@ import org.hibernate.boot.models.spi.ConverterRegistration; import org.hibernate.boot.models.spi.EmbeddableInstantiatorRegistration; import org.hibernate.boot.models.spi.FilterDefRegistration; import org.hibernate.boot.models.spi.GenericGeneratorRegistration; +import org.hibernate.boot.models.spi.GlobalRegistrar; import org.hibernate.boot.models.spi.GlobalRegistrations; import org.hibernate.boot.models.spi.JavaTypeRegistration; import org.hibernate.boot.models.spi.JdbcTypeRegistration; @@ -81,12 +84,15 @@ import org.hibernate.metamodel.CollectionClassification; import org.hibernate.metamodel.spi.EmbeddableInstantiator; import org.hibernate.models.spi.AnnotationTarget; import org.hibernate.models.spi.ClassDetails; +import org.hibernate.models.spi.MemberDetails; import org.hibernate.models.spi.SourceModelBuildingContext; import org.hibernate.usertype.CompositeUserType; import org.hibernate.usertype.UserCollectionType; import org.hibernate.usertype.UserType; import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Entity; +import jakarta.persistence.GenerationType; import jakarta.persistence.QueryHint; import jakarta.persistence.SequenceGenerator; import jakarta.persistence.TableGenerator; @@ -102,6 +108,7 @@ import static org.hibernate.boot.models.HibernateAnnotations.FILTER_DEF; import static org.hibernate.boot.models.HibernateAnnotations.GENERIC_GENERATOR; import static org.hibernate.boot.models.HibernateAnnotations.JAVA_TYPE_REGISTRATION; import static org.hibernate.boot.models.HibernateAnnotations.JDBC_TYPE_REGISTRATION; +import static org.hibernate.boot.models.JpaAnnotations.ENTITY; import static org.hibernate.boot.models.JpaAnnotations.NAMED_STORED_PROCEDURE_QUERY; import static org.hibernate.boot.models.JpaAnnotations.SEQUENCE_GENERATOR; import static org.hibernate.boot.models.JpaAnnotations.TABLE_GENERATOR; @@ -113,7 +120,7 @@ import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty; /** * @author Steve Ebersole */ -public class GlobalRegistrationsImpl implements GlobalRegistrations { +public class GlobalRegistrationsImpl implements GlobalRegistrations, GlobalRegistrar { private final SourceModelBuildingContext sourceModelContext; private final BootstrapContext bootstrapContext; @@ -144,6 +151,12 @@ public class GlobalRegistrationsImpl implements GlobalRegistrations { this.bootstrapContext = bootstrapContext; } + @Override + public T as(Class type) { + //noinspection unchecked + return (T) this; + } + @Override public List getEntityListenerRegistrations() { return jpaEventListeners == null ? emptyList() : jpaEventListeners; @@ -691,15 +704,20 @@ public class GlobalRegistrationsImpl implements GlobalRegistrations { } public void collectIdGenerators(ClassDetails classDetails) { + if ( !classDetails.getName().endsWith( ".package-info" ) + && !bootstrapContext.getJpaCompliance().isGlobalGeneratorScopeEnabled() ) { + return; + } + classDetails.forEachRepeatedAnnotationUsages( SEQUENCE_GENERATOR, sourceModelContext, - this::collectSequenceGenerator + (sequenceGenerator) -> collectSequenceGenerator( classDetails, sequenceGenerator ) ); classDetails.forEachAnnotationUsage( TABLE_GENERATOR, sourceModelContext, - this::collectTableGenerator + tableGenerator -> collectTableGenerator( classDetails, tableGenerator ) ); classDetails.forEachAnnotationUsage( GENERIC_GENERATOR, @@ -708,6 +726,51 @@ public class GlobalRegistrationsImpl implements GlobalRegistrations { ); } + @Override + public void collectIdGenerators(MemberDetails memberDetails) { + if ( !bootstrapContext.getJpaCompliance().isGlobalGeneratorScopeEnabled() ) { + return; + } + + memberDetails.forEachRepeatedAnnotationUsages( + SEQUENCE_GENERATOR, + sourceModelContext, + (sequenceGenerator) -> collectSequenceGenerator( memberDetails, sequenceGenerator ) + ); + memberDetails.forEachAnnotationUsage( + TABLE_GENERATOR, + sourceModelContext, + tableGenerator -> collectTableGenerator( memberDetails, tableGenerator ) + ); + memberDetails.forEachAnnotationUsage( + GENERIC_GENERATOR, + sourceModelContext, + this::collectGenericGenerator + ); + } + + /** + * Account for implicit naming of sequence and table generators when applied to an entity class per JPA 3.2 + */ + private String determineImplicitGeneratorNameBase(ClassDetails classDetails, GenerationType generationType) { + if ( classDetails.getName().endsWith( ".package-info" ) ) { + throw new MappingException( String.format( + Locale.ROOT, + "@%s placed on package (%s) specified no name", + generationType == GenerationType.SEQUENCE ? SequenceGenerator.class.getSimpleName() : TableGenerator.class.getSimpleName(), + StringHelper.qualifier( classDetails.getName() ) + ) ); + } + final Entity entityAnnotation = classDetails.getDirectAnnotationUsage( ENTITY ); + if ( entityAnnotation != null ) { + final String explicitEntityName = entityAnnotation.name(); + return StringHelper.isNotEmpty( explicitEntityName ) + ? explicitEntityName + : StringHelper.unqualify( classDetails.getName() ); + } + return null; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Sequence generator @@ -752,17 +815,35 @@ public class GlobalRegistrationsImpl implements GlobalRegistrations { } ); } - public void collectSequenceGenerator(SequenceGenerator usage) { - collectSequenceGenerator( new SequenceGeneratorRegistration( usage.name(), usage ) ); + public void collectSequenceGenerator(MemberDetails memberDetails, SequenceGenerator usage) { + collectSequenceGenerator( memberDetails.getDeclaringType(), usage ); } + + public void collectSequenceGenerator(ClassDetails classDetails, SequenceGenerator usage) { + final String registrationName; + if ( !usage.name().isEmpty() ) { + registrationName = usage.name(); + } + else { + final Entity entityAnnotation = classDetails.getDirectAnnotationUsage( Entity.class ); + if ( entityAnnotation != null && !entityAnnotation.name().isEmpty() ) { + registrationName = entityAnnotation.name(); + } + else { + registrationName = StringHelper.unqualify( classDetails.getName() ); + } + } + collectSequenceGenerator( new SequenceGeneratorRegistration( registrationName, usage ) ); + } + + public void collectSequenceGenerator(SequenceGeneratorRegistration generatorRegistration) { checkGeneratorName( generatorRegistration.name() ); if ( sequenceGeneratorRegistrations == null ) { sequenceGeneratorRegistrations = new HashMap<>(); } - sequenceGeneratorRegistrations.put( generatorRegistration.name(), generatorRegistration ); } @@ -842,16 +923,57 @@ public class GlobalRegistrationsImpl implements GlobalRegistrations { } ); } - public void collectTableGenerator(TableGenerator usage) { - collectTableGenerator( new TableGeneratorRegistration( usage.name(), usage ) ); + public void collectTableGenerator(ClassDetails classDetails, TableGenerator usage) { + final TableGeneratorJpaAnnotation generatorAnnotation = new TableGeneratorJpaAnnotation( + usage, + sourceModelContext + ); + + final Entity entityAnnotation = classDetails.getDirectAnnotationUsage( Entity.class ); + final String simpleName; + if ( entityAnnotation != null && !entityAnnotation.name().isEmpty() ) { + simpleName = entityAnnotation.name(); + } + else { + simpleName = StringHelper.unqualify( classDetails.getName() ); + } + + final String registrationName; + if ( !usage.name().isEmpty() ) { + registrationName = usage.name(); + } + else { + registrationName = simpleName; + } + generatorAnnotation.name( registrationName ); + collectTableGenerator( new TableGeneratorRegistration( registrationName, usage ) ); + + } + + public void collectTableGenerator(MemberDetails memberDetails, TableGenerator usage) { + final String registrationName; + if ( !usage.name().isEmpty() ) { + registrationName = usage.name(); + } + else { + final Entity entityAnnotation = memberDetails.getDeclaringType().getDirectAnnotationUsage( Entity.class ); + if ( entityAnnotation != null && !entityAnnotation.name().isEmpty() ) { + registrationName = entityAnnotation.name(); + } + else { + registrationName = StringHelper.unqualify( memberDetails.getDeclaringType().getName() ); + } + } + collectTableGenerator( new TableGeneratorRegistration( registrationName, usage ) ); + } public void collectTableGenerator(TableGeneratorRegistration generatorRegistration) { checkGeneratorName( generatorRegistration.name() ); + if ( tableGeneratorRegistrations == null ) { tableGeneratorRegistrations = new HashMap<>(); } - tableGeneratorRegistrations.put( generatorRegistration.name(), generatorRegistration ); } @@ -879,6 +1001,9 @@ public class GlobalRegistrationsImpl implements GlobalRegistrations { } public void collectGenericGenerator(GenericGenerator usage) { + if ( usage.name().isEmpty() ) { + return; + } collectGenericGenerator( new GenericGeneratorRegistration( usage.name(), usage ) ); } @@ -888,7 +1013,6 @@ public class GlobalRegistrationsImpl implements GlobalRegistrations { if ( genericGeneratorRegistrations == null ) { genericGeneratorRegistrations = new HashMap<>(); } - genericGeneratorRegistrations.put( generatorRegistration.name(), generatorRegistration ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/spi/GlobalRegistrar.java b/hibernate-core/src/main/java/org/hibernate/boot/models/spi/GlobalRegistrar.java new file mode 100644 index 0000000000..9740c1a007 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/spi/GlobalRegistrar.java @@ -0,0 +1,16 @@ +/* + * 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.boot.models.spi; + +import org.hibernate.models.spi.MemberDetails; + +/** + * @author Steve Ebersole + */ +public interface GlobalRegistrar { + void collectIdGenerators(MemberDetails memberDetails); +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/spi/GlobalRegistrations.java b/hibernate-core/src/main/java/org/hibernate/boot/models/spi/GlobalRegistrations.java index da721a7ebb..ba4e5332f3 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/spi/GlobalRegistrations.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/spi/GlobalRegistrations.java @@ -55,4 +55,6 @@ public interface GlobalRegistrations { Map getNamedStoredProcedureQueryRegistrations(); // todo : named entity graphs + + T as(Class type); } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java index cd6b734c9e..5dc6372411 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java @@ -10,6 +10,7 @@ import java.lang.invoke.MethodHandles; import java.util.Iterator; import java.util.Properties; import java.util.Set; +import java.util.function.BiConsumer; import org.hibernate.HibernateException; import org.hibernate.MappingException; @@ -25,12 +26,14 @@ import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.generator.GeneratorCreationContext; import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.id.SequenceMismatchStrategy; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.mapping.SimpleValue; import org.hibernate.service.ServiceRegistry; import org.hibernate.tool.schema.Action; import org.hibernate.tool.schema.extract.spi.SequenceInformation; @@ -39,6 +42,8 @@ import org.hibernate.type.Type; import org.jboss.logging.Logger; +import jakarta.persistence.SequenceGenerator; + import static java.util.Collections.singleton; import static org.hibernate.id.IdentifierGeneratorHelper.getNamingStrategy; import static org.hibernate.id.enhanced.OptimizerFactory.determineImplicitOptimizerName; @@ -110,7 +115,7 @@ import static org.hibernate.internal.util.config.ConfigurationHelper.getString; * @author Lukasz Antoniak */ public class SequenceStyleGenerator - implements PersistentIdentifierGenerator, BulkInsertionCapableIdentifierGenerator { + implements PersistentIdentifierGenerator, BulkInsertionCapableIdentifierGenerator, BeforeExecutionGenerator { private static final CoreMessageLogger LOG = Logger.getMessageLogger( MethodHandles.lookup(), @@ -583,4 +588,27 @@ public class SequenceStyleGenerator return ( catalog == null || catalog.equals( jdbcEnvironment.getCurrentCatalog() ) ) && ( schema == null || schema.equals( jdbcEnvironment.getCurrentSchema() ) ); } + + public static void applyConfiguration(SequenceGenerator generatorConfig, SimpleValue idValue, BiConsumer configCollector) { + if ( !generatorConfig.sequenceName().isEmpty() ) { + configCollector.accept( SEQUENCE_PARAM, generatorConfig.sequenceName() ); + } + if ( !generatorConfig.catalog().isEmpty() ) { + configCollector.accept( CATALOG, generatorConfig.catalog() ); + } + if ( !generatorConfig.schema().isEmpty() ) { + configCollector.accept( SCHEMA, generatorConfig.schema() ); + } + if ( !generatorConfig.options().isEmpty() ) { + configCollector.accept( OPTIONS, generatorConfig.options() ); + } + + configCollector.accept( INITIAL_PARAM, Integer.toString( generatorConfig.initialValue() ) ); + if ( generatorConfig.allocationSize() == 50 ) { + // don't do anything - assuming a proper default is already set + } + else { + configCollector.accept( INCREMENT_PARAM, Integer.toString( generatorConfig.allocationSize() ) ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java index 2e2270d9df..b620768b82 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/TableGenerator.java @@ -14,6 +14,7 @@ import java.sql.SQLException; import java.sql.Types; import java.util.Map; import java.util.Properties; +import java.util.function.BiConsumer; import org.hibernate.LockMode; import org.hibernate.LockOptions; @@ -43,9 +44,11 @@ import org.hibernate.id.IdentifierGeneratorHelper; import org.hibernate.id.IntegralDataTypeHolder; import org.hibernate.id.PersistentIdentifierGenerator; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.jdbc.AbstractReturningWork; import org.hibernate.mapping.Column; import org.hibernate.mapping.PrimaryKey; +import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.Table; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.BasicTypeRegistry; @@ -55,6 +58,7 @@ import org.hibernate.type.Type; import org.jboss.logging.Logger; import static java.util.Collections.singletonMap; +import static org.hibernate.boot.model.internal.GeneratorBinder.applyIfNotEmpty; import static org.hibernate.id.IdentifierGeneratorHelper.getNamingStrategy; import static org.hibernate.id.enhanced.OptimizerFactory.determineImplicitOptimizerName; import static org.hibernate.internal.util.StringHelper.isEmpty; @@ -225,6 +229,7 @@ public class TableGenerator implements PersistentIdentifierGenerator { private String contributor; private String options; + /** * Type mapping for the identifier. * @@ -723,4 +728,35 @@ public class TableGenerator implements PersistentIdentifierGenerator { updateQuery = buildUpdateQuery( formattedPhysicalTableName, context ); insertQuery = buildInsertQuery( formattedPhysicalTableName, context ); } + + public static void applyConfiguration( + jakarta.persistence.TableGenerator generatorConfig, + SimpleValue idValue, + Map configuration) { + applyConfiguration( generatorConfig, idValue, configuration::put ); + } + + public static void applyConfiguration( + jakarta.persistence.TableGenerator generatorConfig, + SimpleValue idValue, + BiConsumer configurationCollector) { + configurationCollector.accept( CONFIG_PREFER_SEGMENT_PER_ENTITY, "true" ); + + applyIfNotEmpty( TABLE_PARAM, generatorConfig.table(), configurationCollector ); + applyIfNotEmpty( CATALOG, generatorConfig.catalog(), configurationCollector ); + applyIfNotEmpty( SCHEMA, generatorConfig.schema(), configurationCollector ); + applyIfNotEmpty( OPTIONS, generatorConfig.options(), configurationCollector ); + + applyIfNotEmpty( SEGMENT_COLUMN_PARAM, generatorConfig.pkColumnName(), configurationCollector ); + applyIfNotEmpty( SEGMENT_VALUE_PARAM, generatorConfig.pkColumnValue(), configurationCollector ); + applyIfNotEmpty( VALUE_COLUMN_PARAM, generatorConfig.valueColumnName(), configurationCollector ); + + configurationCollector.accept( INITIAL_PARAM, Integer.toString( generatorConfig.initialValue() + 1 ) ); + if ( generatorConfig.allocationSize() == 50 ) { + // don't do anything - assuming a proper default is already set + } + else { + configurationCollector.accept( INCREMENT_PARAM, Integer.toString( generatorConfig.allocationSize() ) ); + } + } } 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 index 16dab2c602..029212a38c 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/uuid/UuidGenerator.java @@ -19,7 +19,7 @@ import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.generator.EventType; import org.hibernate.generator.EventTypeSets; import org.hibernate.generator.GeneratorCreationContext; -import org.hibernate.generator.BeforeExecutionGenerator; +import org.hibernate.models.spi.MemberDetails; import org.hibernate.type.descriptor.java.UUIDJavaType; import org.hibernate.type.descriptor.java.UUIDJavaType.ValueTransformer; @@ -49,7 +49,47 @@ public class UuidGenerator implements BeforeExecutionGenerator { valueTransformer = determineProperTransformer( memberType ); } - private UuidGenerator( + /** + * This form is used when there is no {@code @UuidGenerator} but we know we want this generator + */ + @Internal + public UuidGenerator( + org.hibernate.annotations.UuidGenerator config, + MemberDetails memberDetails) { + generator = determineValueGenerator( config, memberDetails ); + + final Class memberType = memberDetails.getType().determineRawClass().toJavaClass(); + valueTransformer = determineProperTransformer( memberType ); + } + + private static UuidValueGenerator determineValueGenerator( + org.hibernate.annotations.UuidGenerator config, + MemberDetails memberDetails) { + if ( config != null ) { + 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(), + memberDetails.getDeclaringType().getName(), + memberDetails.getName() + ) + ); + } + return instantiateCustomGenerator( config.algorithm() ); + } + else if ( config.style() == TIME ) { + return new CustomVersionOneStrategy(); + } + } + + return StandardRandomStrategy.INSTANCE; + } + + @Internal + public UuidGenerator( org.hibernate.annotations.UuidGenerator config, Member idMember) { if ( config.algorithm() != UuidValueGenerator.class ) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java index 4fd922b427..1a71df69bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java @@ -570,6 +570,18 @@ public final class ConfigurationHelper { .getPreferredSqlTypeCodeForArray(); } + public static void setIfNotEmpty(String value, String settingName, Map configuration) { + if ( StringHelper.isNotEmpty( value ) ) { + configuration.put( settingName, value ); + } + } + + public static void setIfNotNull(Object value, String settingName, Map configuration) { + if ( value != null ) { + configuration.put( settingName, value ); + } + } + private static class TypeCodeConverter implements ConfigurationService.Converter { public static final TypeCodeConverter INSTANCE = new TypeCodeConverter(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/IdClassAndAssociationsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/IdClassAndAssociationsTest.java index 047ab608b9..6dc3e28f81 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/IdClassAndAssociationsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/IdClassAndAssociationsTest.java @@ -87,7 +87,7 @@ public class IdClassAndAssociationsTest { @Table(name = "course_enrollment") public static class CourseEnrollment { @Id - @GeneratedValue(strategy = GenerationType.AUTO) + @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; @Column(name = "course") @@ -123,7 +123,7 @@ public class IdClassAndAssociationsTest { @Table(name = "units") public static class Unit { @Id - @GeneratedValue(strategy = GenerationType.AUTO) + @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; private String name; @@ -140,7 +140,7 @@ public class IdClassAndAssociationsTest { @Table(name = "users") public static class User { @Id - @GeneratedValue(strategy = GenerationType.AUTO) + @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; @Column(name = "first_name") diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/NewGeneratorMappingsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/NewGeneratorMappingsTest.java index 0182c9afbd..b4bd300bf2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/NewGeneratorMappingsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/NewGeneratorMappingsTest.java @@ -12,6 +12,8 @@ import org.hibernate.id.enhanced.NoopOptimizer; import org.hibernate.id.enhanced.PooledOptimizer; import org.hibernate.id.enhanced.SequenceStyleGenerator; import org.hibernate.id.enhanced.TableGenerator; +import org.hibernate.orm.test.annotations.id.generationmappings.sub.DedicatedSequenceEntity1; +import org.hibernate.orm.test.annotations.id.generationmappings.sub.DedicatedSequenceEntity2; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.DomainModel; @@ -130,7 +132,8 @@ public class NewGeneratorMappingsTest { @JiraKey(value = "HHH-6790") public void testSequencePerEntity(SessionFactoryScope scope) { // Checking first entity. - EntityPersister persister = scope.getSessionFactory().getRuntimeMetamodels().getMappingMetamodel().getEntityDescriptor(DedicatedSequenceEntity1.class.getName()); + EntityPersister persister = scope.getSessionFactory().getRuntimeMetamodels().getMappingMetamodel().getEntityDescriptor( + DedicatedSequenceEntity1.class.getName()); IdentifierGenerator generator = persister.getIdentifierGenerator(); assertTrue( SequenceStyleGenerator.class.isInstance( generator ) ); SequenceStyleGenerator seqGenerator = (SequenceStyleGenerator) generator; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/DedicatedSequenceEntity1.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/sub/DedicatedSequenceEntity1.java similarity index 88% rename from hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/DedicatedSequenceEntity1.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/sub/DedicatedSequenceEntity1.java index 2b9e3a780a..a2096e28f2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/DedicatedSequenceEntity1.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/sub/DedicatedSequenceEntity1.java @@ -2,9 +2,9 @@ * 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 . + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. */ -package org.hibernate.orm.test.annotations.id.generationmappings; +package org.hibernate.orm.test.annotations.id.generationmappings.sub; import java.io.Serializable; import jakarta.persistence.Entity; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/DedicatedSequenceEntity2.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/sub/DedicatedSequenceEntity2.java similarity index 88% rename from hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/DedicatedSequenceEntity2.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/sub/DedicatedSequenceEntity2.java index bdbf3dbfdf..0f8e1da732 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/DedicatedSequenceEntity2.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/sub/DedicatedSequenceEntity2.java @@ -2,9 +2,9 @@ * 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 . + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. */ -package org.hibernate.orm.test.annotations.id.generationmappings; +package org.hibernate.orm.test.annotations.id.generationmappings.sub; import java.io.Serializable; import jakarta.persistence.Entity; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/package-info.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/sub/package-info.java similarity index 86% rename from hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/package-info.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/sub/package-info.java index 5ef1006221..6ffa4e33d2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/package-info.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generationmappings/sub/package-info.java @@ -2,7 +2,7 @@ * 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 . + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. */ @GenericGenerator(name = "SequencePerEntityGenerator", strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator", @@ -12,7 +12,7 @@ } ) -package org.hibernate.orm.test.annotations.id.generationmappings; +package org.hibernate.orm.test.annotations.id.generationmappings.sub; import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.Parameter; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/SimpleIdTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/SimpleIdTests.java new file mode 100644 index 0000000000..a2249087da --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/SimpleIdTests.java @@ -0,0 +1,141 @@ +/* + * 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.annotations.id.generators; + +import java.util.UUID; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("JUnitMalformedDeclaration") +public class SimpleIdTests { + @Test + @DomainModel(annotatedClasses = Entity1.class) + @SessionFactory + void testSimpleAutoFallback(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + session.persist( new Entity1( "1" ) ); + } ); + } + + @Test + @DomainModel(annotatedClasses = Entity2.class) + @SessionFactory + void testSimpleTableFallback(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + session.persist( new Entity2( "2" ) ); + } ); + } + + @Test + @DomainModel(annotatedClasses = Entity3.class) + @SessionFactory + void testSimpleNamedGenerator(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + session.persist( new Entity3( "3" ) ); + } ); + } + + @Test + @DomainModel(annotatedClasses = Entity4.class) + @SessionFactory + void testSimpleUuidFallback(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + session.persist( new Entity4( "4" ) ); + } ); + } + + @Test + @DomainModel(annotatedClasses = Entity5.class) + @SessionFactory + void testSimpleUuidAsStringFallback(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + session.persist( new Entity5( "5" ) ); + } ); + } + + @Entity(name="Entity1") + @Table(name="Entity1") + public static class Entity1 { + @Id + @GeneratedValue + private Integer id; + private String name; + public Entity1() { + } + public Entity1(String name) { + this.name = name; + } + } + + @Entity(name="Entity2") + @Table(name="Entity2") + public static class Entity2 { + @Id + @GeneratedValue(strategy = GenerationType.TABLE) + private Integer id; + private String name; + public Entity2() { + } + public Entity2(String name) { + this.name = name; + } + } + + @Entity(name="Entity3") + @Table(name="Entity3") + public static class Entity3 { + @Id + @GeneratedValue(generator = "increment") + private Integer id; + private String name; + public Entity3() { + } + public Entity3(String name) { + this.name = name; + } + } + + @Entity(name="Entity4") + @Table(name="Entity4") + public static class Entity4 { + @Id + @GeneratedValue + private UUID id; + private String name; + public Entity4() { + } + public Entity4(String name) { + this.name = name; + } + } + + @Entity(name="Entity5") + @Table(name="Entity5") + public static class Entity5 { + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private String id; + private String name; + public Entity5() { + } + public Entity5(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/UnnamedGeneratorTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/UnnamedGeneratorTests.java new file mode 100644 index 0000000000..bf322db74a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/UnnamedGeneratorTests.java @@ -0,0 +1,177 @@ +/* + * 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.annotations.id.generators; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.generator.Generator; +import org.hibernate.mapping.RootClass; + +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.TableGenerator; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +public class UnnamedGeneratorTests { + @Test + void testAutoWithNoGenerator() { + checkSequence( Entity1.class, "Entity1_seq" ); + } + + @Test + void testAutoWithSequenceGenerator() { + checkSequence( Entity2.class, "Entity2_seq" ); + checkSequence( Entity3.class, "my_seq" ); + checkSequence( Entity4.class, "another_seq" ); + } + + @Test + void testAutoWithTableGenerator() { + checkTableGenerator( Entity5.class, "hibernate_sequences", "Entity5" ); + checkTableGenerator( Entity6.class, "my_sequences", "Entity6" ); + checkTableGenerator( Entity7.class, "sequences_table", "Entity7" ); + checkTableGenerator( Entity8.class, "sequences_table", "ent_8" ); + } + + private void checkSequence(Class entityClass, String expectedDbName) { + // strictly-global = false + withGenerator( entityClass, false, (generator) -> { + final String name = SEQUENCE_NAME_EXTRACTOR.apply( generator ); + assertThat( name ).isEqualToIgnoringCase( expectedDbName ); + } ); + + // strictly-global = true + withGenerator( entityClass, true, (generator) -> { + final String name = SEQUENCE_NAME_EXTRACTOR.apply( generator ); + assertThat( name ).isEqualToIgnoringCase( expectedDbName ); + } ); + } + + private void checkTableGenerator(Class entityClass, String expectedTableName, String expectedSegmentName) { + // strictly-global = false + withGenerator( entityClass, false, (generator) -> { + final org.hibernate.id.enhanced.TableGenerator tableGenerator = (org.hibernate.id.enhanced.TableGenerator) generator; + assertThat( tableGenerator.getTableName() ).isEqualToIgnoringCase( expectedTableName ); + assertThat( tableGenerator.getSegmentValue() ).isEqualTo( expectedSegmentName ); + } ); + + // strictly-global = true + withGenerator( entityClass, true, (generator) -> { + final org.hibernate.id.enhanced.TableGenerator tableGenerator = (org.hibernate.id.enhanced.TableGenerator) generator; + assertThat( tableGenerator.getTableName() ).isEqualToIgnoringCase( expectedTableName ); + assertThat( tableGenerator.getSegmentValue() ).isEqualTo( expectedSegmentName ); + } ); + } + + private void withGenerator(Class entityClass, boolean strictlyGlobal, Consumer checks) { + try (StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE, strictlyGlobal ) + .build()) { + final Metadata metadata = new MetadataSources( serviceRegistry ) + .addAnnotatedClasses( entityClass ) + .buildMetadata(); + final RootClass entityBinding = metadata.getEntityBinding( entityClass.getName() ).getRootClass(); + final Generator generator = entityBinding.getIdentifier().createGenerator( + metadata.getDatabase().getDialect(), + entityBinding, + entityBinding.getIdentifierProperty() + ); + + checks.accept( generator ); + } + } + + @Entity(name="Entity1") + public static class Entity1 { + @Id + @GeneratedValue + private Integer id; + private String name; + } + + @Entity(name="Entity2") + public static class Entity2 { + @Id + @GeneratedValue + @SequenceGenerator + private Integer id; + private String name; + } + + @Entity(name="Entity3") + public static class Entity3 { + @Id + @GeneratedValue + @SequenceGenerator(sequenceName = "my_seq") + private Integer id; + private String name; + } + + @Entity(name="Entity4") + @SequenceGenerator(sequenceName = "another_seq") + public static class Entity4 { + @Id + @GeneratedValue + private Integer id; + private String name; + } + + @Entity(name="Entity5") + public static class Entity5 { + @Id + @GeneratedValue + @TableGenerator + private Integer id; + private String name; + } + + @Entity(name="Entity6") + public static class Entity6 { + @Id + @GeneratedValue + @TableGenerator(table = "my_sequences") + private Integer id; + private String name; + } + + @Entity(name="Entity7") + @TableGenerator(table = "sequences_table") + public static class Entity7 { + @Id + @GeneratedValue + private Integer id; + private String name; + } + + @Entity(name="Entity8") + @TableGenerator(table = "sequences_table", pkColumnValue = "ent_8") + public static class Entity8 { + @Id + @GeneratedValue + private Integer id; + private String name; + } + + private static final Function SEQUENCE_NAME_EXTRACTOR = (generator) -> { + return ( (org.hibernate.id.enhanced.SequenceStyleGenerator) generator ).getDatabaseStructure().getPhysicalName().getObjectName().getText(); + }; +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/entity/ClassLevelGeneratorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/entity/ClassLevelGeneratorTest.java index 8e94ae005f..221822a56c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/entity/ClassLevelGeneratorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/entity/ClassLevelGeneratorTest.java @@ -1,45 +1,49 @@ package org.hibernate.orm.test.annotations.id.generators.entity; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.SequenceGenerator; import jakarta.persistence.TableGenerator; -import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.FailureExpected; -import org.hibernate.testing.orm.junit.Jira; -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; import static org.junit.jupiter.api.Assertions.assertEquals; @SessionFactory -@DomainModel(annotatedClasses = - {ClassLevelGeneratorTest.EntityWithAnonSequenceGenerator.class, - ClassLevelGeneratorTest.EntityWithAnonTableGenerator.class}) -@FailureExpected( reason = "Support for unnamed generators is not implemented yet" ) -@Jira( "https://hibernate.atlassian.net/browse/HHH-18498" ) +@DomainModel( + annotatedClasses = { + ClassLevelGeneratorTest.EntityWithAnonSequenceGenerator.class, + ClassLevelGeneratorTest.EntityWithAnonTableGenerator.class + } +) public class ClassLevelGeneratorTest { @Test void testAnonGenerator(SessionFactoryScope scope) { + // this won't work with global scoping due to + assertThat( scope.getSessionFactory().getSessionFactoryOptions().getJpaCompliance().isGlobalGeneratorScopeEnabled() ).isFalse(); + scope.inSession(s-> { EntityWithAnonSequenceGenerator entity1 = new EntityWithAnonSequenceGenerator(); EntityWithAnonTableGenerator entity2 = new EntityWithAnonTableGenerator(); s.persist(entity1); s.persist(entity2); assertEquals(42, entity1.id); - assertEquals(69, entity2.id); + assertEquals(70, entity2.id); }); } - @Entity + @Entity(name = "EntityWithAnonSequenceGenerator") @SequenceGenerator(initialValue = 42) static class EntityWithAnonSequenceGenerator { @Id @GeneratedValue long id; } - @Entity + @Entity(name = "EntityWithAnonTableGenerator") @TableGenerator(initialValue = 69) static class EntityWithAnonTableGenerator { @Id diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/entity/DefaultedGeneratorWithGlobalScopeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/entity/DefaultedGeneratorWithGlobalScopeTest.java new file mode 100644 index 0000000000..bd15f4cc66 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/entity/DefaultedGeneratorWithGlobalScopeTest.java @@ -0,0 +1,58 @@ +/* + * 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.annotations.id.generators.entity; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.TableGenerator; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SessionFactory +@ServiceRegistry(settings = @Setting(name= AvailableSettings.JPA_ID_GENERATOR_GLOBAL_SCOPE_COMPLIANCE, value = "true")) +@DomainModel(annotatedClasses = { + DefaultedGeneratorWithGlobalScopeTest.EntityWithAnonSequenceGenerator.class, + DefaultedGeneratorWithGlobalScopeTest.EntityWithAnonTableGenerator.class +}) +public class DefaultedGeneratorWithGlobalScopeTest { + @Test + void testAnonGenerator(SessionFactoryScope scope) { + scope.inSession(s-> { + EntityWithAnonSequenceGenerator entity1 = new EntityWithAnonSequenceGenerator(); + EntityWithAnonTableGenerator entity2 = new EntityWithAnonTableGenerator(); + s.persist(entity1); + s.persist(entity2); + assertEquals(42, entity1.id); + assertEquals(70, entity2.id); + }); + } + @Entity(name = "EntityWithAnonSequenceGenerator") + @SequenceGenerator(initialValue = 42) + static class EntityWithAnonSequenceGenerator { + @Id + @GeneratedValue + long id; + } + @Entity(name = "EntityWithAnonTableGenerator") + @TableGenerator(initialValue = 69) + static class EntityWithAnonTableGenerator { + @Id + @GeneratedValue + long id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/entity/FieldLevelGeneratorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/entity/FieldLevelGeneratorTest.java index 4609bab273..becbc5bc62 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/entity/FieldLevelGeneratorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/entity/FieldLevelGeneratorTest.java @@ -18,8 +18,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; @DomainModel(annotatedClasses = {FieldLevelGeneratorTest.EntityWithAnonSequenceGenerator.class, FieldLevelGeneratorTest.EntityWithAnonTableGenerator.class}) -@FailureExpected( reason = "Support for unnamed generators is not implemented yet" ) -@Jira( "https://hibernate.atlassian.net/browse/HHH-18498" ) public class FieldLevelGeneratorTest { @Test void testAnonGenerator(SessionFactoryScope scope) { @@ -29,17 +27,19 @@ public class FieldLevelGeneratorTest { s.persist(entity1); s.persist(entity2); assertEquals(42, entity1.id); - assertEquals(69, entity2.id); + assertEquals(70, entity2.id); }); } - @Entity + + @Entity(name = "EntityWithAnonSequenceGenerator") static class EntityWithAnonSequenceGenerator { @Id @GeneratedValue @SequenceGenerator(initialValue = 42) long id; } - @Entity + + @Entity(name = "EntityWithAnonTableGenerator") static class EntityWithAnonTableGenerator { @Id @GeneratedValue diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/pkg/PackageLevelGeneratorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/pkg/PackageLevelGeneratorTest.java index 4885b13fca..42601d21e0 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/pkg/PackageLevelGeneratorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/generators/pkg/PackageLevelGeneratorTest.java @@ -2,6 +2,7 @@ package org.hibernate.orm.test.annotations.id.generators.pkg; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.FailureExpected; @@ -13,33 +14,52 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @SessionFactory -@DomainModel(annotatedClasses = - {PackageLevelGeneratorTest.EntityWithAnonSequenceGenerator.class, - PackageLevelGeneratorTest.EntityWithAnonTableGenerator.class}) -@FailureExpected( reason = "Support for unnamed generators is not implemented yet" ) -@Jira( "https://hibernate.atlassian.net/browse/HHH-18498" ) +@DomainModel(annotatedClasses = { + PackageLevelGeneratorTest.EntityWithDefaultedPackageGenerator.class, + PackageLevelGeneratorTest.EntityWithDefaultedPackageGenerator2.class, + PackageLevelGeneratorTest.EntityWithDefaultedPackageGenerator3.class, + PackageLevelGeneratorTest.EntityWithDefaultedPackageGenerator4.class +}) public class PackageLevelGeneratorTest { @Test void testAnonGenerator(SessionFactoryScope scope) { scope.inSession(s-> { - EntityWithAnonSequenceGenerator entity1 = new EntityWithAnonSequenceGenerator(); - EntityWithAnonTableGenerator entity2 = new EntityWithAnonTableGenerator(); + EntityWithDefaultedPackageGenerator entity1 = new EntityWithDefaultedPackageGenerator(); + EntityWithDefaultedPackageGenerator2 entity2 = new EntityWithDefaultedPackageGenerator2(); + EntityWithDefaultedPackageGenerator3 entity3 = new EntityWithDefaultedPackageGenerator3(); + EntityWithDefaultedPackageGenerator4 entity4 = new EntityWithDefaultedPackageGenerator4(); s.persist(entity1); s.persist(entity2); + s.persist(entity3); + s.persist(entity4); assertEquals(42, entity1.id); - assertEquals(69, entity2.id); + assertEquals(42, entity2.id); + assertEquals(42, entity3.id); + assertEquals(70, entity4.id); }); } - @Entity - static class EntityWithAnonSequenceGenerator { + @Entity(name="EntityWithDefaultedPackageGenerator") + static class EntityWithDefaultedPackageGenerator { @Id @GeneratedValue long id; } - @Entity - static class EntityWithAnonTableGenerator { + @Entity(name = "EntityWithDefaultedPackageGenerator2") + static class EntityWithDefaultedPackageGenerator2 { @Id @GeneratedValue long id; } + @Entity(name = "EntityWithDefaultedPackageGenerator3") + static class EntityWithDefaultedPackageGenerator3 { + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + long id; + } + @Entity(name = "EntityWithDefaultedPackageGenerator4") + static class EntityWithDefaultedPackageGenerator4 { + @Id + @GeneratedValue(strategy = GenerationType.TABLE) + long id; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/sequences/IdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/sequences/IdTest.java index 33fa33dfe9..ddca47d191 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/sequences/IdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/sequences/IdTest.java @@ -14,8 +14,8 @@ import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; -import org.hibernate.orm.test.annotations.id.generationmappings.DedicatedSequenceEntity1; -import org.hibernate.orm.test.annotations.id.generationmappings.DedicatedSequenceEntity2; +import org.hibernate.orm.test.annotations.id.generationmappings.sub.DedicatedSequenceEntity1; +import org.hibernate.orm.test.annotations.id.generationmappings.sub.DedicatedSequenceEntity2; import org.hibernate.orm.test.annotations.id.sequences.entities.Ball; import org.hibernate.orm.test.annotations.id.sequences.entities.BreakDance; import org.hibernate.orm.test.annotations.id.sequences.entities.Computer; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java index b115bd4c75..355f60c3a2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/database/qualfiedTableNaming/DefaultCatalogAndSchemaTest.java @@ -1130,7 +1130,7 @@ public class DefaultCatalogAndSchemaTest { public static class EntityWithDefaultQualifiersWithTableGenerator { public static final String NAME = "EntityWithDefaultQualifiersWithTableGenerator"; @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GeneratedValue(strategy = GenerationType.TABLE, generator = NAME + "_generator") @TableGenerator(name = NAME + "_generator", table = NAME + "_tableseq") private Long id; @Basic @@ -1142,7 +1142,7 @@ public class DefaultCatalogAndSchemaTest { public static class EntityWithExplicitQualifiersWithTableGenerator { public static final String NAME = "EntityWithExplicitQualifiersWithTableGenerator"; @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GeneratedValue(strategy = GenerationType.TABLE, generator = NAME + "_generator") @TableGenerator(name = NAME + "_generator", table = NAME + "_tableseq", catalog = EXPLICIT_CATALOG, schema = EXPLICIT_SCHEMA) private Long id; @@ -1155,7 +1155,7 @@ public class DefaultCatalogAndSchemaTest { public static class EntityWithDefaultQualifiersWithIncrementGenerator { public static final String NAME = "EntityWithDefaultQualifiersWithIncrementGenerator"; @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GeneratedValue(generator = NAME + "_generator") @GenericGenerator(name = NAME + "_generator", strategy = "increment") private Long id; @Basic @@ -1167,7 +1167,7 @@ public class DefaultCatalogAndSchemaTest { public static class EntityWithExplicitQualifiersWithIncrementGenerator { public static final String NAME = "EntityWithExplicitQualifiersWithIncrementGenerator"; @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GeneratedValue(generator = NAME + "_generator") @GenericGenerator(name = NAME + "_generator", strategy = "increment", parameters = { @Parameter(name = "catalog", value = EXPLICIT_CATALOG), @Parameter(name = "schema", value = EXPLICIT_SCHEMA) @@ -1181,7 +1181,7 @@ public class DefaultCatalogAndSchemaTest { public static class EntityWithDefaultQualifiersWithEnhancedSequenceGenerator { public static final String NAME = "EntityWithDefaultQualifiersWithEnhancedSequenceGenerator"; @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GeneratedValue(generator = NAME + "_generator") @GenericGenerator(name = NAME + "_generator", strategy = "enhanced-sequence", parameters = { @Parameter(name = "sequence_name", value = NAME + "_seq") }) @@ -1195,7 +1195,7 @@ public class DefaultCatalogAndSchemaTest { public static class EntityWithExplicitQualifiersWithEnhancedSequenceGenerator { public static final String NAME = "EntityWithExplicitQualifiersWithEnhancedSequenceGenerator"; @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = NAME + "_generator") + @GeneratedValue(generator = NAME + "_generator") @GenericGenerator(name = NAME + "_generator", strategy = "enhanced-sequence", parameters = { @Parameter(name = "sequence_name", value = NAME + "_seq"), @Parameter(name = "catalog", value = EXPLICIT_CATALOG), diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/GeneratedValueTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/GeneratedValueTest.java index 99c64cb1ee..5aa49bd45d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/GeneratedValueTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/uuid/GeneratedValueTest.java @@ -25,6 +25,7 @@ import org.hibernate.dialect.SybaseDialect; import org.hibernate.generator.Generator; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.UUIDGenerator; +import org.hibernate.id.uuid.UuidGenerator; import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.RootClass; @@ -60,9 +61,8 @@ public class GeneratedValueTest { assertEquals( UUID.class, entityBinding.getIdentifier().getType().getReturnedClass() ); KeyValue keyValue = entityBinding.getIdentifier(); Dialect dialect = metadata.getDatabase().getDialect(); - final Generator generator1 = keyValue.createGenerator( dialect, (RootClass) entityBinding); - IdentifierGenerator generator = generator1 instanceof IdentifierGenerator ? (IdentifierGenerator) generator1 : null; - assertTyping( UUIDGenerator.class, generator ); + final Generator generator = keyValue.createGenerator( dialect, (RootClass) entityBinding); + assertTyping( UuidGenerator.class, generator ); // now a functional test SessionFactory sf = metadata.buildSessionFactory(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/enhanced/auto/AutoGenerationTypeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/enhanced/auto/AutoGenerationTypeTests.java index 8b63f27fe4..6684c17d81 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/enhanced/auto/AutoGenerationTypeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/enhanced/auto/AutoGenerationTypeTests.java @@ -15,6 +15,8 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; +import org.hamcrest.MatcherAssert; + import org.hibernate.annotations.CollectionId; import org.hibernate.annotations.CollectionIdJdbcTypeCode; import org.hibernate.boot.Metadata; @@ -28,6 +30,7 @@ import org.hibernate.id.IncrementGenerator; import org.hibernate.id.UUIDGenerator; import org.hibernate.id.enhanced.DatabaseStructure; import org.hibernate.id.enhanced.SequenceStyleGenerator; +import org.hibernate.id.uuid.UuidGenerator; import org.hibernate.mapping.IdentifierBag; import org.hibernate.mapping.KeyValue; import org.hibernate.mapping.PersistentClass; @@ -131,12 +134,11 @@ public class AutoGenerationTypeTests { .buildMetadata(); final PersistentClass entityBinding = metadata.getEntityBinding( Entity4.class.getName() ); + final Property identifierProperty = entityBinding.getRootClass().getIdentifierProperty(); final KeyValue idMapping = entityBinding.getRootClass().getIdentifier(); Dialect dialect = new H2Dialect(); - final Generator generator = idMapping.createGenerator( dialect, entityBinding.getRootClass()); - final IdentifierGenerator idGenerator = generator instanceof IdentifierGenerator ? (IdentifierGenerator) generator : null; - - assertThat( idGenerator, instanceOf( UUIDGenerator.class ) ); + final Generator generator = idMapping.createGenerator( dialect, entityBinding.getRootClass(), identifierProperty ); + MatcherAssert.assertThat( generator, instanceOf( UuidGenerator.class ) ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/enhanced/sequence/HiLoSequenceMismatchStrategyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/enhanced/sequence/HiLoSequenceMismatchStrategyTest.java index a33f5c1d1f..1411711e84 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/enhanced/sequence/HiLoSequenceMismatchStrategyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/idgen/enhanced/sequence/HiLoSequenceMismatchStrategyTest.java @@ -131,7 +131,7 @@ public class HiLoSequenceMismatchStrategyTest { public static class TestEntity { @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "hilo_sequence_generator") + @GeneratedValue(generator = "hilo_sequence_generator") @GenericGenerator(name = "hilo_sequence_generator", strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator", parameters = { @Parameter(name = "sequence_name", value = sequenceName), @Parameter(name = "initial_value", value = "1"), diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/uuid/implicit/ImplicitUuidGenerationTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/uuid/implicit/ImplicitUuidGenerationTests.java index 8be59e6d97..4f2f6a6994 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/uuid/implicit/ImplicitUuidGenerationTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/uuid/implicit/ImplicitUuidGenerationTests.java @@ -26,7 +26,6 @@ public class ImplicitUuidGenerationTests { @Test @ServiceRegistry @DomainModel(annotatedClasses = Book.class) - @FailureExpected( jiraKey = "HHH-18420", reason = "Implicit UUID generation continues to use UUIDGenerator for the time being" ) void verifyModel(ServiceRegistryScope registryScope, DomainModelScope domainModelScope) { domainModelScope.withHierarchy( Book.class, (descriptor) -> { Helper.verifyAlgorithm( registryScope, domainModelScope, descriptor, StandardRandomStrategy.class ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/uuid/implicit2/Implicit2UuidGenerationTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/uuid/implicit2/Implicit2UuidGenerationTests.java index 0c137d4c45..e5b8d27d7d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/uuid/implicit2/Implicit2UuidGenerationTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/uuid/implicit2/Implicit2UuidGenerationTests.java @@ -11,7 +11,6 @@ import org.hibernate.orm.test.mapping.identifier.uuid.Helper; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModelScope; -import org.hibernate.testing.orm.junit.FailureExpected; import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.ServiceRegistryScope; import org.hibernate.testing.orm.junit.SessionFactory; @@ -26,7 +25,6 @@ public class Implicit2UuidGenerationTests { @Test @ServiceRegistry @DomainModel(annotatedClasses = Book.class) - @FailureExpected( jiraKey = "HHH-18420", reason = "Implicit UUID generation continues to use UUIDGenerator for the time being" ) void verifyModel(ServiceRegistryScope registryScope, DomainModelScope domainModelScope) { domainModelScope.withHierarchy( Book.class, (descriptor) -> { Helper.verifyAlgorithm( registryScope, domainModelScope, descriptor, StandardRandomStrategy.class ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/uuid/implicit3/Implicit3UuidGenerationTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/uuid/implicit3/Implicit3UuidGenerationTests.java index 880305e37c..51c29bf3c8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/uuid/implicit3/Implicit3UuidGenerationTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/identifier/uuid/implicit3/Implicit3UuidGenerationTests.java @@ -26,7 +26,6 @@ public class Implicit3UuidGenerationTests { @Test @ServiceRegistry @DomainModel(annotatedClasses = Book.class) - @FailureExpected( jiraKey = "HHH-18420", reason = "Implicit UUID generation continues to use UUIDGenerator for the time being" ) void verifyModel(ServiceRegistryScope registryScope, DomainModelScope domainModelScope) { domainModelScope.withHierarchy( Book.class, (descriptor) -> { Helper.verifyAlgorithm( registryScope, domainModelScope, descriptor, StandardRandomStrategy.class ); diff --git a/local-build-plugins/settings.gradle b/local-build-plugins/settings.gradle deleted file mode 100644 index 1412c27cbe..0000000000 --- a/local-build-plugins/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'local-build-plugins'