more work on multi-id entity loading and key-based loading in general

This commit is contained in:
Steve Ebersole 2019-11-25 15:58:50 -06:00
parent 1b7f60c348
commit 0c6c8b4406
21 changed files with 919 additions and 642 deletions

View File

@ -21,7 +21,7 @@ import org.hibernate.UnknownProfileException;
import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.internal.FilterImpl; import org.hibernate.internal.FilterImpl;
import org.hibernate.loader.spi.InternalFetchProfile; import org.hibernate.loader.spi.CascadingFetchProfile;
import org.hibernate.type.Type; import org.hibernate.type.Type;
/** /**
@ -44,7 +44,7 @@ public class LoadQueryInfluencers implements Serializable {
private final SessionFactoryImplementor sessionFactory; private final SessionFactoryImplementor sessionFactory;
private InternalFetchProfile enabledInternalFetchProfile; private CascadingFetchProfile enabledCascadingFetchProfile;
//Lazily initialized! //Lazily initialized!
private HashSet<String> enabledFetchProfileNames; private HashSet<String> enabledFetchProfileNames;
@ -69,21 +69,21 @@ public class LoadQueryInfluencers implements Serializable {
// internal fetch profile support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // internal fetch profile support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public void withInternalFetchProfile(InternalFetchProfile profile, InternalFetchProfileAction action) { public void withInternalFetchProfile(CascadingFetchProfile profile, InternalFetchProfileAction action) {
final InternalFetchProfile previous = this.enabledInternalFetchProfile; final CascadingFetchProfile previous = this.enabledCascadingFetchProfile;
this.enabledInternalFetchProfile = profile; this.enabledCascadingFetchProfile = profile;
action.performAction(); action.performAction();
this.enabledInternalFetchProfile = previous; this.enabledCascadingFetchProfile = previous;
} }
public <T> T fromInternalFetchProfile(InternalFetchProfile profile, Supplier<T> supplier) { public <T> T fromInternalFetchProfile(CascadingFetchProfile profile, Supplier<T> supplier) {
final InternalFetchProfile previous = this.enabledInternalFetchProfile; final CascadingFetchProfile previous = this.enabledCascadingFetchProfile;
this.enabledInternalFetchProfile = profile; this.enabledCascadingFetchProfile = profile;
try { try {
return supplier.get(); return supplier.get();
} }
finally { finally {
this.enabledInternalFetchProfile = previous; this.enabledCascadingFetchProfile = previous;
} }
} }
@ -92,34 +92,34 @@ public class LoadQueryInfluencers implements Serializable {
void performAction(); void performAction();
} }
public InternalFetchProfile getEnabledInternalFetchProfile() { public CascadingFetchProfile getEnabledCascadingFetchProfile() {
return enabledInternalFetchProfile; return enabledCascadingFetchProfile;
} }
public void setEnabledInternalFetchProfile(InternalFetchProfile enabledInternalFetchProfile) { public void setEnabledCascadingFetchProfile(CascadingFetchProfile enabledCascadingFetchProfile) {
if ( sessionFactory == null ) { if ( sessionFactory == null ) {
// thats the signal that this is the immutable, context-less // thats the signal that this is the immutable, context-less
// variety // variety
throw new IllegalStateException( "Cannot modify context-less LoadQueryInfluencers" ); throw new IllegalStateException( "Cannot modify context-less LoadQueryInfluencers" );
} }
this.enabledInternalFetchProfile = enabledInternalFetchProfile; this.enabledCascadingFetchProfile = enabledCascadingFetchProfile;
} }
/** /**
* @deprecated Use {@link #getEnabledInternalFetchProfile} instead * @deprecated Use {@link #getEnabledCascadingFetchProfile} instead
*/ */
@Deprecated @Deprecated
public String getInternalFetchProfile() { public String getInternalFetchProfile() {
return getEnabledInternalFetchProfile().getLegacyName(); return getEnabledCascadingFetchProfile().getLegacyName();
} }
/** /**
* @deprecated Use {@link #setEnabledInternalFetchProfile} instead * @deprecated Use {@link #setEnabledCascadingFetchProfile} instead
*/ */
@Deprecated @Deprecated
public void setInternalFetchProfile(String internalFetchProfile) { public void setInternalFetchProfile(String internalFetchProfile) {
setEnabledInternalFetchProfile( InternalFetchProfile.fromLegacyName( internalFetchProfile ) ); setEnabledCascadingFetchProfile( CascadingFetchProfile.fromLegacyName( internalFetchProfile ) );
} }

View File

@ -34,7 +34,7 @@ import org.hibernate.event.spi.MergeEvent;
import org.hibernate.event.spi.MergeEventListener; import org.hibernate.event.spi.MergeEventListener;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.loader.spi.InternalFetchProfile; import org.hibernate.loader.spi.CascadingFetchProfile;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer; import org.hibernate.proxy.LazyInitializer;
@ -303,7 +303,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme
// apply the special MERGE fetch profile and perform the resolution (Session#get) // apply the special MERGE fetch profile and perform the resolution (Session#get)
final Object result = source.getLoadQueryInfluencers().fromInternalFetchProfile( final Object result = source.getLoadQueryInfluencers().fromInternalFetchProfile(
InternalFetchProfile.MERGE, CascadingFetchProfile.MERGE,
() -> source.get( entityName, clonedIdentifier ) () -> source.get( entityName, clonedIdentifier )
); );

View File

