HHH-16750 ClassCastException when an Entity with an ElementCollection has an EmbeddableId with just one field and Batch is enabled

This commit is contained in:
Andrea Boriero 2023-06-05 15:37:34 +02:00 committed by Andrea Boriero
parent 254d01484d
commit 37aa5b21a3
7 changed files with 126 additions and 10 deletions

View File

@ -35,7 +35,7 @@ public abstract class AbstractCollectionBatchLoader implements CollectionBatchLo
private final int keyJdbcCount; private final int keyJdbcCount;
private final CollectionLoaderSingleKey singleKeyLoader; final CollectionLoaderSingleKey singleKeyLoader;
public AbstractCollectionBatchLoader( public AbstractCollectionBatchLoader(
int domainBatchSize, int domainBatchSize,
@ -91,10 +91,14 @@ public abstract class AbstractCollectionBatchLoader implements CollectionBatchLo
initializeKeys( key, keys, session ); initializeKeys( key, keys, session );
finishInitializingKeys( keys, session );
final CollectionKey collectionKey = new CollectionKey( getLoadable().getCollectionDescriptor(), key ); final CollectionKey collectionKey = new CollectionKey( getLoadable().getCollectionDescriptor(), key );
return session.getPersistenceContext().getCollection( collectionKey ); return session.getPersistenceContext().getCollection( collectionKey );
} }
abstract void finishInitializingKeys(Object[] key, SharedSessionContractImplementor session);
protected void finishInitializingKey(Object key, SharedSessionContractImplementor session) { protected void finishInitializingKey(Object key, SharedSessionContractImplementor session) {
if ( key == null ) { if ( key == null ) {
return; return;

View File

@ -9,6 +9,8 @@ package org.hibernate.loader.ast.internal;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.CollectionKey;
import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
@ -18,7 +20,6 @@ import org.hibernate.loader.ast.spi.SqlArrayMultiKeyLoader;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor;
import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryOptions;
import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.select.SelectStatement;
@ -32,6 +33,8 @@ import org.hibernate.sql.results.internal.RowTransformerStandardImpl;
import org.hibernate.sql.results.spi.ListResultsConsumer; import org.hibernate.sql.results.spi.ListResultsConsumer;
import org.hibernate.type.BasicType; import org.hibernate.type.BasicType;
import static org.hibernate.loader.ast.internal.MultiKeyLoadHelper.hasSingleId;
import static org.hibernate.loader.ast.internal.MultiKeyLoadHelper.trimIdBatch;
import static org.hibernate.loader.ast.internal.MultiKeyLoadLogging.MULTI_KEY_LOAD_DEBUG_ENABLED; import static org.hibernate.loader.ast.internal.MultiKeyLoadLogging.MULTI_KEY_LOAD_DEBUG_ENABLED;
import static org.hibernate.loader.ast.internal.MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER; import static org.hibernate.loader.ast.internal.MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER;
@ -67,15 +70,12 @@ public class CollectionBatchLoaderArrayParam
final Class<?> keyType = keyDescriptor.getJavaType().getJavaTypeClass(); final Class<?> keyType = keyDescriptor.getJavaType().getJavaTypeClass();
final Class<?> arrayClass = Array.newInstance( keyType, 0 ).getClass(); final Class<?> arrayClass = Array.newInstance( keyType, 0 ).getClass();
// this typecast is always safe because we don't instantiate this class unless the FK is "simple"
final SimpleForeignKeyDescriptor simpleKeyDescriptor = (SimpleForeignKeyDescriptor) keyDescriptor;
final BasicType<?> arrayBasicType = getSessionFactory().getTypeConfiguration() final BasicType<?> arrayBasicType = getSessionFactory().getTypeConfiguration()
.getBasicTypeRegistry() .getBasicTypeRegistry()
.getRegisteredType( arrayClass ); .getRegisteredType( arrayClass );
arrayJdbcMapping = MultiKeyLoadHelper.resolveArrayJdbcMapping( arrayJdbcMapping = MultiKeyLoadHelper.resolveArrayJdbcMapping(
arrayBasicType, arrayBasicType,
simpleKeyDescriptor.getJdbcMapping(), keyDescriptor.getSingleJdbcMapping(),
arrayClass, arrayClass,
getSessionFactory() getSessionFactory()
); );
@ -83,7 +83,7 @@ public class CollectionBatchLoaderArrayParam
jdbcParameter = new JdbcParameterImpl( arrayJdbcMapping ); jdbcParameter = new JdbcParameterImpl( arrayJdbcMapping );
sqlSelect = LoaderSelectBuilder.createSelectBySingleArrayParameter( sqlSelect = LoaderSelectBuilder.createSelectBySingleArrayParameter(
getLoadable(), getLoadable(),
simpleKeyDescriptor.getKeyPart(), keyDescriptor.getKeyPart(),
getInfluencers(), getInfluencers(),
LockOptions.NONE, LockOptions.NONE,
jdbcParameter, jdbcParameter,
@ -97,6 +97,72 @@ public class CollectionBatchLoaderArrayParam
.translate( JdbcParameterBindings.NO_BINDINGS, QueryOptions.NONE ); .translate( JdbcParameterBindings.NO_BINDINGS, QueryOptions.NONE );
} }
@Override
public PersistentCollection<?> load(Object keyBeingLoaded, SharedSessionContractImplementor session) {
final ForeignKeyDescriptor keyDescriptor = getLoadable().getKeyDescriptor();
if ( keyDescriptor.isEmbedded() ) {
assert keyDescriptor.getJdbcTypeCount() == 1;
return loadEmbeddable( keyBeingLoaded, session, keyDescriptor );
}
else {
return super.load( keyBeingLoaded, session );
}
}
private PersistentCollection<?> loadEmbeddable(
Object keyBeingLoaded,
SharedSessionContractImplementor session,
ForeignKeyDescriptor keyDescriptor) {
if ( MULTI_KEY_LOAD_DEBUG_ENABLED ) {
MULTI_KEY_LOAD_LOGGER.debugf(
"Batch fetching collection: %s.%s",
getLoadable().getNavigableRole().getFullPath(), keyBeingLoaded
);
}
final int length = getDomainBatchSize();
final Object[] keysToInitialize = (Object[]) Array.newInstance(
keyDescriptor.getSingleJdbcMapping().getJdbcJavaType().getJavaTypeClass(),
length
);
final Object[] embeddedKeys = (Object[]) Array.newInstance(
keyDescriptor.getJavaType().getJavaTypeClass(),
length
);
session.getPersistenceContextInternal().getBatchFetchQueue()
.collectBatchLoadableCollectionKeys(
length,
(index, key) ->
keyDescriptor.forEachJdbcValue( key, (i, value, jdbcMapping) -> {
keysToInitialize[index] = value;
embeddedKeys[index] = key;
}, session )
,
keyBeingLoaded,
getLoadable()
);
// now trim down the array to the number of keys we found
final Object[] keys = trimIdBatch( length, keysToInitialize );
if ( hasSingleId( keys ) ) {
return singleKeyLoader.load( keyBeingLoaded, session );
}
initializeKeys( keyBeingLoaded, keys, session );
for ( Object initializedKey : embeddedKeys ) {
if ( initializedKey != null ) {
finishInitializingKey( initializedKey, session );
}
}
final CollectionKey collectionKey = new CollectionKey(
getLoadable().getCollectionDescriptor(),
keysToInitialize
);
return session.getPersistenceContext().getCollection( collectionKey );
}
@Override @Override
void initializeKeys(Object key, Object[] keysToInitialize, SharedSessionContractImplementor session) { void initializeKeys(Object key, Object[] keysToInitialize, SharedSessionContractImplementor session) {
if ( MULTI_KEY_LOAD_DEBUG_ENABLED ) { if ( MULTI_KEY_LOAD_DEBUG_ENABLED ) {
@ -134,9 +200,36 @@ public class CollectionBatchLoaderArrayParam
RowTransformerStandardImpl.instance(), RowTransformerStandardImpl.instance(),
ListResultsConsumer.UniqueSemantic.FILTER ListResultsConsumer.UniqueSemantic.FILTER
); );
}
for ( Object initializedKey : keysToInitialize ) { @Override
void finishInitializingKeys(Object[] keys, SharedSessionContractImplementor session) {
for ( Object initializedKey : keys ) {
finishInitializingKey( initializedKey, session ); finishInitializingKey( initializedKey, session );
} }
} }
@Override
Object[] resolveKeysToInitialize(Object keyBeingLoaded, SharedSessionContractImplementor session) {
final ForeignKeyDescriptor keyDescriptor = getLoadable().getKeyDescriptor();
if( keyDescriptor.isEmbedded()){
assert keyDescriptor.getJdbcTypeCount() == 1;
final int length = getDomainBatchSize();
final Object[] keysToInitialize = (Object[]) Array.newInstance( keyDescriptor.getSingleJdbcMapping().getJdbcJavaType().getJavaTypeClass(), length );
session.getPersistenceContextInternal().getBatchFetchQueue()
.collectBatchLoadableCollectionKeys(
length,
(index, key) ->
keyDescriptor.forEachJdbcValue( key, (i, value, jdbcMapping) -> {
keysToInitialize[index] = value;
}, session )
,
keyBeingLoaded,
getLoadable()
);
// now trim down the array to the number of keys we found
return trimIdBatch( length, keysToInitialize );
}
return super.resolveKeysToInitialize( keyBeingLoaded, session );
}
} }

View File

@ -152,4 +152,8 @@ public class CollectionBatchLoaderInPredicate
); );
} }
@Override
void finishInitializingKeys(Object[] key, SharedSessionContractImplementor session) {
// do nothing
}
} }

