From f3e31fe427c28958497eff46a0257b18016d59f4 Mon Sep 17 00:00:00 2001 From: Gavin Date: Tue, 20 Dec 2022 01:43:38 +0100 Subject: [PATCH] remove ancient limitation that allowed only one property as UK for 'select' id generator --- .../generator/InDatabaseGenerator.java | 8 ++--- .../internal/CurrentTimestampGeneration.java | 1 - .../generator/internal/NaturalIdHelper.java | 32 +++++++++---------- .../id/PostInsertIdentityPersister.java | 20 ++++++++++-- .../org/hibernate/id/SelectGenerator.java | 8 ++--- .../id/insert/UniqueKeySelectingDelegate.java | 23 +++++++++---- .../entity/AbstractEntityPersister.java | 18 ++++++++--- .../tuple/entity/EntityMetamodel.java | 22 +++++++------ 8 files changed, 82 insertions(+), 50 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/generator/InDatabaseGenerator.java b/hibernate-core/src/main/java/org/hibernate/generator/InDatabaseGenerator.java index f070c0035d..060f930e57 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/InDatabaseGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/InDatabaseGenerator.java @@ -15,7 +15,7 @@ import org.hibernate.id.insert.InsertReturningDelegate; import org.hibernate.id.insert.UniqueKeySelectingDelegate; import org.hibernate.persister.entity.EntityPersister; -import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyName; +import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyNames; /** * A value generated by the database might be generated implicitly, by a trigger, or using @@ -116,7 +116,7 @@ public interface InDatabaseGenerator extends Generator { } else { // let's just hope the entity has a @NaturalId! - return new UniqueKeySelectingDelegate( persister, dialect, getUniqueKeyPropertyName( persister ) ); + return new UniqueKeySelectingDelegate( persister, dialect, getUniqueKeyPropertyNames( persister ) ); } } @@ -129,8 +129,8 @@ public interface InDatabaseGenerator extends Generator { * property, if there is one. */ @Incubating - default String getUniqueKeyPropertyName(EntityPersister persister) { - return getNaturalIdPropertyName( persister ); + default String[] getUniqueKeyPropertyNames(EntityPersister persister) { + return getNaturalIdPropertyNames( persister ); } default boolean generatedByDatabase() { diff --git a/hibernate-core/src/main/java/org/hibernate/generator/internal/CurrentTimestampGeneration.java b/hibernate-core/src/main/java/org/hibernate/generator/internal/CurrentTimestampGeneration.java index 86beea67e9..3bc31d64e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/internal/CurrentTimestampGeneration.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/internal/CurrentTimestampGeneration.java @@ -7,7 +7,6 @@ package org.hibernate.generator.internal; import org.hibernate.AssertionFailure; -import org.hibernate.Session; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CurrentTimestamp; import org.hibernate.annotations.SourceType; diff --git a/hibernate-core/src/main/java/org/hibernate/generator/internal/NaturalIdHelper.java b/hibernate-core/src/main/java/org/hibernate/generator/internal/NaturalIdHelper.java index 2564b4ee57..d460d068cc 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/internal/NaturalIdHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/internal/NaturalIdHelper.java @@ -9,27 +9,25 @@ package org.hibernate.generator.internal; import org.hibernate.id.IdentifierGenerationException; import org.hibernate.persister.entity.EntityPersister; +/** + * @author Gavin King + */ public class NaturalIdHelper { - public static String getNaturalIdPropertyName(EntityPersister persister) { - int[] naturalIdPropertyIndices = persister.getNaturalIdentifierProperties(); + public static String[] getNaturalIdPropertyNames(EntityPersister persister) { + final int[] naturalIdPropertyIndices = persister.getNaturalIdentifierProperties(); if ( naturalIdPropertyIndices == null ) { - throw new IdentifierGenerationException( - "no natural-id property defined; " + - "need to specify [key] in generator parameters" - ); - } - if ( naturalIdPropertyIndices.length > 1 ) { - throw new IdentifierGenerationException( - "generator does not currently support composite natural-id properties;" + - " need to specify [key] in generator parameters" - ); + throw new IdentifierGenerationException( "entity '" + persister.getEntityName() + + "' has no '@NaturalId' property" ); } if ( persister.getEntityMetamodel().isNaturalIdentifierInsertGenerated() ) { - throw new IdentifierGenerationException( - "natural-id also defined as insert-generated; " + - "need to specify [key] in generator parameters" - ); + throw new IdentifierGenerationException( "entity '" + persister.getEntityName() + + "' has a '@NaturalId' property which is also defined as insert-generated" ); } - return persister.getPropertyNames()[naturalIdPropertyIndices[0]]; + final String[] allPropertyNames = persister.getPropertyNames(); + final String[] propertyNames = new String[naturalIdPropertyIndices.length]; + for ( int i = 0; i < naturalIdPropertyIndices.length; i++ ) { + propertyNames[i] = allPropertyNames[naturalIdPropertyIndices[i]]; + } + return propertyNames; } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/PostInsertIdentityPersister.java b/hibernate-core/src/main/java/org/hibernate/id/PostInsertIdentityPersister.java index a041465fe6..39c598542a 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/PostInsertIdentityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/id/PostInsertIdentityPersister.java @@ -18,14 +18,30 @@ import org.hibernate.persister.entity.mutation.EntityMutationTarget; public interface PostInsertIdentityPersister extends EntityPersister, EntityMutationTarget { /** * Get a SQL select string that performs a select based on a unique - * key determined by the given property name). + * key determined by the given property name. * * @param propertyName The name of the property which maps to the - * column(s) to use in the select statement restriction. + * column(s) to use in the select statement restriction. * @return The SQL select string */ String getSelectByUniqueKeyString(String propertyName); + /** + * Get a SQL select string that performs a select based on a unique + * key determined by the given property names. + * + * @param propertyNames The names of the properties which maps to the + * column(s) to use in the select statement restriction. + * @return The SQL select string + */ + default String getSelectByUniqueKeyString(String[] propertyNames) { + // default impl only for backward compatibility + if ( propertyNames.length > 1 ) { + throw new IllegalArgumentException( "support for multiple properties not implemented" ); + } + return getSelectByUniqueKeyString( propertyNames[0] ); + } + /** * Get the database-specific SQL command to retrieve the last * generated IDENTITY value. diff --git a/hibernate-core/src/main/java/org/hibernate/id/SelectGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/SelectGenerator.java index c951af80ae..6188fe8ae3 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/SelectGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/SelectGenerator.java @@ -15,7 +15,7 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.Type; -import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyName; +import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyNames; /** * A generator that {@code select}s the just-{@code insert}ed row to determine the @@ -82,10 +82,10 @@ public class SelectGenerator } @Override - public String getUniqueKeyPropertyName(EntityPersister persister) { + public String[] getUniqueKeyPropertyNames(EntityPersister persister) { return uniqueKeyPropertyName != null - ? uniqueKeyPropertyName - : getNaturalIdPropertyName( persister ); + ? new String[] { uniqueKeyPropertyName } + : getNaturalIdPropertyNames( persister ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/id/insert/UniqueKeySelectingDelegate.java b/hibernate-core/src/main/java/org/hibernate/id/insert/UniqueKeySelectingDelegate.java index 90f289016b..6c47e9b03f 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/insert/UniqueKeySelectingDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/id/insert/UniqueKeySelectingDelegate.java @@ -22,25 +22,30 @@ import java.sql.SQLException; /** * Uses a unique key of the inserted entity to locate the newly inserted row. + * + * @author Gavin King */ public class UniqueKeySelectingDelegate extends AbstractSelectingDelegate { private final PostInsertIdentityPersister persister; private final Dialect dialect; - private final String uniqueKeyPropertyName; - private final Type uniqueKeyType; + private final String[] uniqueKeyPropertyNames; + private final Type[] uniqueKeyTypes; private final String idSelectString; - public UniqueKeySelectingDelegate(PostInsertIdentityPersister persister, Dialect dialect, String uniqueKeyPropertyName) { + public UniqueKeySelectingDelegate(PostInsertIdentityPersister persister, Dialect dialect, String[] uniqueKeyPropertyNames) { super( persister ); this.persister = persister; this.dialect = dialect; - this.uniqueKeyPropertyName = uniqueKeyPropertyName; + this.uniqueKeyPropertyNames = uniqueKeyPropertyNames; - idSelectString = persister.getSelectByUniqueKeyString( uniqueKeyPropertyName ); - uniqueKeyType = persister.getPropertyType( uniqueKeyPropertyName ); + idSelectString = persister.getSelectByUniqueKeyString( uniqueKeyPropertyNames ); + uniqueKeyTypes = new Type[ uniqueKeyPropertyNames.length ]; + for (int i = 0; i < uniqueKeyPropertyNames.length; i++ ) { + uniqueKeyTypes[i] = persister.getPropertyType( uniqueKeyPropertyNames[i] ); + } } protected String getSelectSQL() { @@ -62,6 +67,10 @@ public class UniqueKeySelectingDelegate extends AbstractSelectingDelegate { protected void bindParameters(Object entity, PreparedStatement ps, SharedSessionContractImplementor session) throws SQLException { - uniqueKeyType.nullSafeSet( ps, persister.getPropertyValue( entity, uniqueKeyPropertyName ), 1, session ); + int index = 1; + for ( int i = 0; i < uniqueKeyPropertyNames.length; i++ ) { + uniqueKeyTypes[i].nullSafeSet( ps, persister.getPropertyValue( entity, uniqueKeyPropertyNames[i] ), index, session ); + index += uniqueKeyTypes[i].getColumnSpan( session.getFactory() ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 4155cb8d70..ef31c70696 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -2756,11 +2756,19 @@ public abstract class AbstractEntityPersister @Override public String getSelectByUniqueKeyString(String propertyName) { - return new SimpleSelect( getFactory().getJdbcServices().getDialect() ) - .setTableName( getTableName( 0 ) ) - .addColumns( getKeyColumns( 0 ) ) - .addCondition( getPropertyColumnNames( propertyName ), "=?" ) - .toStatementString(); + return getSelectByUniqueKeyString( new String[] { propertyName } ); + } + + @Override + public String getSelectByUniqueKeyString(String[] propertyNames) { + final SimpleSelect select = + new SimpleSelect( getFactory().getJdbcServices().getDialect() ) + .setTableName( getTableName(0) ) + .addColumns( getKeyColumns(0) ); + for ( int i = 0; i < propertyNames.length; i++ ) { + select.addCondition( getPropertyColumnNames( propertyNames[i] ), "= ?" ); + } + return select.toStatementString(); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index 67b7aed668..3acff37b25 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -672,19 +672,21 @@ public class EntityMetamodel implements Serializable { } } + /** + * @return {@code true} if one of the properties belonging to the natural id + * is generated during the execution of an {@code insert} statement + */ public boolean isNaturalIdentifierInsertGenerated() { - // the intention is for this call to replace the usage of the old ValueInclusion stuff (as exposed from - // persister) in SelectGenerator to determine if it is safe to use the natural identifier to find the - // insert-generated identifier. That wont work if the natural-id is also insert-generated. - // - // Assumptions: - // * That code checks that there is a natural identifier before making this call, so we assume the same here - // * That code assumes a non-composite natural-id, so we assume the same here - if ( naturalIdPropertyNumbers.length < 1 ) { + if ( naturalIdPropertyNumbers.length == 0 ) { throw new IllegalStateException( "entity does not have a natural id: " + name ); } - final Generator strategy = generators[ naturalIdPropertyNumbers[0] ]; - return strategy != null && strategy.generatesSometimes(); + for ( int i = 0; i < naturalIdPropertyNumbers.length; i++ ) { + final Generator strategy = generators[ naturalIdPropertyNumbers[i] ]; + if ( strategy != null && strategy.generatesOnInsert() && strategy.generatedOnExecute() ) { + return true; + } + } + return false; } public boolean isVersionGeneratedByDatabase() {