@ -30,7 +30,7 @@ import org.hibernate.event.spi.RefreshEvent;
import org.hibernate.event.spi.RefreshEventListener; import org.hibernate.event.spi.RefreshEventListener;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.loader.spi.InternalFetchProfile; import org.hibernate.loader.spi.CascadingFetchProfile;
import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.metamodel.spi.MetamodelImplementor;
import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
@ -170,7 +170,7 @@ public class DefaultRefreshEventListener implements RefreshEventListener {
evictCachedCollections( persister, id, source ); evictCachedCollections( persister, id, source );
final Object result = source.getLoadQueryInfluencers().fromInternalFetchProfile( final Object result = source.getLoadQueryInfluencers().fromInternalFetchProfile(
InternalFetchProfile.REFRESH, CascadingFetchProfile.REFRESH,
() -> doRefresh( event, source, object, e, persister, id, persistenceContext ) () -> doRefresh( event, source, object, e, persister, id, persistenceContext )
); );

View File

@ -0,0 +1,231 @@
/*
* 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.loader.internal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.hibernate.LockOptions;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.CollectionKey;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.spi.CollectionLoader;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
import org.hibernate.sql.exec.spi.Callback;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcParameter;
import org.hibernate.sql.exec.spi.JdbcParameterBinding;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcSelect;
import org.hibernate.sql.results.internal.RowTransformerPassThruImpl;
import org.jboss.logging.Logger;
/**
* @author Steve Ebersole
*/
public class BatchCollectionKeyLoader implements CollectionLoader {
private static final Logger log = Logger.getLogger( BatchCollectionKeyLoader.class );
private final PluralAttributeMapping attributeMapping;
private final int batchSize;
private final int keyJdbcCount;
private SelectStatement batchSizeSqlAst;
private List<JdbcParameter> batchSizeJdbcParameters;
public BatchCollectionKeyLoader(
PluralAttributeMapping attributeMapping,
int batchSize,
LoadQueryInfluencers influencers,
SessionFactoryImplementor sessionFactory) {
this.attributeMapping = attributeMapping;
this.batchSize = batchSize;
this.keyJdbcCount = attributeMapping.getKeyDescriptor().getJdbcTypeCount( sessionFactory.getTypeConfiguration() );
this.batchSizeJdbcParameters = new ArrayList<>();
this.batchSizeSqlAst = MetamodelSelectBuilderProcess.createSelect(
attributeMapping,
null,
attributeMapping.getKeyDescriptor(),
null,
batchSize,
influencers,
LockOptions.READ,
batchSizeJdbcParameters::add,
sessionFactory
);
}
@Override
public PluralAttributeMapping getLoadable() {
return attributeMapping;
}
@Override
public PersistentCollection load(
Object key,
SharedSessionContractImplementor session) {
final Object[] batchIds = session.getPersistenceContextInternal()
.getBatchFetchQueue()
.getCollectionBatch( getLoadable().getCollectionDescriptor(), key, batchSize );
final int numberOfIds = ArrayHelper.countNonNull( batchIds );
if ( numberOfIds == 1 ) {
final List<JdbcParameter> jdbcParameters = new ArrayList<>( keyJdbcCount );
final SelectStatement sqlAst = MetamodelSelectBuilderProcess.createSelect(
attributeMapping,
null,
attributeMapping.getKeyDescriptor(),
null,
batchSize,
session.getLoadQueryInfluencers(),
LockOptions.READ,
jdbcParameters::add,
session.getFactory()
);
new SingleIdLoadPlan( attributeMapping.getKeyDescriptor(), sqlAst, jdbcParameters ).load( key, LockOptions.READ, session );
}
else {
batchLoad( batchIds, session );
}
final CollectionKey collectionKey = new CollectionKey( attributeMapping.getCollectionDescriptor(), key );
return session.getPersistenceContext().getCollection( collectionKey );
}
private void batchLoad(
Object[] batchIds,
SharedSessionContractImplementor session) {
if ( log.isDebugEnabled() ) {
log.debugf(
"Batch loading collection [%s] : %s",
getLoadable().getCollectionDescriptor().getRole(),
batchIds
);
}
int smallBatchStart = 0;
int smallBatchLength = Math.min( batchIds.length, batchSize );
while ( true ) {
final List<JdbcParameter> jdbcParameters;
final SelectStatement sqlAst;
if ( smallBatchLength == batchSize ) {
jdbcParameters = this.batchSizeJdbcParameters;
sqlAst = this.batchSizeSqlAst;
}
else {
jdbcParameters = new ArrayList<>();
sqlAst = MetamodelSelectBuilderProcess.createSelect(
getLoadable(),
// null here means to select everything
null,
getLoadable().getKeyDescriptor(),
null,
batchIds.length,
session.getLoadQueryInfluencers(),
LockOptions.READ,
jdbcParameters::add,
session.getFactory()
);
}
final SessionFactoryImplementor sessionFactory = session.getFactory();
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate(
sqlAst );
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( keyJdbcCount * smallBatchLength );
final Iterator<JdbcParameter> paramItr = jdbcParameters.iterator();
for ( int i = smallBatchStart; i < smallBatchStart + smallBatchLength; i++ ) {
getLoadable().getKeyDescriptor().visitJdbcValues(
batchIds[i],
Clause.WHERE,
(value, type) -> {
assert paramItr.hasNext();
final JdbcParameter parameter = paramItr.next();
jdbcParameterBindings.addBinding(
parameter,
new JdbcParameterBinding() {
@Override
public JdbcMapping getBindType() {
return type;
}
@Override
public Object getBindValue() {
return value;
}
}
);
},
session
);
jdbcServices.getJdbcSelectExecutor().list(
jdbcSelect,
jdbcParameterBindings,
new ExecutionContext() {
@Override
public SharedSessionContractImplementor getSession() {
return session;
}
@Override
public QueryOptions getQueryOptions() {
return QueryOptions.NONE;
}
@Override
public QueryParameterBindings getQueryParameterBindings() {
return QueryParameterBindings.NO_PARAM_BINDINGS;
}
@Override
public Callback getCallback() {
return null;
}
},
RowTransformerPassThruImpl.instance()
);
}
assert !paramItr.hasNext();
// prepare for the next round...
smallBatchStart += smallBatchLength;
if ( smallBatchStart >= batchIds.length ) {
break;
}
smallBatchLength = Math.min( batchIds.length - smallBatchStart, batchSize );
}
}
}

View File

@ -6,13 +6,16 @@
*/ */
package org.hibernate.loader.internal; package org.hibernate.loader.internal;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.hibernate.LockOptions;
import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.CollectionKey; import org.hibernate.engine.spi.CollectionKey;
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;
import org.hibernate.loader.spi.CollectionLoader; import org.hibernate.loader.spi.CollectionLoader;
@ -24,7 +27,6 @@ import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
import org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl;
import org.hibernate.sql.exec.spi.Callback; import org.hibernate.sql.exec.spi.Callback;
import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcParameter; import org.hibernate.sql.exec.spi.JdbcParameter;
@ -34,29 +36,48 @@ import org.hibernate.sql.exec.spi.JdbcSelect;
import org.hibernate.sql.results.internal.RowTransformerPassThruImpl; import org.hibernate.sql.results.internal.RowTransformerPassThruImpl;
/** /**
* Main implementation of CollectionLoader for handling a load of a single collection-key
*
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class SingleCollectionKeyLoader implements CollectionLoader { public class SingleCollectionKeyLoader implements CollectionLoader {
private final PluralAttributeMapping pluralAttributeMapping; private final PluralAttributeMapping attributeMapping;
private final int keyJdbcCount;
private final SelectStatement sqlAst; private final SelectStatement sqlAst;
private final List<JdbcParameter> jdbcParameters; private final List<JdbcParameter> jdbcParameters;
public SingleCollectionKeyLoader( public SingleCollectionKeyLoader(
PluralAttributeMapping pluralAttributeMapping, PluralAttributeMapping attributeMapping,
SelectStatement sqlAst, List<JdbcParameter> jdbcParameters) { LoadQueryInfluencers influencers,
this.pluralAttributeMapping = pluralAttributeMapping; SessionFactoryImplementor sessionFactory) {
this.sqlAst = sqlAst; this.attributeMapping = attributeMapping;
this.jdbcParameters = jdbcParameters;
this.keyJdbcCount = attributeMapping.getKeyDescriptor().getJdbcTypeCount( sessionFactory.getTypeConfiguration() );
this.jdbcParameters = new ArrayList<>();
this.sqlAst = MetamodelSelectBuilderProcess.createSelect(
attributeMapping,
null,
attributeMapping.getKeyDescriptor(),
null,
1,
influencers,
LockOptions.READ,
jdbcParameters::add,
sessionFactory
);
} }
@Override @Override
public PluralAttributeMapping getLoadable() { public PluralAttributeMapping getLoadable() {
return pluralAttributeMapping; return attributeMapping;
} }
@Override @Override
public PersistentCollection load(Object key, SharedSessionContractImplementor session) { public PersistentCollection load(Object key, SharedSessionContractImplementor session) {
final CollectionKey collectionKey = new CollectionKey( pluralAttributeMapping.getCollectionDescriptor(), key ); final CollectionKey collectionKey = new CollectionKey( attributeMapping.getCollectionDescriptor(), key );
final SessionFactoryImplementor sessionFactory = session.getFactory(); final SessionFactoryImplementor sessionFactory = session.getFactory();
final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
@ -65,13 +86,11 @@ public class SingleCollectionKeyLoader implements CollectionLoader {
final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst ); final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst );
final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( keyJdbcCount );
pluralAttributeMapping.getKeyDescriptor().getJdbcTypeCount( sessionFactory.getTypeConfiguration() )
);
final Iterator<JdbcParameter> paramItr = jdbcParameters.iterator(); final Iterator<JdbcParameter> paramItr = jdbcParameters.iterator();
pluralAttributeMapping.getKeyDescriptor().visitJdbcValues( attributeMapping.getKeyDescriptor().visitJdbcValues(
key, key,
Clause.WHERE, Clause.WHERE,
(value, type) -> { (value, type) -> {
@ -96,7 +115,7 @@ public class SingleCollectionKeyLoader implements CollectionLoader {
); );
assert !paramItr.hasNext(); assert !paramItr.hasNext();
JdbcSelectExecutorStandardImpl.INSTANCE.list( jdbcServices.getJdbcSelectExecutor().list(
jdbcSelect, jdbcSelect,
jdbcParameterBindings, jdbcParameterBindings,
new ExecutionContext() { new ExecutionContext() {

View File

@ -1,34 +0,0 @@
/*
* 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.loader.internal;
import org.hibernate.LockOptions;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.BatchFetchStyle;
import org.hibernate.metamodel.mapping.EntityMappingType;
/**
* @author Steve Ebersole
*/
public class SingleIdEntityLoaderLegacyBatch<T> extends SingleIdEntityLoaderSupport<T> {
private final int batchSize;
public SingleIdEntityLoaderLegacyBatch(
EntityMappingType entityDescriptor,
int batchSize,
SessionFactoryImplementor sessionFactory) {
super( entityDescriptor, sessionFactory );
this.batchSize = batchSize;
}
@Override
public T load(Object pkValue, LockOptions lockOptions, SharedSessionContractImplementor session) {
throw new NotYetImplementedFor6Exception( "Support for " + BatchFetchStyle.LEGACY + " not yet implemented" );
}
}

View File

@ -1,39 +0,0 @@
/*
* 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.loader.internal;
import org.hibernate.LockOptions;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.BatchFetchStyle;
import org.hibernate.metamodel.mapping.EntityMappingType;
/**
* @author Steve Ebersole
*/
public class SingleIdEntityLoaderPaddedBatch<T> extends SingleIdEntityLoaderSupport<T> implements Preparable {
private final int batchSize;
public SingleIdEntityLoaderPaddedBatch(
EntityMappingType entityDescriptor,
int batchSize,
SessionFactoryImplementor sessionFactory) {
super( entityDescriptor, sessionFactory );
this.batchSize = batchSize;
}
@Override
public void prepare() {
}
@Override
public T load(Object pkValue, LockOptions lockOptions, SharedSessionContractImplementor session) {
throw new NotYetImplementedFor6Exception( "Support for " + BatchFetchStyle.PADDED + " not yet implemented" );
}
}

View File

@ -16,7 +16,7 @@ import org.hibernate.LockOptions;
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;
import org.hibernate.loader.spi.InternalFetchProfile; import org.hibernate.loader.spi.CascadingFetchProfile;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.spi.JdbcParameter; import org.hibernate.sql.exec.spi.JdbcParameter;
@ -28,7 +28,7 @@ import org.hibernate.sql.exec.spi.JdbcParameter;
*/ */
public class SingleIdEntityLoaderStandardImpl<T> extends SingleIdEntityLoaderSupport<T> implements Preparable { public class SingleIdEntityLoaderStandardImpl<T> extends SingleIdEntityLoaderSupport<T> implements Preparable {
private EnumMap<LockMode, SingleIdLoadPlan> selectByLockMode = new EnumMap<>( LockMode.class ); private EnumMap<LockMode, SingleIdLoadPlan> selectByLockMode = new EnumMap<>( LockMode.class );
private EnumMap<InternalFetchProfile, SingleIdLoadPlan> selectByInternalCascadeProfile; private EnumMap<CascadingFetchProfile, SingleIdLoadPlan> selectByInternalCascadeProfile;
private AtomicInteger nonReusablePlansGenerated = new AtomicInteger(); private AtomicInteger nonReusablePlansGenerated = new AtomicInteger();
@ -71,14 +71,14 @@ public class SingleIdEntityLoaderStandardImpl<T> extends SingleIdEntityLoaderSup
return createLoadPlan( lockOptions, loadQueryInfluencers, session.getFactory() ); return createLoadPlan( lockOptions, loadQueryInfluencers, session.getFactory() );
} }
final InternalFetchProfile enabledInternalFetchProfile = loadQueryInfluencers.getEnabledInternalFetchProfile(); final CascadingFetchProfile enabledCascadingFetchProfile = loadQueryInfluencers.getEnabledCascadingFetchProfile();
if ( enabledInternalFetchProfile != null ) { if ( enabledCascadingFetchProfile != null ) {
if ( LockMode.UPGRADE.greaterThan( lockOptions.getLockMode() ) ) { if ( LockMode.UPGRADE.greaterThan( lockOptions.getLockMode() ) ) {
if ( selectByInternalCascadeProfile == null ) { if ( selectByInternalCascadeProfile == null ) {
selectByInternalCascadeProfile = new EnumMap<>( InternalFetchProfile.class ); selectByInternalCascadeProfile = new EnumMap<>( CascadingFetchProfile.class );
} }
else { else {
final SingleIdLoadPlan existing = selectByInternalCascadeProfile.get( enabledInternalFetchProfile ); final SingleIdLoadPlan existing = selectByInternalCascadeProfile.get( enabledCascadingFetchProfile );
if ( existing != null ) { if ( existing != null ) {
//noinspection unchecked //noinspection unchecked
return existing; return existing;
@ -90,7 +90,7 @@ public class SingleIdEntityLoaderStandardImpl<T> extends SingleIdEntityLoaderSup
loadQueryInfluencers, loadQueryInfluencers,
session.getFactory() session.getFactory()
); );
selectByInternalCascadeProfile.put( enabledInternalFetchProfile, plan ); selectByInternalCascadeProfile.put( enabledCascadingFetchProfile, plan );
return plan; return plan;
} }
} }

View File

@ -11,13 +11,13 @@ import org.hibernate.internal.util.StringHelper;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public enum InternalFetchProfile { public enum CascadingFetchProfile {
MERGE( "merge" ), MERGE( "merge" ),
REFRESH( "refresh" ); REFRESH( "refresh" );
private final String legacyName; private final String legacyName;
InternalFetchProfile(String legacyName) { CascadingFetchProfile(String legacyName) {
this.legacyName = legacyName; this.legacyName = legacyName;
} }
@ -25,7 +25,7 @@ public enum InternalFetchProfile {
return legacyName; return legacyName;
} }
public static InternalFetchProfile fromLegacyName(String legacyName) { public static CascadingFetchProfile fromLegacyName(String legacyName) {
if ( StringHelper.isEmpty( legacyName ) ) { if ( StringHelper.isEmpty( legacyName ) ) {
return null; return null;
} }

View File

@ -0,0 +1,29 @@
/*
* 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.metamodel.mapping;
import org.hibernate.sql.results.spi.Fetchable;
/**
* Commonality between `many-to-one`, `one-to-one` and `any`, as well as entity-valued collection elements and map-keys
*
* @author Steve Ebersole
*/
public interface EntityAssociationMapping extends ModelPart, Fetchable {
@Override
default String getFetchableName() {
return getPartName();
}
EntityMappingType getAssociatedEntityMappingType();
/**
* The model sub-part relative to the associated entity type that is the target
* of this association's foreign-key
*/
ModelPart getKeyTargetMatchPart();
}

View File

@ -11,8 +11,10 @@ import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming; import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.query.NavigablePath; import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.internal.domain.collection.EntityCollectionPartTableGroup; import org.hibernate.sql.results.internal.domain.collection.EntityCollectionPartTableGroup;
@ -26,14 +28,30 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class EntityCollectionPart implements CollectionPart, EntityValuedModelPart { public class EntityCollectionPart implements CollectionPart, EntityAssociationMapping, EntityValuedModelPart {
private final Nature nature; private final Nature nature;
private final EntityMappingType entityMappingType; private final EntityMappingType entityMappingType;
private ModelPart fkTargetModelPart;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public EntityCollectionPart(Nature nature, EntityMappingType entityMappingType) { public EntityCollectionPart(
Nature nature,
EntityMappingType entityMappingType,
String fkTargetModelPartName,
MappingModelCreationProcess creationProcess) {
this.nature = nature; this.nature = nature;
this.entityMappingType = entityMappingType; this.entityMappingType = entityMappingType;
creationProcess.registerInitializationCallback(
() -> {
fkTargetModelPart = fkTargetModelPartName == null
? entityMappingType.getIdentifierMapping()
: entityMappingType.findSubPart( fkTargetModelPartName, entityMappingType );
assert fkTargetModelPart != null;
return true;
}
);
} }
@Override @Override
@ -51,6 +69,16 @@ public class EntityCollectionPart implements CollectionPart, EntityValuedModelPa
return entityMappingType; return entityMappingType;
} }
@Override
public EntityMappingType getAssociatedEntityMappingType() {
return getEntityMappingType();
}
@Override
public ModelPart getKeyTargetMatchPart() {
return fkTargetModelPart;
}
@Override @Override
public JavaTypeDescriptor getJavaTypeDescriptor() { public JavaTypeDescriptor getJavaTypeDescriptor() {
return getEntityMappingType().getJavaTypeDescriptor(); return getEntityMappingType().getJavaTypeDescriptor();

View File

@ -1098,7 +1098,14 @@ public class MappingModelCreationHelper {
); );
} }
return new EntityCollectionPart( CollectionPart.Nature.ELEMENT, associatedEntity ); final EntityType indexEntityType = (EntityType) collectionDescriptor.getIndexType();
return new EntityCollectionPart(
CollectionPart.Nature.ELEMENT,
associatedEntity,
indexEntityType.getRHSUniqueKeyPropertyName(),
creationProcess
);
} }
throw new NotYetImplementedFor6Exception( throw new NotYetImplementedFor6Exception(
@ -1171,7 +1178,14 @@ public class MappingModelCreationHelper {
); );
} }
return new EntityCollectionPart( CollectionPart.Nature.ELEMENT, associatedEntity ); final EntityType indexEntityType = (EntityType) collectionDescriptor.getElementType();
return new EntityCollectionPart(
CollectionPart.Nature.ELEMENT,
associatedEntity,
indexEntityType.getRHSUniqueKeyPropertyName(),
creationProcess
);
} }
throw new NotYetImplementedFor6Exception( throw new NotYetImplementedFor6Exception(

View File

@ -136,9 +136,18 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme
return baseIndex; return baseIndex;
} }
}; };
if ( collectionDescriptor instanceof Aware ) { if ( collectionDescriptor instanceof Aware ) {
( (Aware) collectionDescriptor ).injectAttributeMapping( this ); ( (Aware) collectionDescriptor ).injectAttributeMapping( this );
} }
if ( elementDescriptor instanceof Aware ) {
( (Aware) elementDescriptor ).injectAttributeMapping( this );
}
if ( indexDescriptor instanceof Aware ) {
( (Aware) indexDescriptor ).injectAttributeMapping( this );
}
} }
@Override @Override

