remove ancient limitation that allowed only one property as UK for 'select' id generator

This commit is contained in:
Gavin 2022-12-20 01:43:38 +01:00 committed by Gavin King
parent d30bf092aa
commit f3e31fe427
8 changed files with 82 additions and 50 deletions

View File

@ -15,7 +15,7 @@ import org.hibernate.id.insert.InsertReturningDelegate;
import org.hibernate.id.insert.UniqueKeySelectingDelegate; import org.hibernate.id.insert.UniqueKeySelectingDelegate;
import org.hibernate.persister.entity.EntityPersister; 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 * 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 { else {
// let's just hope the entity has a @NaturalId! // 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. * property, if there is one.
*/ */
@Incubating @Incubating
default String getUniqueKeyPropertyName(EntityPersister persister) { default String[] getUniqueKeyPropertyNames(EntityPersister persister) {
return getNaturalIdPropertyName( persister ); return getNaturalIdPropertyNames( persister );
} }
default boolean generatedByDatabase() { default boolean generatedByDatabase() {

View File

@ -7,7 +7,6 @@
package org.hibernate.generator.internal; package org.hibernate.generator.internal;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.hibernate.Session;
import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.CurrentTimestamp; import org.hibernate.annotations.CurrentTimestamp;
import org.hibernate.annotations.SourceType; import org.hibernate.annotations.SourceType;

View File

@ -9,27 +9,25 @@ package org.hibernate.generator.internal;
import org.hibernate.id.IdentifierGenerationException; import org.hibernate.id.IdentifierGenerationException;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
/**
* @author Gavin King
*/
public class NaturalIdHelper { public class NaturalIdHelper {
public static String getNaturalIdPropertyName(EntityPersister persister) { public static String[] getNaturalIdPropertyNames(EntityPersister persister) {
int[] naturalIdPropertyIndices = persister.getNaturalIdentifierProperties(); final int[] naturalIdPropertyIndices = persister.getNaturalIdentifierProperties();
if ( naturalIdPropertyIndices == null ) { if ( naturalIdPropertyIndices == null ) {
throw new IdentifierGenerationException( throw new IdentifierGenerationException( "entity '" + persister.getEntityName()
"no natural-id property defined; " + + "' has no '@NaturalId' property" );
"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"
);
} }
if ( persister.getEntityMetamodel().isNaturalIdentifierInsertGenerated() ) { if ( persister.getEntityMetamodel().isNaturalIdentifierInsertGenerated() ) {
throw new IdentifierGenerationException( throw new IdentifierGenerationException( "entity '" + persister.getEntityName()
"natural-id also defined as insert-generated; " + + "' has a '@NaturalId' property which is also defined as insert-generated" );
"need to specify [key] in generator parameters"
);
} }
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;
} }
} }

View File

@ -18,14 +18,30 @@ import org.hibernate.persister.entity.mutation.EntityMutationTarget;
public interface PostInsertIdentityPersister extends EntityPersister, EntityMutationTarget { public interface PostInsertIdentityPersister extends EntityPersister, EntityMutationTarget {
/** /**
* Get a SQL select string that performs a select based on a unique * 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 * @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 * @return The SQL select string
*/ */
String getSelectByUniqueKeyString(String propertyName); 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 * Get the database-specific SQL command to retrieve the last
* generated IDENTITY value. * generated IDENTITY value.

View File

@ -15,7 +15,7 @@ import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.Type; 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 * A generator that {@code select}s the just-{@code insert}ed row to determine the
@ -82,10 +82,10 @@ public class SelectGenerator
} }
@Override @Override
public String getUniqueKeyPropertyName(EntityPersister persister) { public String[] getUniqueKeyPropertyNames(EntityPersister persister) {
return uniqueKeyPropertyName != null return uniqueKeyPropertyName != null
? uniqueKeyPropertyName ? new String[] { uniqueKeyPropertyName }
: getNaturalIdPropertyName( persister ); : getNaturalIdPropertyNames( persister );
} }
@Override @Override

View File

@ -22,25 +22,30 @@ import java.sql.SQLException;
/** /**
* Uses a unique key of the inserted entity to locate the newly inserted row. * Uses a unique key of the inserted entity to locate the newly inserted row.
*
* @author Gavin King
*/ */
public class UniqueKeySelectingDelegate extends AbstractSelectingDelegate { public class UniqueKeySelectingDelegate extends AbstractSelectingDelegate {
private final PostInsertIdentityPersister persister; private final PostInsertIdentityPersister persister;
private final Dialect dialect; private final Dialect dialect;
private final String uniqueKeyPropertyName; private final String[] uniqueKeyPropertyNames;
private final Type uniqueKeyType; private final Type[] uniqueKeyTypes;
private final String idSelectString; private final String idSelectString;
public UniqueKeySelectingDelegate(PostInsertIdentityPersister persister, Dialect dialect, String uniqueKeyPropertyName) { public UniqueKeySelectingDelegate(PostInsertIdentityPersister persister, Dialect dialect, String[] uniqueKeyPropertyNames) {
super( persister ); super( persister );
this.persister = persister; this.persister = persister;
this.dialect = dialect; this.dialect = dialect;
this.uniqueKeyPropertyName = uniqueKeyPropertyName; this.uniqueKeyPropertyNames = uniqueKeyPropertyNames;
idSelectString = persister.getSelectByUniqueKeyString( uniqueKeyPropertyName ); idSelectString = persister.getSelectByUniqueKeyString( uniqueKeyPropertyNames );
uniqueKeyType = persister.getPropertyType( uniqueKeyPropertyName ); uniqueKeyTypes = new Type[ uniqueKeyPropertyNames.length ];
for (int i = 0; i < uniqueKeyPropertyNames.length; i++ ) {
uniqueKeyTypes[i] = persister.getPropertyType( uniqueKeyPropertyNames[i] );
}
} }
protected String getSelectSQL() { protected String getSelectSQL() {
@ -62,6 +67,10 @@ public class UniqueKeySelectingDelegate extends AbstractSelectingDelegate {
protected void bindParameters(Object entity, PreparedStatement ps, SharedSessionContractImplementor session) protected void bindParameters(Object entity, PreparedStatement ps, SharedSessionContractImplementor session)
throws SQLException { 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() );
}
} }
} }

View File

@ -2756,11 +2756,19 @@ public abstract class AbstractEntityPersister
@Override @Override
public String getSelectByUniqueKeyString(String propertyName) { public String getSelectByUniqueKeyString(String propertyName) {
return new SimpleSelect( getFactory().getJdbcServices().getDialect() ) return getSelectByUniqueKeyString( new String[] { propertyName } );
.setTableName( getTableName( 0 ) ) }
.addColumns( getKeyColumns( 0 ) )
.addCondition( getPropertyColumnNames( propertyName ), "=?" ) @Override
.toStatementString(); 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();
} }
/** /**

View File

@ -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() { public boolean isNaturalIdentifierInsertGenerated() {
// the intention is for this call to replace the usage of the old ValueInclusion stuff (as exposed from if ( naturalIdPropertyNumbers.length == 0 ) {
// 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 ) {
throw new IllegalStateException( "entity does not have a natural id: " + name ); throw new IllegalStateException( "entity does not have a natural id: " + name );
} }
final Generator strategy = generators[ naturalIdPropertyNumbers[0] ]; for ( int i = 0; i < naturalIdPropertyNumbers.length; i++ ) {
return strategy != null && strategy.generatesSometimes(); final Generator strategy = generators[ naturalIdPropertyNumbers[i] ];
if ( strategy != null && strategy.generatesOnInsert() && strategy.generatedOnExecute() ) {
return true;
}
}
return false;
} }
public boolean isVersionGeneratedByDatabase() { public boolean isVersionGeneratedByDatabase() {