View File

@ -40,6 +40,7 @@ import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping;
import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.Restrictable; import org.hibernate.metamodel.mapping.Restrictable;
import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.ValuedModelPart;
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor; import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
@ -142,7 +143,7 @@ public class LoaderSelectBuilder {
*/ */
public static SelectStatement createSelectBySingleArrayParameter( public static SelectStatement createSelectBySingleArrayParameter(
Loadable loadable, Loadable loadable,
BasicValuedModelPart restrictedPart, ValuedModelPart restrictedPart,
LoadQueryInfluencers influencers, LoadQueryInfluencers influencers,
LockOptions lockOptions, LockOptions lockOptions,
JdbcParameter jdbcArrayParameter, JdbcParameter jdbcArrayParameter,
@ -202,9 +203,10 @@ public class LoaderSelectBuilder {
QuerySpec rootQuerySpec, QuerySpec rootQuerySpec,
NavigablePath rootNavigablePath, NavigablePath rootNavigablePath,
TableGroup rootTableGroup, TableGroup rootTableGroup,
BasicValuedModelPart restrictedPart, ValuedModelPart restrictedPart,
JdbcParameter jdbcArrayParameter, JdbcParameter jdbcArrayParameter,
LoaderSqlAstCreationState sqlAstCreationState) { LoaderSqlAstCreationState sqlAstCreationState) {
assert restrictedPart.getJdbcTypeCount() == 1;
final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver(); final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver();
final SelectableMapping restrictedPartMapping = restrictedPart.getSelectable( 0 ); final SelectableMapping restrictedPartMapping = restrictedPart.getSelectable( 0 );
final NavigablePath restrictionPath = rootNavigablePath.append( restrictedPart.getNavigableRole().getNavigableName() ); final NavigablePath restrictionPath = rootNavigablePath.append( restrictedPart.getNavigableRole().getNavigableName() );

View File

@ -198,4 +198,7 @@ public interface ForeignKeyDescriptor extends VirtualModelPart, ValuedModelPart
ValuedModelPart getModelPart(); ValuedModelPart getModelPart();
} }
boolean isEmbedded();
} }

View File

@ -670,4 +670,9 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor {
public boolean hasPartitionedSelectionMapping() { public boolean hasPartitionedSelectionMapping() {
return keySide.getModelPart().hasPartitionedSelectionMapping(); return keySide.getModelPart().hasPartitionedSelectionMapping();
} }
@Override
public boolean isEmbedded() {
return true;
}
} }

View File

@ -719,4 +719,9 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa
targetSide.getModelPart().getSelectionExpression() targetSide.getModelPart().getSelectionExpression()
); );
} }
@Override
public boolean isEmbedded() {
return false;
}
} }