View File

@ -10,10 +10,12 @@ import org.hibernate.LockMode;
import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming; import org.hibernate.engine.FetchTiming;
import org.hibernate.mapping.ToOne; import org.hibernate.mapping.ToOne;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess; import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
@ -40,12 +42,13 @@ import org.hibernate.sql.results.spi.DomainResult;
import org.hibernate.sql.results.spi.DomainResultCreationState; import org.hibernate.sql.results.spi.DomainResultCreationState;
import org.hibernate.sql.results.spi.Fetch; import org.hibernate.sql.results.spi.Fetch;
import org.hibernate.sql.results.spi.FetchParent; import org.hibernate.sql.results.spi.FetchParent;
import org.hibernate.sql.results.spi.Fetchable;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class SingularAssociationAttributeMapping extends AbstractSingularAttributeMapping public class SingularAssociationAttributeMapping extends AbstractSingularAttributeMapping
implements EntityValuedModelPart, TableGroupJoinProducer { implements EntityValuedModelPart, EntityAssociationMapping, TableGroupJoinProducer {
private final String sqlAliasStem; private final String sqlAliasStem;
private final boolean isNullable; private final boolean isNullable;
private final boolean referringPrimaryKey; private final boolean referringPrimaryKey;
@ -278,14 +281,17 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
.getFromClauseAccess() .getFromClauseAccess()
.findTableGroup( parentParentNavigablePath ) .findTableGroup( parentParentNavigablePath )
.getModelPart(); .getModelPart();
final SingularAssociationAttributeMapping part = (SingularAssociationAttributeMapping) modelPart // final SingularAssociationAttributeMapping part = (SingularAssociationAttributeMapping) modelPart
.findSubPart( panentNaviblePath.getLocalName(), null ); // .findSubPart( panentNaviblePath.getLocalName(), null );
final EntityAssociationMapping part = (EntityAssociationMapping) modelPart.findSubPart( panentNaviblePath.getLocalName(), null );
if ( panentNaviblePath.getLocalName().equals( referencedPropertyName ) if ( panentNaviblePath.getLocalName().equals( referencedPropertyName )
&& part.getFetchableName().equals( referencedPropertyName ) ) { && part.getFetchableName().equals( referencedPropertyName ) ) {
return true; return true;
} }
else if ( part.getReferencedPropertyName() != null else if ( part.getKeyTargetMatchPart() != null
&& part.getReferencedPropertyName().equals( getAttributeName() ) ) { // && part.getKeyTargetMatchPart().equals( this ) ) {
&& part.getKeyTargetMatchPart().getPartName().equals( getAttributeName() ) ) {
return true; return true;
} }
} }
@ -300,4 +306,14 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
public boolean isUnwrapProxy() { public boolean isUnwrapProxy() {
return unwrapProxy; return unwrapProxy;
} }
@Override
public EntityMappingType getAssociatedEntityMappingType() {
return getEntityMappingType();
}
@Override
public ModelPart getKeyTargetMatchPart() {
return foreignKeyDescriptor;
}
} }

