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.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() {

View File

@ -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;

View File

@ -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;
}
}

View File

@ -18,7 +18,7 @@ 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.
@ -26,6 +26,22 @@ public interface PostInsertIdentityPersister extends EntityPersister, EntityMuta
*/
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.

View File

@ -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

View File

@ -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() );
}
}
}

View File

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