View File

@ -22,7 +22,6 @@ import org.hibernate.AssertionFailure;
import org.hibernate.FetchMode; import org.hibernate.FetchMode;
import org.hibernate.Filter; import org.hibernate.Filter;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.LockOptions;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.QueryException; import org.hibernate.QueryException;
@ -43,7 +42,6 @@ import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
import org.hibernate.engine.profile.Fetch; import org.hibernate.engine.profile.Fetch;
import org.hibernate.engine.profile.FetchProfile; import org.hibernate.engine.profile.FetchProfile;
import org.hibernate.engine.spi.CollectionKey;
import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.LoadQueryInfluencers;
@ -61,7 +59,7 @@ import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.jdbc.Expectation; import org.hibernate.jdbc.Expectation;
import org.hibernate.jdbc.Expectations; import org.hibernate.jdbc.Expectations;
import org.hibernate.loader.collection.CollectionInitializer; import org.hibernate.loader.collection.CollectionInitializer;
import org.hibernate.loader.internal.MetamodelSelectBuilderProcess; import org.hibernate.loader.internal.BatchCollectionKeyLoader;
import org.hibernate.loader.internal.SingleCollectionKeyLoader; import org.hibernate.loader.internal.SingleCollectionKeyLoader;
import org.hibernate.loader.internal.SubSelectFetchCollectionLoader; import org.hibernate.loader.internal.SubSelectFetchCollectionLoader;
import org.hibernate.loader.spi.CollectionLoader; import org.hibernate.loader.spi.CollectionLoader;
@ -115,8 +113,6 @@ import org.hibernate.sql.ast.tree.from.TableReferenceCollector;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin; import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.spi.JdbcParameter;
import org.hibernate.sql.results.spi.DomainResult; import org.hibernate.sql.results.spi.DomainResult;
import org.hibernate.type.AnyType; import org.hibernate.type.AnyType;
import org.hibernate.type.AssociationType; import org.hibernate.type.AssociationType;
@ -859,21 +855,12 @@ public abstract class AbstractCollectionPersister
} }
protected CollectionLoader createCollectionLoader(LoadQueryInfluencers loadQueryInfluencers) { protected CollectionLoader createCollectionLoader(LoadQueryInfluencers loadQueryInfluencers) {
final java.util.List<JdbcParameter> jdbcParameters = new ArrayList<>(); final int batchSize = getBatchSize();
if ( batchSize > 1 ) {
return new BatchCollectionKeyLoader( attributeMapping, batchSize, loadQueryInfluencers, getFactory() );
}
final SelectStatement sqlAst = MetamodelSelectBuilderProcess.createSelect( return new SingleCollectionKeyLoader( attributeMapping, loadQueryInfluencers, getFactory() );
attributeMapping,
null,
attributeMapping.getKeyDescriptor(),
null,
1,
loadQueryInfluencers,
LockOptions.READ,
jdbcParameters::add,
getFactory()
);
return new SingleCollectionKeyLoader( attributeMapping, sqlAst, jdbcParameters );
} }
protected CollectionInitializer getAppropriateInitializer(Object key, SharedSessionContractImplementor session) { protected CollectionInitializer getAppropriateInitializer(Object key, SharedSessionContractImplementor session) {

View File

@ -107,18 +107,15 @@ import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.jdbc.Expectation; import org.hibernate.jdbc.Expectation;
import org.hibernate.jdbc.Expectations; import org.hibernate.jdbc.Expectations;
import org.hibernate.jdbc.TooManyRowsAffectedException; import org.hibernate.jdbc.TooManyRowsAffectedException;
import org.hibernate.loader.BatchFetchStyle;
import org.hibernate.loader.custom.sql.SQLQueryParser; import org.hibernate.loader.custom.sql.SQLQueryParser;
import org.hibernate.loader.entity.BatchingEntityLoaderBuilder; import org.hibernate.loader.entity.BatchingEntityLoaderBuilder;
import org.hibernate.loader.entity.CascadeEntityLoader; import org.hibernate.loader.entity.CascadeEntityLoader;
import org.hibernate.loader.entity.EntityLoader; import org.hibernate.loader.entity.EntityLoader;
import org.hibernate.loader.entity.UniqueEntityLoader; import org.hibernate.loader.entity.UniqueEntityLoader;
import org.hibernate.loader.internal.SingleIdEntityLoaderDynamicBatch;
import org.hibernate.loader.internal.MultiIdEntityLoaderStandardImpl; import org.hibernate.loader.internal.MultiIdEntityLoaderStandardImpl;
import org.hibernate.loader.internal.NaturalIdLoaderStandardImpl; import org.hibernate.loader.internal.NaturalIdLoaderStandardImpl;
import org.hibernate.loader.internal.Preparable; import org.hibernate.loader.internal.Preparable;
import org.hibernate.loader.internal.SingleIdEntityLoaderLegacyBatch; import org.hibernate.loader.internal.SingleIdEntityLoaderDynamicBatch;
import org.hibernate.loader.internal.SingleIdEntityLoaderPaddedBatch;
import org.hibernate.loader.internal.SingleIdEntityLoaderProvidedQueryImpl; import org.hibernate.loader.internal.SingleIdEntityLoaderProvidedQueryImpl;
import org.hibernate.loader.internal.SingleIdEntityLoaderStandardImpl; import org.hibernate.loader.internal.SingleIdEntityLoaderStandardImpl;
import org.hibernate.loader.spi.Loader; import org.hibernate.loader.spi.Loader;
@ -144,7 +141,6 @@ import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityVersionMapping; import org.hibernate.metamodel.mapping.EntityVersionMapping;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.NaturalIdMapping;
@ -172,7 +168,6 @@ import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.NavigablePath; import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.Alias; import org.hibernate.sql.Alias;
import org.hibernate.sql.Delete; import org.hibernate.sql.Delete;
import org.hibernate.sql.Insert; import org.hibernate.sql.Insert;
@ -188,6 +183,7 @@ import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAliasStemHelper; import org.hibernate.sql.ast.spi.SqlAliasStemHelper;
import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroup;
@ -1016,22 +1012,7 @@ public abstract class AbstractEntityPersister
EntityMappingType entityDescriptor, EntityMappingType entityDescriptor,
int batchSize, int batchSize,
SessionFactoryImplementor factory) { SessionFactoryImplementor factory) {
final BatchFetchStyle batchFetchStyle = factory.getSettings().getBatchFetchStyle(); return new SingleIdEntityLoaderDynamicBatch( entityDescriptor, batchSize, factory );
switch ( batchFetchStyle ) {
case LEGACY: {
return new SingleIdEntityLoaderLegacyBatch( entityDescriptor, batchSize, factory );
}
case DYNAMIC: {
return new SingleIdEntityLoaderDynamicBatch( entityDescriptor, batchSize, factory );
}
case PADDED: {
return new SingleIdEntityLoaderPaddedBatch( entityDescriptor, batchSize, factory );
}
default: {
throw new UnsupportedOperationException( "BatchFetchStyle [" + batchFetchStyle.name() + "] not supported" );
}
}
} }
@SuppressWarnings("RedundantIfStatement") @SuppressWarnings("RedundantIfStatement")

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later * 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 * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/ */
package org.hibernate.orm.test.loading; package org.hibernate.orm.test.loading.multiLoad;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;

View File

@ -4,7 +4,7 @@
* License: GNU Lesser General Public License (LGPL), version 2.1 or later. * 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>. * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/ */
package org.hibernate.test.ops.multiLoad; package org.hibernate.orm.test.loading.multiLoad;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -25,8 +25,8 @@ import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Fetch; import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode; import org.hibernate.annotations.FetchMode;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import org.hibernate.stat.Statistics;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.After; import org.junit.After;
@ -83,6 +83,7 @@ public class MultiLoadSubSelectCollectionTest extends BaseNonConfigCoreFunctiona
@Test @Test
@TestForIssue( jiraKey = "HHH-12740" ) @TestForIssue( jiraKey = "HHH-12740" )
@FailureExpected( jiraKey = "none yet", message = "subselect-fetching not triggered")
public void testSubselect() { public void testSubselect() {
doInHibernate( doInHibernate(
this::sessionFactory, session -> { this::sessionFactory, session -> {

View File

@ -0,0 +1,497 @@
/*
* 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.loading.multiLoad;
import java.util.List;
import java.util.Objects;
import javax.persistence.Cacheable;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.SharedCacheMode;
import javax.persistence.Table;
import org.hibernate.CacheMode;
import org.hibernate.annotations.BatchSize;
import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.cache.spi.access.AccessType;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.stat.Statistics;
import org.hibernate.tool.schema.Action;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.jdbc.SQLStatementInterceptor;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryProducer;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
/**
* @author Steve Ebersole
*/
@SuppressWarnings("WeakerAccess")
@ServiceRegistry(
settings = {
@ServiceRegistry.Setting( name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "true" ),
@ServiceRegistry.Setting( name = AvailableSettings.GENERATE_STATISTICS, value = "true" ),
@ServiceRegistry.Setting( name = AvailableSettings.HBM2DDL_DATABASE_ACTION, value = "create-drop" )
}
)
@DomainModel(
annotatedClasses = MultiLoadTest.SimpleEntity.class,
sharedCacheMode = SharedCacheMode.ENABLE_SELECTIVE,
accessType = AccessType.READ_WRITE
)
@SessionFactory
public class MultiLoadTest implements SessionFactoryProducer {
private SQLStatementInterceptor sqlStatementInterceptor;
@Override
public SessionFactoryImplementor produceSessionFactory(MetadataImplementor model) {
final SessionFactoryBuilder sessionFactoryBuilder = model.getSessionFactoryBuilder();
sqlStatementInterceptor = new SQLStatementInterceptor( sessionFactoryBuilder );
return (SessionFactoryImplementor) sessionFactoryBuilder.build();
}
@BeforeEach
public void before(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.setCacheMode( CacheMode.IGNORE );
for ( int i = 1; i <= 60; i++ ) {
session.save( new SimpleEntity( i, "Entity #" + i ) );
}
}
);
}
@AfterEach
public void after(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "delete SimpleEntity" ).executeUpdate();
}
);
}
@Test
public void testBasicMultiLoad(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
sqlStatementInterceptor.getSqlQueries().clear();
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( ids( 5 ) );
assertEquals( 5, list.size() );
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?, ?, ?, ?, ?)" ) );
}
);
}
@Test
@TestForIssue( jiraKey = "HHH-10984" )
public void testUnflushedDeleteAndThenMultiLoad(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
// delete one of them (but do not flush)...
session.delete( session.load( SimpleEntity.class, 5 ) );
// as a baseline, assert based on how load() handles it
SimpleEntity s5 = session.load( SimpleEntity.class, 5 );
assertNotNull( s5 );
// and then, assert how get() handles it
s5 = session.get( SimpleEntity.class, 5 );
assertNull( s5 );
// finally assert how multiLoad handles it
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( ids(56) );
assertEquals( 56, list.size() );
}
);
}
@Test
@TestForIssue( jiraKey = "HHH-10617" )
public void testDuplicatedRequestedIds(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
// ordered multiLoad
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( 1, 2, 3, 2, 2 );
assertEquals( 5, list.size() );
assertSame( list.get( 1 ), list.get( 3 ) );
assertSame( list.get( 1 ), list.get( 4 ) );
// un-ordered multiLoad
list = session.byMultipleIds( SimpleEntity.class ).enableOrderedReturn( false ).multiLoad( 1, 2, 3, 2, 2 );
assertEquals( 3, list.size() );
}
);
}
@Test
@TestForIssue( jiraKey = "HHH-10617" )
public void testNonExistentIdRequest(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
// ordered multiLoad
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( 1, 699, 2 );
assertEquals( 3, list.size() );
assertNull( list.get( 1 ) );
// un-ordered multiLoad
list = session.byMultipleIds( SimpleEntity.class ).enableOrderedReturn( false ).multiLoad( 1, 699, 2 );
assertEquals( 2, list.size() );
}
);
}
@Test
public void testBasicMultiLoadWithManagedAndNoChecking(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
SimpleEntity first = session.byId( SimpleEntity.class ).load( 1 );
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( ids( 56 ) );
assertEquals( 56, list.size() );
// this check is HIGHLY specific to implementation in the batch loader
// which puts existing managed entities first...
assertSame( first, list.get( 0 ) );
}
);
}
@Test
public void testBasicMultiLoadWithManagedAndChecking(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
SimpleEntity first = session.byId( SimpleEntity.class ).load( 1 );
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class )
.enableSessionCheck( true )
.multiLoad( ids( 56 ) );
assertEquals( 56, list.size() );
// this check is HIGHLY specific to implementation in the batch loader
// which puts existing managed entities first...
assertSame( first, list.get( 0 ) );
}
);
}
@Test
@TestForIssue(jiraKey = "HHH-12944")
@FailureExpected( reason = "Caching/CacheMode supported not yet implemented" )
public void testMultiLoadFrom2ndLevelCache(SessionFactoryScope scope) {
Statistics statistics = scope.getSessionFactory().getStatistics();
scope.getSessionFactory().getCache().evictAll();
statistics.clear();
scope.inTransaction(
session -> {
// Load 1 of the items directly
SimpleEntity entity = session.get( SimpleEntity.class, 2 );
assertNotNull( entity );
assertEquals( 1, statistics.getSecondLevelCacheMissCount() );
assertEquals( 0, statistics.getSecondLevelCacheHitCount() );
assertEquals( 1, statistics.getSecondLevelCachePutCount() );
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
}
);
statistics.clear();
scope.inTransaction(
session -> {
// Validate that the entity is still in the Level 2 cache
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
sqlStatementInterceptor.getSqlQueries().clear();
// Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache.
List<SimpleEntity> entities = session.byMultipleIds( SimpleEntity.class )
.with( CacheMode.NORMAL )
.enableSessionCheck( true )
.multiLoad( ids( 3 ) );
assertEquals( 3, entities.size() );
assertEquals( 1, statistics.getSecondLevelCacheHitCount() );
for(SimpleEntity entity: entities) {
assertTrue( session.contains( entity ) );
}
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?, ?)" ) );
}
);
}
@Test
@TestForIssue(jiraKey = "HHH-12944")
@FailureExpected( reason = "Caching/CacheMode supported not yet implemented" )
public void testUnorderedMultiLoadFrom2ndLevelCache(SessionFactoryScope scope) {
Statistics statistics = scope.getSessionFactory().getStatistics();
scope.getSessionFactory().getCache().evictAll();
statistics.clear();
scope.inTransaction(
session -> {
// Load 1 of the items directly
SimpleEntity entity = session.get( SimpleEntity.class, 2 );
assertNotNull( entity );
assertEquals( 1, statistics.getSecondLevelCacheMissCount() );
assertEquals( 0, statistics.getSecondLevelCacheHitCount() );
assertEquals( 1, statistics.getSecondLevelCachePutCount() );
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
}
);
statistics.clear();
scope.inTransaction(
session -> {
// Validate that the entity is still in the Level 2 cache
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
sqlStatementInterceptor.getSqlQueries().clear();
// Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache.
List<SimpleEntity> entities = session.byMultipleIds( SimpleEntity.class )
.with( CacheMode.NORMAL )
.enableSessionCheck( true )
.enableOrderedReturn( false )
.multiLoad( ids( 3 ) );
assertEquals( 3, entities.size() );
assertEquals( 1, statistics.getSecondLevelCacheHitCount() );
for(SimpleEntity entity: entities) {
assertTrue( session.contains( entity ) );
}
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?, ?)" ) );
}
);
}
@Test
@TestForIssue(jiraKey = "HHH-12944")
@FailureExpected( reason = "Caching/CacheMode supported not yet implemented" )
public void testOrderedMultiLoadFrom2ndLevelCachePendingDelete(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.remove( session.find( SimpleEntity.class, 2 ) );
sqlStatementInterceptor.getSqlQueries().clear();
// Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache.
List<SimpleEntity> entities = session.byMultipleIds( SimpleEntity.class )
.with( CacheMode.NORMAL )
.enableSessionCheck( true )
.enableOrderedReturn( true )
.multiLoad( ids( 3 ) );
assertEquals( 3, entities.size() );
assertNull( entities.get(1) );
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?)" ) );
}
);
}
@Test
@TestForIssue(jiraKey = "HHH-12944")
public void testOrderedMultiLoadFrom2ndLevelCachePendingDeleteReturnRemoved(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.remove( session.find( SimpleEntity.class, 2 ) );
sqlStatementInterceptor.getSqlQueries().clear();
// Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache.
List<SimpleEntity> entities = session.byMultipleIds( SimpleEntity.class )
.with( CacheMode.NORMAL )
.enableSessionCheck( true )
.enableOrderedReturn( true )
.enableReturnOfDeletedEntities( true )
.multiLoad( ids( 3 ) );
assertEquals( 3, entities.size() );
SimpleEntity deletedEntity = entities.get(1);
assertNotNull( deletedEntity );
final EntityEntry entry = ((SharedSessionContractImplementor) session).getPersistenceContext().getEntry( deletedEntity );
assertTrue( entry.getStatus() == Status.DELETED || entry.getStatus() == Status.GONE );
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?, ?)" ) );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12944")
@FailureExpected( reason = "Caching/CacheMode supported not yet implemented" )
public void testUnorderedMultiLoadFrom2ndLevelCachePendingDelete(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.remove( session.find( SimpleEntity.class, 2 ) );
sqlStatementInterceptor.getSqlQueries().clear();
// Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache.
List<SimpleEntity> entities = session.byMultipleIds( SimpleEntity.class )
.with( CacheMode.NORMAL )
.enableSessionCheck( true )
.enableOrderedReturn( false )
.multiLoad( ids( 3 ) );
assertEquals( 3, entities.size() );
assertTrue( entities.stream().anyMatch( Objects::isNull ) );
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?)" ) );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12944")
public void testUnorderedMultiLoadFrom2ndLevelCachePendingDeleteReturnRemoved(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.remove( session.find( SimpleEntity.class, 2 ) );
sqlStatementInterceptor.getSqlQueries().clear();
// Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache.
List<SimpleEntity> entities = session.byMultipleIds( SimpleEntity.class )
.with( CacheMode.NORMAL )
.enableSessionCheck( true )
.enableOrderedReturn( false )
.enableReturnOfDeletedEntities( true )
.multiLoad( ids( 3 ) );
assertEquals( 3, entities.size() );
SimpleEntity deletedEntity = entities.stream().filter( simpleEntity -> simpleEntity.getId().equals( 2 ) ).findAny().orElse( null );
assertNotNull( deletedEntity );
final EntityEntry entry = ((SharedSessionContractImplementor) session).getPersistenceContext().getEntry( deletedEntity );
assertTrue( entry.getStatus() == Status.DELETED || entry.getStatus() == Status.GONE );
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?, ?)" ) );
} );
}
@Test
@FailureExpected( reason = "CacheMode not yet implemented" )
public void testMultiLoadWithCacheModeIgnore(SessionFactoryScope scope) {
// do the multi-load, telling Hibernate to IGNORE the L2 cache -
// the end result should be that the cache is (still) empty afterwards
scope.inTransaction(
session -> {
session.getTransaction().begin();
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class )
.with( CacheMode.IGNORE )
.multiLoad( ids( 56 ) );
session.getTransaction().commit();
session.close();
assertEquals( 56, list.size() );
for ( SimpleEntity entity : list ) {
assertFalse( scope.getSessionFactory().getCache().containsEntity( SimpleEntity.class, entity.getId() ) );
}
}
);
}
@Test
public void testMultiLoadClearsBatchFetchQueue(SessionFactoryScope scope) {
final EntityKey entityKey = new EntityKey(
1,
scope.getSessionFactory().getEntityPersister( SimpleEntity.class.getName() )
);
scope.inTransaction(
session -> {
// create a proxy, which should add an entry to the BatchFetchQueue
SimpleEntity first = session.byId( SimpleEntity.class ).getReference( 1 );
assertTrue( session.getPersistenceContext()
.getBatchFetchQueue()
.containsEntityKey( entityKey ) );
// now bulk load, which should clean up the BatchFetchQueue entry
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class )
.enableSessionCheck( true )
.multiLoad( ids( 56 ) );
assertEquals( 56, list.size() );
assertFalse( session.getPersistenceContext()
.getBatchFetchQueue()
.containsEntityKey( entityKey ) );
}
);
}
private Integer[] ids(int count) {
Integer[] ids = new Integer[count];
for ( int i = 1; i <= count; i++ ) {
ids[i-1] = i;
}
return ids;
}
@Entity( name = "SimpleEntity" )
@Table( name = "SimpleEntity" )
@Cacheable()
@BatchSize( size = 15 )
public static class SimpleEntity {
Integer id;
String text;
public SimpleEntity() {
}
public SimpleEntity(Integer id, String text) {
this.id = id;
this.text = text;
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
}

View File

@ -1,470 +0,0 @@
/*
* 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.test.ops.multiLoad;
import java.util.List;
import java.util.Objects;
import javax.persistence.Cacheable;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.SharedCacheMode;
import javax.persistence.Table;
import org.hibernate.CacheMode;
import org.hibernate.Session;
import org.hibernate.annotations.BatchSize;
import org.hibernate.boot.MetadataBuilder;
import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cache.spi.access.AccessType;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.stat.Statistics;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.jdbc.SQLStatementInterceptor;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
/**
* @author Steve Ebersole
*/
public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
private SQLStatementInterceptor sqlStatementInterceptor;
@Override
protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) {
sqlStatementInterceptor = new SQLStatementInterceptor( sfb );
}
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { SimpleEntity.class };
}
@Override
protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) {
super.configureStandardServiceRegistryBuilder( ssrb );
ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, true );
ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, Boolean.TRUE.toString() );
}
@Override
protected void configureMetadataBuilder(MetadataBuilder metadataBuilder) {
super.configureMetadataBuilder( metadataBuilder );
metadataBuilder.applySharedCacheMode( SharedCacheMode.ENABLE_SELECTIVE );
metadataBuilder.applyAccessType( AccessType.READ_WRITE );
}
@Before
public void before() {
Session session = sessionFactory().openSession();
session.getTransaction().begin();
session.setCacheMode( CacheMode.IGNORE );
for ( int i = 1; i <= 60; i++ ) {
session.save( new SimpleEntity( i, "Entity #" + i ) );
}
session.getTransaction().commit();
session.close();
}
@After
public void after() {
Session session = sessionFactory().openSession();
session.getTransaction().begin();
session.createQuery( "delete SimpleEntity" ).executeUpdate();
session.getTransaction().commit();
session.close();
}
@Test
public void testBasicMultiLoad() {
doInHibernate(
this::sessionFactory, session -> {
sqlStatementInterceptor.getSqlQueries().clear();
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( ids( 5 ) );
assertEquals( 5, list.size() );
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?,?,?,?)" ) );
}
);
}
@Test
@TestForIssue( jiraKey = "HHH-10984" )
public void testUnflushedDeleteAndThenMultiLoad() {
doInHibernate(
this::sessionFactory, session -> {
// delete one of them (but do not flush)...
session.delete( session.load( SimpleEntity.class, 5 ) );
// as a baseline, assert based on how load() handles it
SimpleEntity s5 = session.load( SimpleEntity.class, 5 );
assertNotNull( s5 );
// and then, assert how get() handles it
s5 = session.get( SimpleEntity.class, 5 );
assertNull( s5 );
// finally assert how multiLoad handles it
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( ids(56) );
assertEquals( 56, list.size() );
}
);
}
@Test
@TestForIssue( jiraKey = "HHH-10617" )
public void testDuplicatedRequestedIds() {
doInHibernate(
this::sessionFactory, session -> {
// ordered multiLoad
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( 1, 2, 3, 2, 2 );
assertEquals( 5, list.size() );
assertSame( list.get( 1 ), list.get( 3 ) );
assertSame( list.get( 1 ), list.get( 4 ) );
// un-ordered multiLoad
list = session.byMultipleIds( SimpleEntity.class ).enableOrderedReturn( false ).multiLoad( 1, 2, 3, 2, 2 );
assertEquals( 3, list.size() );
}
);
}
@Test
@TestForIssue( jiraKey = "HHH-10617" )
public void testNonExistentIdRequest() {
doInHibernate(
this::sessionFactory, session -> {
// ordered multiLoad
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( 1, 699, 2 );
assertEquals( 3, list.size() );
assertNull( list.get( 1 ) );
// un-ordered multiLoad
list = session.byMultipleIds( SimpleEntity.class ).enableOrderedReturn( false ).multiLoad( 1, 699, 2 );
assertEquals( 2, list.size() );
}
);
}
@Test
public void testBasicMultiLoadWithManagedAndNoChecking() {
Session session = openSession();
session.getTransaction().begin();
SimpleEntity first = session.byId( SimpleEntity.class ).load( 1 );
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( ids(56) );
assertEquals( 56, list.size() );
// this check is HIGHLY specific to implementation in the batch loader
// which puts existing managed entities first...
assertSame( first, list.get( 0 ) );
session.getTransaction().commit();
session.close();
}
@Test
public void testBasicMultiLoadWithManagedAndChecking() {
Session session = openSession();
session.getTransaction().begin();
SimpleEntity first = session.byId( SimpleEntity.class ).load( 1 );
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).enableSessionCheck( true ).multiLoad( ids(56) );
assertEquals( 56, list.size() );
// this check is HIGHLY specific to implementation in the batch loader
// which puts existing managed entities first...
assertSame( first, list.get( 0 ) );
session.getTransaction().commit();
session.close();
}
@Test
@TestForIssue(jiraKey = "HHH-12944")
public void testMultiLoadFrom2ndLevelCache() {
Statistics statistics = sessionFactory().getStatistics();
sessionFactory().getCache().evictAll();
statistics.clear();
doInHibernate( this::sessionFactory, session -> {
// Load 1 of the items directly
SimpleEntity entity = session.get( SimpleEntity.class, 2 );
assertNotNull( entity );
assertEquals( 1, statistics.getSecondLevelCacheMissCount() );
assertEquals( 0, statistics.getSecondLevelCacheHitCount() );
assertEquals( 1, statistics.getSecondLevelCachePutCount() );
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
} );
statistics.clear();
doInHibernate( this::sessionFactory, session -> {
// Validate that the entity is still in the Level 2 cache
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
sqlStatementInterceptor.getSqlQueries().clear();
// Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache.
List<SimpleEntity> entities = session.byMultipleIds( SimpleEntity.class )
.with( CacheMode.NORMAL )
.enableSessionCheck( true )
.multiLoad( ids( 3 ) );
assertEquals( 3, entities.size() );
assertEquals( 1, statistics.getSecondLevelCacheHitCount() );
for(SimpleEntity entity: entities) {
assertTrue( session.contains( entity ) );
}
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?)" ) );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12944")
public void testUnorderedMultiLoadFrom2ndLevelCache() {
Statistics statistics = sessionFactory().getStatistics();
sessionFactory().getCache().evictAll();
statistics.clear();
doInHibernate( this::sessionFactory, session -> {
// Load 1 of the items directly
SimpleEntity entity = session.get( SimpleEntity.class, 2 );
assertNotNull( entity );
assertEquals( 1, statistics.getSecondLevelCacheMissCount() );
assertEquals( 0, statistics.getSecondLevelCacheHitCount() );
assertEquals( 1, statistics.getSecondLevelCachePutCount() );
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
} );
statistics.clear();
doInHibernate( this::sessionFactory, session -> {
// Validate that the entity is still in the Level 2 cache
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
sqlStatementInterceptor.getSqlQueries().clear();
// Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache.
List<SimpleEntity> entities = session.byMultipleIds( SimpleEntity.class )
.with( CacheMode.NORMAL )
.enableSessionCheck( true )
.enableOrderedReturn( false )
.multiLoad( ids( 3 ) );
assertEquals( 3, entities.size() );
assertEquals( 1, statistics.getSecondLevelCacheHitCount() );
for(SimpleEntity entity: entities) {
assertTrue( session.contains( entity ) );
}
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?)" ) );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12944")
public void testOrderedMultiLoadFrom2ndLevelCachePendingDelete() {
doInHibernate( this::sessionFactory, session -> {
session.remove( session.find( SimpleEntity.class, 2 ) );
sqlStatementInterceptor.getSqlQueries().clear();
// Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache.
List<SimpleEntity> entities = session.byMultipleIds( SimpleEntity.class )
.with( CacheMode.NORMAL )
.enableSessionCheck( true )
.enableOrderedReturn( true )
.multiLoad( ids( 3 ) );
assertEquals( 3, entities.size() );
assertNull( entities.get(1) );
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?)" ) );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12944")
public void testOrderedMultiLoadFrom2ndLevelCachePendingDeleteReturnRemoved() {
doInHibernate( this::sessionFactory, session -> {
session.remove( session.find( SimpleEntity.class, 2 ) );
sqlStatementInterceptor.getSqlQueries().clear();
// Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache.
List<SimpleEntity> entities = session.byMultipleIds( SimpleEntity.class )
.with( CacheMode.NORMAL )
.enableSessionCheck( true )
.enableOrderedReturn( true )
.enableReturnOfDeletedEntities( true )
.multiLoad( ids( 3 ) );
assertEquals( 3, entities.size() );
SimpleEntity deletedEntity = entities.get(1);
assertNotNull( deletedEntity );
final EntityEntry entry = ((SharedSessionContractImplementor) session).getPersistenceContext().getEntry( deletedEntity );
assertTrue( entry.getStatus() == Status.DELETED || entry.getStatus() == Status.GONE );
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?)" ) );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12944")
public void testUnorderedMultiLoadFrom2ndLevelCachePendingDelete() {
doInHibernate( this::sessionFactory, session -> {
session.remove( session.find( SimpleEntity.class, 2 ) );
sqlStatementInterceptor.getSqlQueries().clear();
// Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache.
List<SimpleEntity> entities = session.byMultipleIds( SimpleEntity.class )
.with( CacheMode.NORMAL )
.enableSessionCheck( true )
.enableOrderedReturn( false )
.multiLoad( ids( 3 ) );
assertEquals( 3, entities.size() );
assertTrue( entities.stream().anyMatch( Objects::isNull ) );
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?)" ) );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12944")
public void testUnorderedMultiLoadFrom2ndLevelCachePendingDeleteReturnRemoved() {
doInHibernate( this::sessionFactory, session -> {
session.remove( session.find( SimpleEntity.class, 2 ) );
sqlStatementInterceptor.getSqlQueries().clear();
// Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache.
List<SimpleEntity> entities = session.byMultipleIds( SimpleEntity.class )
.with( CacheMode.NORMAL )
.enableSessionCheck( true )
.enableOrderedReturn( false )
.enableReturnOfDeletedEntities( true )
.multiLoad( ids( 3 ) );
assertEquals( 3, entities.size() );
SimpleEntity deletedEntity = entities.stream().filter( simpleEntity -> simpleEntity.getId().equals( 2 ) ).findAny().orElse( null );
assertNotNull( deletedEntity );
final EntityEntry entry = ((SharedSessionContractImplementor) session).getPersistenceContext().getEntry( deletedEntity );
assertTrue( entry.getStatus() == Status.DELETED || entry.getStatus() == Status.GONE );
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?)" ) );
} );
}
@Test
public void testMultiLoadWithCacheModeIgnore() {
// do the multi-load, telling Hibernate to IGNORE the L2 cache -
// the end result should be that the cache is (still) empty afterwards
Session session = openSession();
session.getTransaction().begin();
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class )
.with( CacheMode.IGNORE )
.multiLoad( ids(56) );
session.getTransaction().commit();
session.close();
assertEquals( 56, list.size() );
for ( SimpleEntity entity : list ) {
assertFalse( sessionFactory().getCache().containsEntity( SimpleEntity.class, entity.getId() ) );
}
}
@Test
public void testMultiLoadClearsBatchFetchQueue() {
final EntityKey entityKey = new EntityKey(
1,
sessionFactory().getEntityPersister( SimpleEntity.class.getName() )
);
Session session = openSession();
session.getTransaction().begin();
// create a proxy, which should add an entry to the BatchFetchQueue
SimpleEntity first = session.byId( SimpleEntity.class ).getReference( 1 );
assertTrue( ( (SessionImplementor) session ).getPersistenceContext().getBatchFetchQueue().containsEntityKey( entityKey ) );
// now bulk load, which should clean up the BatchFetchQueue entry
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).enableSessionCheck( true ).multiLoad( ids(56) );
assertEquals( 56, list.size() );
assertFalse( ( (SessionImplementor) session ).getPersistenceContext().getBatchFetchQueue().containsEntityKey( entityKey ) );
session.getTransaction().commit();
session.close();
}
private Integer[] ids(int count) {
Integer[] ids = new Integer[count];
for ( int i = 1; i <= count; i++ ) {
ids[i-1] = i;
}
return ids;
}
@Entity( name = "SimpleEntity" )
@Table( name = "SimpleEntity" )
@Cacheable()
@BatchSize( size = 15 )
public static class SimpleEntity {
Integer id;
String text;
public SimpleEntity() {
}
public SimpleEntity(Integer id, String text) {
this.id = id;
this.text = text;
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
}

View File

@ -12,6 +12,10 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import javax.persistence.SharedCacheMode;
import org.hibernate.cache.spi.access.AccessType;
import org.hibernate.testing.orm.domain.DomainModelDescriptor; import org.hibernate.testing.orm.domain.DomainModelDescriptor;
import org.hibernate.testing.orm.domain.StandardDomainModel; import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
@ -96,6 +100,10 @@ public @interface DomainModel {
ExtraQueryImport[] extraQueryImports() default {}; ExtraQueryImport[] extraQueryImports() default {};
Class<?>[] extraQueryImportClasses() default {}; Class<?>[] extraQueryImportClasses() default {};
SharedCacheMode sharedCacheMode() default SharedCacheMode.ENABLE_SELECTIVE;
AccessType accessType() default AccessType.READ_WRITE;
@interface ExtraQueryImport { @interface ExtraQueryImport {
String name(); String name();
Class<?> importedClass(); Class<?> importedClass();