HHH-14312 - entity graph is ignored for 'padded' and 'dynamic' batch style entity loader
- Adjusted multi-loading to use LoadPlans (and apply entity-graphs) as well. - All of the loaders/builder in `org.hibernate.loader.entity` are now no longer used, superseded by `org.hibernate.loader.entity.plan`. Removed no longer needed code. - Adjusted multi-load tests that relied on the actual generated SQL *String* - they now explicitly count the number of parameters and use that for assertions
This commit is contained in:
parent
0b2fb4e28b
commit
b296459851
|
@ -13,11 +13,9 @@ import java.util.List;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.LockOptions;
|
import org.hibernate.LockOptions;
|
||||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||||
import org.hibernate.engine.spi.QueryParameters;
|
|
||||||
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.OuterJoinLoader;
|
import org.hibernate.loader.OuterJoinLoader;
|
||||||
import org.hibernate.param.ParameterBinder;
|
|
||||||
import org.hibernate.persister.entity.OuterJoinLoadable;
|
import org.hibernate.persister.entity.OuterJoinLoadable;
|
||||||
import org.hibernate.transform.ResultTransformer;
|
import org.hibernate.transform.ResultTransformer;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
|
|
|
@ -1,142 +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.entity;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.hibernate.HibernateException;
|
|
||||||
import org.hibernate.LockOptions;
|
|
||||||
import org.hibernate.engine.internal.BatchFetchQueueHelper;
|
|
||||||
import org.hibernate.engine.spi.QueryParameters;
|
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
|
||||||
import org.hibernate.loader.Loader;
|
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
|
||||||
import org.hibernate.pretty.MessageHelper;
|
|
||||||
import org.hibernate.type.Type;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The base contract for loaders capable of performing batch-fetch loading of entities using multiple primary key
|
|
||||||
* values in the SQL <tt>WHERE</tt> clause.
|
|
||||||
*
|
|
||||||
* @author Gavin King
|
|
||||||
* @author Steve Ebersole
|
|
||||||
*
|
|
||||||
* @see BatchingEntityLoaderBuilder
|
|
||||||
* @see UniqueEntityLoader
|
|
||||||
*/
|
|
||||||
public abstract class BatchingEntityLoader implements UniqueEntityLoader {
|
|
||||||
private static final Logger log = Logger.getLogger( BatchingEntityLoader.class );
|
|
||||||
|
|
||||||
private final EntityPersister persister;
|
|
||||||
|
|
||||||
public BatchingEntityLoader(EntityPersister persister) {
|
|
||||||
this.persister = persister;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EntityPersister persister() {
|
|
||||||
return persister;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session) {
|
|
||||||
return load( id, optionalObject, session, LockOptions.NONE );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object load(
|
|
||||||
Serializable id,
|
|
||||||
Object optionalObject,
|
|
||||||
SharedSessionContractImplementor session,
|
|
||||||
LockOptions lockOptions,
|
|
||||||
Boolean readOnly) {
|
|
||||||
return load( id, optionalObject, session, lockOptions, readOnly );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, Boolean readOnly) {
|
|
||||||
return load( id, optionalObject, session, LockOptions.NONE, readOnly );
|
|
||||||
}
|
|
||||||
|
|
||||||
protected QueryParameters buildQueryParameters(
|
|
||||||
Serializable id,
|
|
||||||
Serializable[] ids,
|
|
||||||
Object optionalObject,
|
|
||||||
LockOptions lockOptions,
|
|
||||||
Boolean readOnly) {
|
|
||||||
Type[] types = new Type[ids.length];
|
|
||||||
Arrays.fill( types, persister().getIdentifierType() );
|
|
||||||
|
|
||||||
QueryParameters qp = new QueryParameters();
|
|
||||||
qp.setPositionalParameterTypes( types );
|
|
||||||
qp.setPositionalParameterValues( ids );
|
|
||||||
qp.setOptionalObject( optionalObject );
|
|
||||||
qp.setOptionalEntityName( persister().getEntityName() );
|
|
||||||
qp.setOptionalId( id );
|
|
||||||
qp.setLockOptions( lockOptions );
|
|
||||||
if ( readOnly != null ) {
|
|
||||||
qp.setReadOnly( readOnly );
|
|
||||||
}
|
|
||||||
return qp;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Object getObjectFromList(List results, Serializable id, SharedSessionContractImplementor session) {
|
|
||||||
for ( Object obj : results ) {
|
|
||||||
final boolean equal = persister.getIdentifierType().isEqual(
|
|
||||||
id,
|
|
||||||
session.getContextEntityIdentifier( obj ),
|
|
||||||
session.getFactory()
|
|
||||||
);
|
|
||||||
if ( equal ) {
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Object doBatchLoad(
|
|
||||||
Serializable id,
|
|
||||||
Loader loaderToUse,
|
|
||||||
SharedSessionContractImplementor session,
|
|
||||||
Serializable[] ids,
|
|
||||||
Object optionalObject,
|
|
||||||
LockOptions lockOptions,
|
|
||||||
Boolean readOnly) {
|
|
||||||
if ( log.isDebugEnabled() ) {
|
|
||||||
log.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister, ids, session.getFactory() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryParameters qp = buildQueryParameters( id, ids, optionalObject, lockOptions, readOnly );
|
|
||||||
|
|
||||||
try {
|
|
||||||
final List results = loaderToUse.doQueryAndInitializeNonLazyCollections( session, qp, false );
|
|
||||||
log.debug( "Done entity batch load" );
|
|
||||||
// The EntityKey for any entity that is not found will remain in the batch.
|
|
||||||
// Explicitly remove the EntityKeys for entities that were not found to
|
|
||||||
// avoid including them in future batches that get executed.
|
|
||||||
BatchFetchQueueHelper.removeNotFoundBatchLoadableEntityKeys(
|
|
||||||
ids,
|
|
||||||
results,
|
|
||||||
persister(),
|
|
||||||
session
|
|
||||||
);
|
|
||||||
return getObjectFromList(results, id, session);
|
|
||||||
}
|
|
||||||
catch ( SQLException sqle ) {
|
|
||||||
throw session.getJdbcServices().getSqlExceptionHelper().convert(
|
|
||||||
sqle,
|
|
||||||
"could not load an entity batch: " + MessageHelper.infoString( persister(), ids, session.getFactory() ),
|
|
||||||
loaderToUse.getSQLString()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -27,16 +27,12 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SessionImplementor;
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
import org.hibernate.engine.spi.Status;
|
import org.hibernate.engine.spi.Status;
|
||||||
import org.hibernate.event.internal.AbstractLockUpgradeEventListener;
|
import org.hibernate.event.internal.AbstractLockUpgradeEventListener;
|
||||||
import org.hibernate.event.service.spi.EventListenerRegistry;
|
|
||||||
import org.hibernate.event.spi.EventSource;
|
import org.hibernate.event.spi.EventSource;
|
||||||
import org.hibernate.event.spi.EventType;
|
|
||||||
import org.hibernate.event.spi.LoadEvent;
|
import org.hibernate.event.spi.LoadEvent;
|
||||||
import org.hibernate.event.spi.LoadEventListener;
|
import org.hibernate.event.spi.LoadEventListener;
|
||||||
import org.hibernate.event.spi.PostLoadEvent;
|
import org.hibernate.event.spi.PostLoadEvent;
|
||||||
import org.hibernate.event.spi.PostLoadEventListener;
|
|
||||||
import org.hibernate.internal.CoreLogging;
|
import org.hibernate.internal.CoreLogging;
|
||||||
import org.hibernate.internal.CoreMessageLogger;
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
import org.hibernate.internal.FastSessionServices;
|
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
import org.hibernate.pretty.MessageHelper;
|
import org.hibernate.pretty.MessageHelper;
|
||||||
import org.hibernate.proxy.HibernateProxy;
|
import org.hibernate.proxy.HibernateProxy;
|
||||||
|
|
|
@ -1,589 +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.entity;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.sql.Statement;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.hibernate.LockMode;
|
|
||||||
import org.hibernate.LockOptions;
|
|
||||||
import org.hibernate.dialect.pagination.LimitHelper;
|
|
||||||
import org.hibernate.engine.internal.BatchFetchQueueHelper;
|
|
||||||
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
|
|
||||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
|
||||||
import org.hibernate.engine.spi.EntityKey;
|
|
||||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
|
||||||
import org.hibernate.engine.spi.QueryParameters;
|
|
||||||
import org.hibernate.engine.spi.RowSelection;
|
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
|
||||||
import org.hibernate.engine.spi.Status;
|
|
||||||
import org.hibernate.event.spi.EventSource;
|
|
||||||
import org.hibernate.event.spi.LoadEvent;
|
|
||||||
import org.hibernate.event.spi.LoadEventListener;
|
|
||||||
import org.hibernate.internal.util.StringHelper;
|
|
||||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
|
||||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
|
||||||
import org.hibernate.loader.spi.AfterLoadAction;
|
|
||||||
import org.hibernate.persister.entity.MultiLoadOptions;
|
|
||||||
import org.hibernate.persister.entity.OuterJoinLoadable;
|
|
||||||
import org.hibernate.pretty.MessageHelper;
|
|
||||||
import org.hibernate.type.Type;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A BatchingEntityLoaderBuilder that builds UniqueEntityLoader instances capable of dynamically building
|
|
||||||
* its batch-fetch SQL based on the actual number of entity ids waiting to be fetched.
|
|
||||||
*
|
|
||||||
* @author Steve Ebersole
|
|
||||||
*/
|
|
||||||
public class DynamicBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder {
|
|
||||||
private static final Logger log = Logger.getLogger( DynamicBatchingEntityLoaderBuilder.class );
|
|
||||||
|
|
||||||
public static final DynamicBatchingEntityLoaderBuilder INSTANCE = new DynamicBatchingEntityLoaderBuilder();
|
|
||||||
|
|
||||||
public List multiLoad(
|
|
||||||
OuterJoinLoadable persister,
|
|
||||||
Serializable[] ids,
|
|
||||||
SharedSessionContractImplementor session,
|
|
||||||
MultiLoadOptions loadOptions) {
|
|
||||||
if ( loadOptions.isOrderReturnEnabled() ) {
|
|
||||||
return performOrderedMultiLoad( persister, ids, session, loadOptions );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return performUnorderedMultiLoad( persister, ids, session, loadOptions );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private List performOrderedMultiLoad(
|
|
||||||
OuterJoinLoadable persister,
|
|
||||||
Serializable[] ids,
|
|
||||||
SharedSessionContractImplementor session,
|
|
||||||
MultiLoadOptions loadOptions) {
|
|
||||||
assert loadOptions.isOrderReturnEnabled();
|
|
||||||
|
|
||||||
final List result = CollectionHelper.arrayList( ids.length );
|
|
||||||
|
|
||||||
final LockOptions lockOptions = (loadOptions.getLockOptions() == null)
|
|
||||||
? new LockOptions( LockMode.NONE )
|
|
||||||
: loadOptions.getLockOptions();
|
|
||||||
|
|
||||||
final int maxBatchSize;
|
|
||||||
if ( loadOptions.getBatchSize() != null && loadOptions.getBatchSize() > 0 ) {
|
|
||||||
maxBatchSize = loadOptions.getBatchSize();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
maxBatchSize = session.getJdbcServices().getJdbcEnvironment().getDialect().getDefaultBatchLoadSizingStrategy().determineOptimalBatchLoadSize(
|
|
||||||
persister.getIdentifierType().getColumnSpan( session.getFactory() ),
|
|
||||||
ids.length
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Serializable> idsInBatch = new ArrayList<>();
|
|
||||||
final List<Integer> elementPositionsLoadedByBatch = new ArrayList<>();
|
|
||||||
|
|
||||||
for ( int i = 0; i < ids.length; i++ ) {
|
|
||||||
final Serializable id = ids[i];
|
|
||||||
final EntityKey entityKey = new EntityKey( id, persister );
|
|
||||||
|
|
||||||
if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) {
|
|
||||||
LoadEvent loadEvent = new LoadEvent(
|
|
||||||
id,
|
|
||||||
persister.getMappedClass().getName(),
|
|
||||||
lockOptions,
|
|
||||||
(EventSource) session,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
Object managedEntity = null;
|
|
||||||
|
|
||||||
if ( loadOptions.isSessionCheckingEnabled() ) {
|
|
||||||
// look for it in the Session first
|
|
||||||
CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.INSTANCE
|
|
||||||
.loadFromSessionCache(
|
|
||||||
loadEvent,
|
|
||||||
entityKey,
|
|
||||||
LoadEventListener.GET
|
|
||||||
);
|
|
||||||
managedEntity = persistenceContextEntry.getEntity();
|
|
||||||
|
|
||||||
if ( managedEntity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() && !persistenceContextEntry
|
|
||||||
.isManaged() ) {
|
|
||||||
// put a null in the result
|
|
||||||
result.add( i, null );
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) {
|
|
||||||
// look for it in the SessionFactory
|
|
||||||
managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
|
|
||||||
loadEvent,
|
|
||||||
persister,
|
|
||||||
entityKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( managedEntity != null ) {
|
|
||||||
result.add( i, managedEntity );
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we did not hit any of the continues above, then we need to batch
|
|
||||||
// load the entity state.
|
|
||||||
idsInBatch.add( ids[i] );
|
|
||||||
|
|
||||||
if ( idsInBatch.size() >= maxBatchSize ) {
|
|
||||||
performOrderedBatchLoad( idsInBatch, lockOptions, persister, session );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the EntityKey instance for use later!
|
|
||||||
result.add( i, entityKey );
|
|
||||||
elementPositionsLoadedByBatch.add( i );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !idsInBatch.isEmpty() ) {
|
|
||||||
performOrderedBatchLoad( idsInBatch, lockOptions, persister, session );
|
|
||||||
}
|
|
||||||
|
|
||||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
|
||||||
for ( Integer position : elementPositionsLoadedByBatch ) {
|
|
||||||
// the element value at this position in the result List should be
|
|
||||||
// the EntityKey for that entity; reuse it!
|
|
||||||
final EntityKey entityKey = (EntityKey) result.get( position );
|
|
||||||
Object entity = persistenceContext.getEntity( entityKey );
|
|
||||||
if ( entity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() ) {
|
|
||||||
// make sure it is not DELETED
|
|
||||||
final EntityEntry entry = persistenceContext.getEntry( entity );
|
|
||||||
if ( entry.getStatus() == Status.DELETED || entry.getStatus() == Status.GONE ) {
|
|
||||||
// the entity is locally deleted, and the options ask that we not return such entities...
|
|
||||||
entity = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.set( position, entity );
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void performOrderedBatchLoad(
|
|
||||||
List<Serializable> idsInBatch,
|
|
||||||
LockOptions lockOptions,
|
|
||||||
OuterJoinLoadable persister,
|
|
||||||
SharedSessionContractImplementor session) {
|
|
||||||
final int batchSize = idsInBatch.size();
|
|
||||||
final DynamicEntityLoader batchingLoader = new DynamicEntityLoader(
|
|
||||||
persister,
|
|
||||||
batchSize,
|
|
||||||
lockOptions,
|
|
||||||
session.getFactory(),
|
|
||||||
session.getLoadQueryInfluencers()
|
|
||||||
);
|
|
||||||
|
|
||||||
final Serializable[] idsInBatchArray = idsInBatch.toArray( new Serializable[ idsInBatch.size() ] );
|
|
||||||
|
|
||||||
QueryParameters qp = buildMultiLoadQueryParameters( persister, idsInBatchArray, lockOptions );
|
|
||||||
batchingLoader.doEntityBatchFetch( session, qp, idsInBatchArray );
|
|
||||||
|
|
||||||
idsInBatch.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected List performUnorderedMultiLoad(
|
|
||||||
OuterJoinLoadable persister,
|
|
||||||
Serializable[] ids,
|
|
||||||
SharedSessionContractImplementor session,
|
|
||||||
MultiLoadOptions loadOptions) {
|
|
||||||
assert !loadOptions.isOrderReturnEnabled();
|
|
||||||
|
|
||||||
final List result = CollectionHelper.arrayList( ids.length );
|
|
||||||
|
|
||||||
final LockOptions lockOptions = (loadOptions.getLockOptions() == null)
|
|
||||||
? new LockOptions( LockMode.NONE )
|
|
||||||
: loadOptions.getLockOptions();
|
|
||||||
|
|
||||||
if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) {
|
|
||||||
// the user requested that we exclude ids corresponding to already managed
|
|
||||||
// entities from the generated load SQL. So here we will iterate all
|
|
||||||
// incoming id values and see whether it corresponds to an existing
|
|
||||||
// entity associated with the PC - if it does we add it to the result
|
|
||||||
// list immediately and remove its id from the group of ids to load.
|
|
||||||
boolean foundAnyManagedEntities = false;
|
|
||||||
final List<Serializable> nonManagedIds = new ArrayList<Serializable>();
|
|
||||||
for ( Serializable id : ids ) {
|
|
||||||
final EntityKey entityKey = new EntityKey( id, persister );
|
|
||||||
|
|
||||||
LoadEvent loadEvent = new LoadEvent(
|
|
||||||
id,
|
|
||||||
persister.getMappedClass().getName(),
|
|
||||||
lockOptions,
|
|
||||||
(EventSource) session,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
Object managedEntity = null;
|
|
||||||
|
|
||||||
// look for it in the Session first
|
|
||||||
CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.INSTANCE
|
|
||||||
.loadFromSessionCache(
|
|
||||||
loadEvent,
|
|
||||||
entityKey,
|
|
||||||
LoadEventListener.GET
|
|
||||||
);
|
|
||||||
if ( loadOptions.isSessionCheckingEnabled() ) {
|
|
||||||
managedEntity = persistenceContextEntry.getEntity();
|
|
||||||
|
|
||||||
if ( managedEntity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() && !persistenceContextEntry
|
|
||||||
.isManaged() ) {
|
|
||||||
foundAnyManagedEntities = true;
|
|
||||||
result.add( null );
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) {
|
|
||||||
managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
|
|
||||||
loadEvent,
|
|
||||||
persister,
|
|
||||||
entityKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( managedEntity != null ) {
|
|
||||||
foundAnyManagedEntities = true;
|
|
||||||
result.add( managedEntity );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
nonManagedIds.add( id );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( foundAnyManagedEntities ) {
|
|
||||||
if ( nonManagedIds.isEmpty() ) {
|
|
||||||
// all of the given ids were already associated with the Session
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// over-write the ids to be loaded with the collection of
|
|
||||||
// just non-managed ones
|
|
||||||
ids = nonManagedIds.toArray(
|
|
||||||
(Serializable[]) Array.newInstance(
|
|
||||||
ids.getClass().getComponentType(),
|
|
||||||
nonManagedIds.size()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int numberOfIdsLeft = ids.length;
|
|
||||||
final int maxBatchSize;
|
|
||||||
if ( loadOptions.getBatchSize() != null && loadOptions.getBatchSize() > 0 ) {
|
|
||||||
maxBatchSize = loadOptions.getBatchSize();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
maxBatchSize = session.getJdbcServices().getJdbcEnvironment().getDialect().getDefaultBatchLoadSizingStrategy().determineOptimalBatchLoadSize(
|
|
||||||
persister.getIdentifierType().getColumnSpan( session.getFactory() ),
|
|
||||||
numberOfIdsLeft
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
int idPosition = 0;
|
|
||||||
while ( numberOfIdsLeft > 0 ) {
|
|
||||||
int batchSize = Math.min( numberOfIdsLeft, maxBatchSize );
|
|
||||||
final DynamicEntityLoader batchingLoader = new DynamicEntityLoader(
|
|
||||||
persister,
|
|
||||||
batchSize,
|
|
||||||
lockOptions,
|
|
||||||
session.getFactory(),
|
|
||||||
session.getLoadQueryInfluencers()
|
|
||||||
);
|
|
||||||
|
|
||||||
Serializable[] idsInBatch = new Serializable[batchSize];
|
|
||||||
System.arraycopy( ids, idPosition, idsInBatch, 0, batchSize );
|
|
||||||
|
|
||||||
QueryParameters qp = buildMultiLoadQueryParameters( persister, idsInBatch, lockOptions );
|
|
||||||
result.addAll( batchingLoader.doEntityBatchFetch( session, qp, idsInBatch ) );
|
|
||||||
|
|
||||||
numberOfIdsLeft = numberOfIdsLeft - batchSize;
|
|
||||||
idPosition += batchSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static QueryParameters buildMultiLoadQueryParameters(
|
|
||||||
OuterJoinLoadable persister,
|
|
||||||
Serializable[] ids,
|
|
||||||
LockOptions lockOptions) {
|
|
||||||
Type[] types = new Type[ids.length];
|
|
||||||
Arrays.fill( types, persister.getIdentifierType() );
|
|
||||||
|
|
||||||
QueryParameters qp = new QueryParameters();
|
|
||||||
qp.setOptionalEntityName( persister.getEntityName() );
|
|
||||||
qp.setPositionalParameterTypes( types );
|
|
||||||
qp.setPositionalParameterValues( ids );
|
|
||||||
qp.setLockOptions( lockOptions );
|
|
||||||
qp.setOptionalObject( null );
|
|
||||||
qp.setOptionalId( null );
|
|
||||||
return qp;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected UniqueEntityLoader buildBatchingLoader(
|
|
||||||
OuterJoinLoadable persister,
|
|
||||||
int batchSize,
|
|
||||||
LockMode lockMode,
|
|
||||||
SessionFactoryImplementor factory,
|
|
||||||
LoadQueryInfluencers influencers) {
|
|
||||||
return new DynamicBatchingEntityLoader( persister, batchSize, lockMode, factory, influencers );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected UniqueEntityLoader buildBatchingLoader(
|
|
||||||
OuterJoinLoadable persister,
|
|
||||||
int batchSize,
|
|
||||||
LockOptions lockOptions,
|
|
||||||
SessionFactoryImplementor factory,
|
|
||||||
LoadQueryInfluencers influencers) {
|
|
||||||
return new DynamicBatchingEntityLoader( persister, batchSize, lockOptions, factory, influencers );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DynamicBatchingEntityLoader extends BatchingEntityLoader {
|
|
||||||
private final int maxBatchSize;
|
|
||||||
private final UniqueEntityLoader singleKeyLoader;
|
|
||||||
private final DynamicEntityLoader dynamicLoader;
|
|
||||||
|
|
||||||
public DynamicBatchingEntityLoader(
|
|
||||||
OuterJoinLoadable persister,
|
|
||||||
int maxBatchSize,
|
|
||||||
LockMode lockMode,
|
|
||||||
SessionFactoryImplementor factory,
|
|
||||||
LoadQueryInfluencers loadQueryInfluencers) {
|
|
||||||
super( persister );
|
|
||||||
this.maxBatchSize = maxBatchSize;
|
|
||||||
this.singleKeyLoader = new EntityLoader( persister, 1, lockMode, factory, loadQueryInfluencers );
|
|
||||||
this.dynamicLoader = new DynamicEntityLoader( persister, maxBatchSize, lockMode, factory, loadQueryInfluencers );
|
|
||||||
}
|
|
||||||
|
|
||||||
public DynamicBatchingEntityLoader(
|
|
||||||
OuterJoinLoadable persister,
|
|
||||||
int maxBatchSize,
|
|
||||||
LockOptions lockOptions,
|
|
||||||
SessionFactoryImplementor factory,
|
|
||||||
LoadQueryInfluencers loadQueryInfluencers) {
|
|
||||||
super( persister );
|
|
||||||
this.maxBatchSize = maxBatchSize;
|
|
||||||
this.singleKeyLoader = new EntityLoader( persister, 1, lockOptions, factory, loadQueryInfluencers );
|
|
||||||
this.dynamicLoader = new DynamicEntityLoader( persister, maxBatchSize, lockOptions, factory, loadQueryInfluencers );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object load(
|
|
||||||
Serializable id,
|
|
||||||
Object optionalObject,
|
|
||||||
SharedSessionContractImplementor session,
|
|
||||||
LockOptions lockOptions) {
|
|
||||||
return load (id, optionalObject, session, lockOptions, null );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object load(
|
|
||||||
Serializable id,
|
|
||||||
Object optionalObject,
|
|
||||||
SharedSessionContractImplementor session,
|
|
||||||
LockOptions lockOptions,
|
|
||||||
Boolean readOnly) {
|
|
||||||
final Serializable[] batch = session.getPersistenceContextInternal()
|
|
||||||
.getBatchFetchQueue()
|
|
||||||
.getEntityBatch( persister(), id, maxBatchSize, persister().getEntityMode() );
|
|
||||||
|
|
||||||
final int numberOfIds = ArrayHelper.countNonNull( batch );
|
|
||||||
if ( numberOfIds <= 1 ) {
|
|
||||||
final Object result = singleKeyLoader.load( id, optionalObject, session );
|
|
||||||
if ( result == null ) {
|
|
||||||
// There was no entity with the specified ID. Make sure the EntityKey does not remain
|
|
||||||
// in the batch to avoid including it in future batches that get executed.
|
|
||||||
BatchFetchQueueHelper.removeBatchLoadableEntityKey( id, persister(), session );
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Serializable[] idsToLoad = new Serializable[numberOfIds];
|
|
||||||
System.arraycopy( batch, 0, idsToLoad, 0, numberOfIds );
|
|
||||||
|
|
||||||
if ( log.isDebugEnabled() ) {
|
|
||||||
log.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister(), idsToLoad, session.getFactory() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
QueryParameters qp = buildQueryParameters( id, idsToLoad, optionalObject, lockOptions, readOnly );
|
|
||||||
List results = dynamicLoader.doEntityBatchFetch( session, qp, idsToLoad );
|
|
||||||
|
|
||||||
// The EntityKey for any entity that is not found will remain in the batch.
|
|
||||||
// Explicitly remove the EntityKeys for entities that were not found to
|
|
||||||
// avoid including them in future batches that get executed.
|
|
||||||
BatchFetchQueueHelper.removeNotFoundBatchLoadableEntityKeys( idsToLoad, results, persister(), session );
|
|
||||||
|
|
||||||
return getObjectFromList( results, id, session );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static class DynamicEntityLoader extends EntityLoader {
|
|
||||||
// todo : see the discussion on org.hibernate.loader.collection.DynamicBatchingCollectionInitializerBuilder.DynamicBatchingCollectionLoader
|
|
||||||
|
|
||||||
private final String sqlTemplate;
|
|
||||||
private final String alias;
|
|
||||||
|
|
||||||
public DynamicEntityLoader(
|
|
||||||
OuterJoinLoadable persister,
|
|
||||||
int maxBatchSize,
|
|
||||||
LockOptions lockOptions,
|
|
||||||
SessionFactoryImplementor factory,
|
|
||||||
LoadQueryInfluencers loadQueryInfluencers) {
|
|
||||||
this( persister, maxBatchSize, lockOptions.getLockMode(), factory, loadQueryInfluencers );
|
|
||||||
}
|
|
||||||
|
|
||||||
public DynamicEntityLoader(
|
|
||||||
OuterJoinLoadable persister,
|
|
||||||
int maxBatchSize,
|
|
||||||
LockMode lockMode,
|
|
||||||
SessionFactoryImplementor factory,
|
|
||||||
LoadQueryInfluencers loadQueryInfluencers) {
|
|
||||||
super( persister, -1, lockMode, factory, loadQueryInfluencers );
|
|
||||||
|
|
||||||
EntityJoinWalker walker = new EntityJoinWalker(
|
|
||||||
persister,
|
|
||||||
persister.getIdentifierColumnNames(),
|
|
||||||
-1,
|
|
||||||
lockMode,
|
|
||||||
factory,
|
|
||||||
loadQueryInfluencers) {
|
|
||||||
@Override
|
|
||||||
protected StringBuilder whereString(String alias, String[] columnNames, int batchSize) {
|
|
||||||
return StringHelper.buildBatchFetchRestrictionFragment(
|
|
||||||
alias,
|
|
||||||
columnNames,
|
|
||||||
getFactory().getDialect()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
initFromWalker( walker );
|
|
||||||
this.sqlTemplate = walker.getSQLString();
|
|
||||||
this.alias = walker.getAlias();
|
|
||||||
postInstantiate();
|
|
||||||
|
|
||||||
if ( LOG.isDebugEnabled() ) {
|
|
||||||
LOG.debugf(
|
|
||||||
"SQL-template for dynamic entity [%s] batch-fetching [%s] : %s",
|
|
||||||
entityName,
|
|
||||||
lockMode,
|
|
||||||
sqlTemplate
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isSingleRowLoader() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isSubselectLoadingEnabled() {
|
|
||||||
return persister.hasSubselectLoadableCollections();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List doEntityBatchFetch(
|
|
||||||
SharedSessionContractImplementor session,
|
|
||||||
QueryParameters queryParameters,
|
|
||||||
Serializable[] ids) {
|
|
||||||
final JdbcServices jdbcServices = session.getJdbcServices();
|
|
||||||
final String sql = StringHelper.expandBatchIdPlaceholder(
|
|
||||||
sqlTemplate,
|
|
||||||
ids,
|
|
||||||
alias,
|
|
||||||
persister.getKeyColumnNames(),
|
|
||||||
jdbcServices.getJdbcEnvironment().getDialect()
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
|
||||||
boolean defaultReadOnlyOrig = persistenceContext.isDefaultReadOnly();
|
|
||||||
if ( queryParameters.isReadOnlyInitialized() ) {
|
|
||||||
// The read-only/modifiable mode for the query was explicitly set.
|
|
||||||
// Temporarily set the default read-only/modifiable setting to the query's setting.
|
|
||||||
persistenceContext.setDefaultReadOnly( queryParameters.isReadOnly() );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// The read-only/modifiable setting for the query was not initialized.
|
|
||||||
// Use the default read-only/modifiable from the persistence context instead.
|
|
||||||
queryParameters.setReadOnly( persistenceContext.isDefaultReadOnly() );
|
|
||||||
}
|
|
||||||
persistenceContext.beforeLoad();
|
|
||||||
List results;
|
|
||||||
try {
|
|
||||||
try {
|
|
||||||
results = doTheLoad( sql, queryParameters, session );
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
persistenceContext.afterLoad();
|
|
||||||
}
|
|
||||||
persistenceContext.initializeNonLazyCollections();
|
|
||||||
log.debug( "Done batch load" );
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
// Restore the original default
|
|
||||||
persistenceContext.setDefaultReadOnly( defaultReadOnlyOrig );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch ( SQLException sqle ) {
|
|
||||||
throw jdbcServices.getSqlExceptionHelper().convert(
|
|
||||||
sqle,
|
|
||||||
"could not load an entity batch: " + MessageHelper.infoString(
|
|
||||||
getEntityPersisters()[0],
|
|
||||||
ids,
|
|
||||||
session.getFactory()
|
|
||||||
),
|
|
||||||
sql
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List doTheLoad(String sql, QueryParameters queryParameters, SharedSessionContractImplementor session) throws SQLException {
|
|
||||||
final RowSelection selection = queryParameters.getRowSelection();
|
|
||||||
final int maxRows = LimitHelper.hasMaxRows( selection ) ?
|
|
||||||
selection.getMaxRows() :
|
|
||||||
Integer.MAX_VALUE;
|
|
||||||
|
|
||||||
final List<AfterLoadAction> afterLoadActions = new ArrayList<>();
|
|
||||||
final SqlStatementWrapper wrapper = executeQueryStatement( sql, queryParameters, false, afterLoadActions, session );
|
|
||||||
final ResultSet rs = wrapper.getResultSet();
|
|
||||||
final Statement st = wrapper.getStatement();
|
|
||||||
try {
|
|
||||||
return processResultSet( rs, queryParameters, session, false, null, maxRows, afterLoadActions );
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator();
|
|
||||||
jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( st );
|
|
||||||
jdbcCoordinator.afterStatementExecution();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -153,6 +153,10 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OuterJoinLoadable getEntityPersister() {
|
||||||
|
return entityPersister;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected LoadQueryDetails getStaticLoadQuery() {
|
protected LoadQueryDetails getStaticLoadQuery() {
|
||||||
return staticLoadQuery;
|
return staticLoadQuery;
|
||||||
|
@ -162,6 +166,25 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan
|
||||||
return entityName;
|
return entityName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<?> loadEntityBatch(
|
||||||
|
Serializable[] idsInBatch,
|
||||||
|
OuterJoinLoadable persister,
|
||||||
|
LockOptions lockOptions,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
final Type idType = persister.getIdentifierType();
|
||||||
|
|
||||||
|
return loadEntityBatch(
|
||||||
|
session,
|
||||||
|
idsInBatch,
|
||||||
|
persister.getIdentifierType(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
persister,
|
||||||
|
lockOptions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by wrappers that batch load entities
|
* Called by wrappers that batch load entities
|
||||||
* @param persister only needed for logging
|
* @param persister only needed for logging
|
||||||
|
|
|
@ -21,7 +21,7 @@ import org.hibernate.pretty.MessageHelper;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* Batching entity loader using dynamic where-clause
|
||||||
*/
|
*/
|
||||||
public class DynamicBatchingEntityLoader extends BatchingEntityLoader {
|
public class DynamicBatchingEntityLoader extends BatchingEntityLoader {
|
||||||
private static final Logger log = Logger.getLogger( DynamicBatchingEntityLoader.class );
|
private static final Logger log = Logger.getLogger( DynamicBatchingEntityLoader.class );
|
||||||
|
|
|
@ -14,7 +14,9 @@ import org.hibernate.loader.entity.UniqueEntityLoader;
|
||||||
import org.hibernate.persister.entity.OuterJoinLoadable;
|
import org.hibernate.persister.entity.OuterJoinLoadable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* A walking/plan based BatchingEntityLoaderBuilder that builds entity-loader instances
|
||||||
|
* capable of dynamically building its batch-fetch SQL based on the actual number of
|
||||||
|
* entity ids waiting to be batch fetched.
|
||||||
*/
|
*/
|
||||||
public class DynamicBatchingEntityLoaderBuilder extends AbstractBatchingEntityLoaderBuilder {
|
public class DynamicBatchingEntityLoaderBuilder extends AbstractBatchingEntityLoaderBuilder {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,19 +17,11 @@ import org.hibernate.loader.plan.exec.query.internal.QueryBuildingParametersImpl
|
||||||
import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters;
|
import org.hibernate.loader.plan.exec.query.spi.QueryBuildingParameters;
|
||||||
import org.hibernate.persister.entity.OuterJoinLoadable;
|
import org.hibernate.persister.entity.OuterJoinLoadable;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UniqueEntityLoader implementation that is the main functionality for LoadPlan-based Entity loading.
|
* UniqueEntityLoader implementation that is the main functionality for LoadPlan-based Entity loading.
|
||||||
* <p/>
|
|
||||||
* Can handle batch-loading as well as non-pk, unique-key loading,
|
|
||||||
* <p/>
|
|
||||||
* Much is ultimately delegated to its superclass, AbstractLoadPlanBasedEntityLoader. However:
|
|
||||||
*
|
|
||||||
* Loads an entity instance using outerjoin fetching to fetch associated entities.
|
|
||||||
* <br>
|
|
||||||
* The <tt>EntityPersister</tt> must implement <tt>Loadable</tt>. For other entities,
|
|
||||||
* create a customized subclass of <tt>Loader</tt>.
|
|
||||||
*
|
*
|
||||||
* @author Gavin King
|
* @author Gavin King
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
|
@ -176,4 +168,5 @@ public class EntityLoader extends AbstractLoadPlanBasedEntityLoader {
|
||||||
protected EntityLoadQueryDetails getStaticLoadQuery() {
|
protected EntityLoadQueryDetails getStaticLoadQuery() {
|
||||||
return (EntityLoadQueryDetails) super.getStaticLoadQuery();
|
return (EntityLoadQueryDetails) super.getStaticLoadQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,325 @@
|
||||||
|
/*
|
||||||
|
* 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.entity.plan;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.LockMode;
|
||||||
|
import org.hibernate.LockOptions;
|
||||||
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
|
import org.hibernate.engine.spi.EntityKey;
|
||||||
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
|
import org.hibernate.engine.spi.QueryParameters;
|
||||||
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
import org.hibernate.engine.spi.Status;
|
||||||
|
import org.hibernate.event.spi.EventSource;
|
||||||
|
import org.hibernate.event.spi.LoadEvent;
|
||||||
|
import org.hibernate.event.spi.LoadEventListener;
|
||||||
|
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||||
|
import org.hibernate.loader.entity.CacheEntityLoaderHelper;
|
||||||
|
import org.hibernate.persister.entity.MultiLoadOptions;
|
||||||
|
import org.hibernate.persister.entity.OuterJoinLoadable;
|
||||||
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class MultiEntityLoadingSupport {
|
||||||
|
public static List<?> multiLoad(
|
||||||
|
OuterJoinLoadable persister,
|
||||||
|
Serializable[] ids,
|
||||||
|
SharedSessionContractImplementor session,
|
||||||
|
MultiLoadOptions loadOptions) {
|
||||||
|
if ( loadOptions.isOrderReturnEnabled() ) {
|
||||||
|
return performOrderedMultiLoad( persister, ids, session, loadOptions );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return performUnorderedMultiLoad( persister, ids, session, loadOptions );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static List performOrderedMultiLoad(
|
||||||
|
OuterJoinLoadable persister,
|
||||||
|
Serializable[] ids,
|
||||||
|
SharedSessionContractImplementor session,
|
||||||
|
MultiLoadOptions loadOptions) {
|
||||||
|
assert loadOptions.isOrderReturnEnabled();
|
||||||
|
|
||||||
|
final List result = CollectionHelper.arrayList( ids.length );
|
||||||
|
|
||||||
|
final LockOptions lockOptions = (loadOptions.getLockOptions() == null)
|
||||||
|
? new LockOptions( LockMode.NONE )
|
||||||
|
: loadOptions.getLockOptions();
|
||||||
|
|
||||||
|
final int maxBatchSize;
|
||||||
|
if ( loadOptions.getBatchSize() != null && loadOptions.getBatchSize() > 0 ) {
|
||||||
|
maxBatchSize = loadOptions.getBatchSize();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
maxBatchSize = session.getJdbcServices().getJdbcEnvironment().getDialect().getDefaultBatchLoadSizingStrategy().determineOptimalBatchLoadSize(
|
||||||
|
persister.getIdentifierType().getColumnSpan( session.getFactory() ),
|
||||||
|
ids.length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Serializable> idsInBatch = new ArrayList<>();
|
||||||
|
final List<Integer> elementPositionsLoadedByBatch = new ArrayList<>();
|
||||||
|
|
||||||
|
for ( int i = 0; i < ids.length; i++ ) {
|
||||||
|
final Serializable id = ids[i];
|
||||||
|
final EntityKey entityKey = new EntityKey( id, persister );
|
||||||
|
|
||||||
|
if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) {
|
||||||
|
LoadEvent loadEvent = new LoadEvent(
|
||||||
|
id,
|
||||||
|
persister.getMappedClass().getName(),
|
||||||
|
lockOptions,
|
||||||
|
(EventSource) session,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
Object managedEntity = null;
|
||||||
|
|
||||||
|
if ( loadOptions.isSessionCheckingEnabled() ) {
|
||||||
|
// look for it in the Session first
|
||||||
|
CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.INSTANCE
|
||||||
|
.loadFromSessionCache(
|
||||||
|
loadEvent,
|
||||||
|
entityKey,
|
||||||
|
LoadEventListener.GET
|
||||||
|
);
|
||||||
|
managedEntity = persistenceContextEntry.getEntity();
|
||||||
|
|
||||||
|
if ( managedEntity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() && !persistenceContextEntry
|
||||||
|
.isManaged() ) {
|
||||||
|
// put a null in the result
|
||||||
|
result.add( i, null );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) {
|
||||||
|
// look for it in the SessionFactory
|
||||||
|
managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
|
||||||
|
loadEvent,
|
||||||
|
persister,
|
||||||
|
entityKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( managedEntity != null ) {
|
||||||
|
result.add( i, managedEntity );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we did not hit any of the continues above, then we need to batch
|
||||||
|
// load the entity state.
|
||||||
|
idsInBatch.add( ids[i] );
|
||||||
|
|
||||||
|
if ( idsInBatch.size() >= maxBatchSize ) {
|
||||||
|
performOrderedBatchLoad( idsInBatch, lockOptions, persister, session );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the EntityKey instance for use later!
|
||||||
|
result.add( i, entityKey );
|
||||||
|
elementPositionsLoadedByBatch.add( i );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !idsInBatch.isEmpty() ) {
|
||||||
|
performOrderedBatchLoad( idsInBatch, lockOptions, persister, session );
|
||||||
|
}
|
||||||
|
|
||||||
|
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||||
|
for ( Integer position : elementPositionsLoadedByBatch ) {
|
||||||
|
// the element value at this position in the result List should be
|
||||||
|
// the EntityKey for that entity; reuse it!
|
||||||
|
final EntityKey entityKey = (EntityKey) result.get( position );
|
||||||
|
Object entity = persistenceContext.getEntity( entityKey );
|
||||||
|
if ( entity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() ) {
|
||||||
|
// make sure it is not DELETED
|
||||||
|
final EntityEntry entry = persistenceContext.getEntry( entity );
|
||||||
|
if ( entry.getStatus() == Status.DELETED || entry.getStatus() == Status.GONE ) {
|
||||||
|
// the entity is locally deleted, and the options ask that we not return such entities...
|
||||||
|
entity = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.set( position, entity );
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void performOrderedBatchLoad(
|
||||||
|
List<Serializable> idsInBatch,
|
||||||
|
LockOptions lockOptions,
|
||||||
|
OuterJoinLoadable persister,
|
||||||
|
SharedSessionContractImplementor session) {
|
||||||
|
final EntityLoader entityLoader = EntityLoader.forEntity( persister )
|
||||||
|
.withInfluencers( session.getLoadQueryInfluencers() )
|
||||||
|
.withLockOptions( lockOptions )
|
||||||
|
.withBatchSize( idsInBatch.size() ).byPrimaryKey();
|
||||||
|
|
||||||
|
entityLoader.loadEntityBatch(
|
||||||
|
idsInBatch.toArray( new Serializable[0] ),
|
||||||
|
persister,
|
||||||
|
lockOptions,
|
||||||
|
session
|
||||||
|
);
|
||||||
|
|
||||||
|
idsInBatch.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected static List performUnorderedMultiLoad(
|
||||||
|
OuterJoinLoadable persister,
|
||||||
|
Serializable[] ids,
|
||||||
|
SharedSessionContractImplementor session,
|
||||||
|
MultiLoadOptions loadOptions) {
|
||||||
|
assert !loadOptions.isOrderReturnEnabled();
|
||||||
|
|
||||||
|
final List result = CollectionHelper.arrayList( ids.length );
|
||||||
|
|
||||||
|
final LockOptions lockOptions = (loadOptions.getLockOptions() == null)
|
||||||
|
? new LockOptions( LockMode.NONE )
|
||||||
|
: loadOptions.getLockOptions();
|
||||||
|
|
||||||
|
if ( loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() ) {
|
||||||
|
// the user requested that we exclude ids corresponding to already managed
|
||||||
|
// entities from the generated load SQL. So here we will iterate all
|
||||||
|
// incoming id values and see whether it corresponds to an existing
|
||||||
|
// entity associated with the PC - if it does we add it to the result
|
||||||
|
// list immediately and remove its id from the group of ids to load.
|
||||||
|
boolean foundAnyManagedEntities = false;
|
||||||
|
final List<Serializable> nonManagedIds = new ArrayList<Serializable>();
|
||||||
|
for ( Serializable id : ids ) {
|
||||||
|
final EntityKey entityKey = new EntityKey( id, persister );
|
||||||
|
|
||||||
|
LoadEvent loadEvent = new LoadEvent(
|
||||||
|
id,
|
||||||
|
persister.getMappedClass().getName(),
|
||||||
|
lockOptions,
|
||||||
|
(EventSource) session,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
Object managedEntity = null;
|
||||||
|
|
||||||
|
// look for it in the Session first
|
||||||
|
CacheEntityLoaderHelper.PersistenceContextEntry persistenceContextEntry = CacheEntityLoaderHelper.INSTANCE
|
||||||
|
.loadFromSessionCache(
|
||||||
|
loadEvent,
|
||||||
|
entityKey,
|
||||||
|
LoadEventListener.GET
|
||||||
|
);
|
||||||
|
if ( loadOptions.isSessionCheckingEnabled() ) {
|
||||||
|
managedEntity = persistenceContextEntry.getEntity();
|
||||||
|
|
||||||
|
if ( managedEntity != null && !loadOptions.isReturnOfDeletedEntitiesEnabled() && !persistenceContextEntry
|
||||||
|
.isManaged() ) {
|
||||||
|
foundAnyManagedEntities = true;
|
||||||
|
result.add( null );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( managedEntity == null && loadOptions.isSecondLevelCacheCheckingEnabled() ) {
|
||||||
|
managedEntity = CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache(
|
||||||
|
loadEvent,
|
||||||
|
persister,
|
||||||
|
entityKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( managedEntity != null ) {
|
||||||
|
foundAnyManagedEntities = true;
|
||||||
|
result.add( managedEntity );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nonManagedIds.add( id );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( foundAnyManagedEntities ) {
|
||||||
|
if ( nonManagedIds.isEmpty() ) {
|
||||||
|
// all of the given ids were already associated with the Session
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// over-write the ids to be loaded with the collection of
|
||||||
|
// just non-managed ones
|
||||||
|
ids = nonManagedIds.toArray(
|
||||||
|
(Serializable[]) Array.newInstance(
|
||||||
|
ids.getClass().getComponentType(),
|
||||||
|
nonManagedIds.size()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int numberOfIdsLeft = ids.length;
|
||||||
|
final int maxBatchSize;
|
||||||
|
if ( loadOptions.getBatchSize() != null && loadOptions.getBatchSize() > 0 ) {
|
||||||
|
maxBatchSize = loadOptions.getBatchSize();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
maxBatchSize = session.getJdbcServices().getJdbcEnvironment().getDialect().getDefaultBatchLoadSizingStrategy().determineOptimalBatchLoadSize(
|
||||||
|
persister.getIdentifierType().getColumnSpan( session.getFactory() ),
|
||||||
|
numberOfIdsLeft
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
int idPosition = 0;
|
||||||
|
while ( numberOfIdsLeft > 0 ) {
|
||||||
|
int batchSize = Math.min( numberOfIdsLeft, maxBatchSize );
|
||||||
|
|
||||||
|
final EntityLoader entityLoader = EntityLoader.forEntity( persister )
|
||||||
|
.withInfluencers( session.getLoadQueryInfluencers() )
|
||||||
|
.withLockOptions( lockOptions )
|
||||||
|
.withBatchSize( batchSize ).byPrimaryKey();
|
||||||
|
|
||||||
|
Serializable[] idsInBatch = new Serializable[batchSize];
|
||||||
|
System.arraycopy( ids, idPosition, idsInBatch, 0, batchSize );
|
||||||
|
|
||||||
|
final List<?> batchResults = entityLoader.loadEntityBatch(
|
||||||
|
idsInBatch,
|
||||||
|
persister,
|
||||||
|
lockOptions,
|
||||||
|
session
|
||||||
|
);
|
||||||
|
result.addAll( batchResults );
|
||||||
|
|
||||||
|
numberOfIdsLeft = numberOfIdsLeft - batchSize;
|
||||||
|
idPosition += batchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static QueryParameters buildMultiLoadQueryParameters(
|
||||||
|
OuterJoinLoadable persister,
|
||||||
|
Serializable[] ids,
|
||||||
|
LockOptions lockOptions) {
|
||||||
|
Type[] types = new Type[ids.length];
|
||||||
|
Arrays.fill( types, persister.getIdentifierType() );
|
||||||
|
|
||||||
|
QueryParameters qp = new QueryParameters();
|
||||||
|
qp.setOptionalEntityName( persister.getEntityName() );
|
||||||
|
qp.setPositionalParameterTypes( types );
|
||||||
|
qp.setPositionalParameterValues( ids );
|
||||||
|
qp.setLockOptions( lockOptions );
|
||||||
|
qp.setOptionalObject( null );
|
||||||
|
qp.setOptionalId( null );
|
||||||
|
return qp;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ import org.hibernate.loader.entity.UniqueEntityLoader;
|
||||||
import org.hibernate.persister.entity.OuterJoinLoadable;
|
import org.hibernate.persister.entity.OuterJoinLoadable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* Batching entity loader using padded where-clause
|
||||||
*/
|
*/
|
||||||
public class PaddedBatchingEntityLoader extends BatchingEntityLoader {
|
public class PaddedBatchingEntityLoader extends BatchingEntityLoader {
|
||||||
private final int[] batchSizes;
|
private final int[] batchSizes;
|
||||||
|
|
|
@ -14,7 +14,9 @@ import org.hibernate.loader.entity.UniqueEntityLoader;
|
||||||
import org.hibernate.persister.entity.OuterJoinLoadable;
|
import org.hibernate.persister.entity.OuterJoinLoadable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* A walking/plan based BatchingEntityLoaderBuilder that builds entity-loader instances
|
||||||
|
* building its batch-fetch SQL based on padding - using a set number of parameters, but
|
||||||
|
* setting "unneeded ones" to null.
|
||||||
*/
|
*/
|
||||||
public class PaddedBatchingEntityLoaderBuilder extends AbstractBatchingEntityLoaderBuilder {
|
public class PaddedBatchingEntityLoaderBuilder extends AbstractBatchingEntityLoaderBuilder {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -104,9 +104,10 @@ import org.hibernate.loader.custom.sql.SQLQueryParser;
|
||||||
import org.hibernate.loader.entity.BatchingEntityLoaderBuilder;
|
import org.hibernate.loader.entity.BatchingEntityLoaderBuilder;
|
||||||
import org.hibernate.loader.entity.CacheEntityLoaderHelper;
|
import org.hibernate.loader.entity.CacheEntityLoaderHelper;
|
||||||
import org.hibernate.loader.entity.CascadeEntityLoader;
|
import org.hibernate.loader.entity.CascadeEntityLoader;
|
||||||
import org.hibernate.loader.entity.DynamicBatchingEntityLoaderBuilder;
|
import org.hibernate.loader.entity.plan.DynamicBatchingEntityLoaderBuilder;
|
||||||
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.entity.plan.MultiEntityLoadingSupport;
|
||||||
import org.hibernate.mapping.Column;
|
import org.hibernate.mapping.Column;
|
||||||
import org.hibernate.mapping.Component;
|
import org.hibernate.mapping.Component;
|
||||||
import org.hibernate.mapping.Formula;
|
import org.hibernate.mapping.Formula;
|
||||||
|
@ -4461,7 +4462,7 @@ public abstract class AbstractEntityPersister
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List multiLoad(Serializable[] ids, SharedSessionContractImplementor session, MultiLoadOptions loadOptions) {
|
public List multiLoad(Serializable[] ids, SharedSessionContractImplementor session, MultiLoadOptions loadOptions) {
|
||||||
return DynamicBatchingEntityLoaderBuilder.INSTANCE.multiLoad(
|
return MultiEntityLoadingSupport.multiLoad(
|
||||||
this,
|
this,
|
||||||
ids,
|
ids,
|
||||||
session,
|
session,
|
||||||
|
|
|
@ -15,7 +15,6 @@ import javax.persistence.SharedCacheMode;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
|
||||||
import org.hibernate.CacheMode;
|
import org.hibernate.CacheMode;
|
||||||
import org.hibernate.Session;
|
|
||||||
import org.hibernate.annotations.BatchSize;
|
import org.hibernate.annotations.BatchSize;
|
||||||
import org.hibernate.boot.MetadataBuilder;
|
import org.hibernate.boot.MetadataBuilder;
|
||||||
import org.hibernate.boot.SessionFactoryBuilder;
|
import org.hibernate.boot.SessionFactoryBuilder;
|
||||||
|
@ -27,6 +26,7 @@ import org.hibernate.engine.spi.EntityKey;
|
||||||
import org.hibernate.engine.spi.SessionImplementor;
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.engine.spi.Status;
|
import org.hibernate.engine.spi.Status;
|
||||||
|
import org.hibernate.internal.util.StringHelper;
|
||||||
import org.hibernate.stat.Statistics;
|
import org.hibernate.stat.Statistics;
|
||||||
|
|
||||||
import org.hibernate.testing.TestForIssue;
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
@ -36,12 +36,13 @@ import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertSame;
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,36 +79,36 @@ public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() {
|
public void before() {
|
||||||
Session session = sessionFactory().openSession();
|
inTransaction(
|
||||||
session.getTransaction().begin();
|
session -> {
|
||||||
session.setCacheMode( CacheMode.IGNORE );
|
session.setCacheMode( CacheMode.IGNORE );
|
||||||
for ( int i = 1; i <= 60; i++ ) {
|
for ( int i = 1; i <= 60; i++ ) {
|
||||||
session.save( new SimpleEntity( i, "Entity #" + i ) );
|
session.save( new SimpleEntity( i, "Entity #" + i ) );
|
||||||
}
|
}
|
||||||
session.getTransaction().commit();
|
}
|
||||||
session.close();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void after() {
|
public void after() {
|
||||||
Session session = sessionFactory().openSession();
|
inTransaction(
|
||||||
session.getTransaction().begin();
|
session -> {
|
||||||
session.createQuery( "delete SimpleEntity" ).executeUpdate();
|
session.createQuery( "delete SimpleEntity" ).executeUpdate();
|
||||||
session.getTransaction().commit();
|
}
|
||||||
session.close();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBasicMultiLoad() {
|
public void testBasicMultiLoad() {
|
||||||
doInHibernate(
|
inTransaction(
|
||||||
this::sessionFactory, session -> {
|
session -> {
|
||||||
sqlStatementInterceptor.getSqlQueries().clear();
|
sqlStatementInterceptor.getSqlQueries().clear();
|
||||||
|
|
||||||
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( ids( 5 ) );
|
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( ids( 5 ) );
|
||||||
assertEquals( 5, list.size() );
|
assertEquals( 5, list.size() );
|
||||||
|
|
||||||
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?,?,?,?)" ) );
|
final int paramCount = StringHelper.countUnquoted( sqlStatementInterceptor.getSqlQueries().getFirst(), '?' );
|
||||||
|
assertThat( paramCount, is( 5 ) );
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -115,8 +116,8 @@ public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
@Test
|
@Test
|
||||||
@TestForIssue( jiraKey = "HHH-10984" )
|
@TestForIssue( jiraKey = "HHH-10984" )
|
||||||
public void testUnflushedDeleteAndThenMultiLoad() {
|
public void testUnflushedDeleteAndThenMultiLoad() {
|
||||||
doInHibernate(
|
inTransaction(
|
||||||
this::sessionFactory, session -> {
|
session -> {
|
||||||
// delete one of them (but do not flush)...
|
// delete one of them (but do not flush)...
|
||||||
session.delete( session.load( SimpleEntity.class, 5 ) );
|
session.delete( session.load( SimpleEntity.class, 5 ) );
|
||||||
|
|
||||||
|
@ -138,8 +139,8 @@ public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
@Test
|
@Test
|
||||||
@TestForIssue( jiraKey = "HHH-10617" )
|
@TestForIssue( jiraKey = "HHH-10617" )
|
||||||
public void testDuplicatedRequestedIds() {
|
public void testDuplicatedRequestedIds() {
|
||||||
doInHibernate(
|
inTransaction(
|
||||||
this::sessionFactory, session -> {
|
session -> {
|
||||||
// ordered multiLoad
|
// ordered multiLoad
|
||||||
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( 1, 2, 3, 2, 2 );
|
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( 1, 2, 3, 2, 2 );
|
||||||
assertEquals( 5, list.size() );
|
assertEquals( 5, list.size() );
|
||||||
|
@ -156,8 +157,8 @@ public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
@Test
|
@Test
|
||||||
@TestForIssue( jiraKey = "HHH-10617" )
|
@TestForIssue( jiraKey = "HHH-10617" )
|
||||||
public void testNonExistentIdRequest() {
|
public void testNonExistentIdRequest() {
|
||||||
doInHibernate(
|
inTransaction(
|
||||||
this::sessionFactory, session -> {
|
session -> {
|
||||||
// ordered multiLoad
|
// ordered multiLoad
|
||||||
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( 1, 699, 2 );
|
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( 1, 699, 2 );
|
||||||
assertEquals( 3, list.size() );
|
assertEquals( 3, list.size() );
|
||||||
|
@ -172,40 +173,42 @@ public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBasicMultiLoadWithManagedAndNoChecking() {
|
public void testBasicMultiLoadWithManagedAndNoChecking() {
|
||||||
Session session = openSession();
|
inTransaction(
|
||||||
session.getTransaction().begin();
|
session -> {
|
||||||
SimpleEntity first = session.byId( SimpleEntity.class ).load( 1 );
|
SimpleEntity first = session.byId( SimpleEntity.class ).load( 1 );
|
||||||
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( ids(56) );
|
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( ids(56) );
|
||||||
assertEquals( 56, list.size() );
|
assertEquals( 56, list.size() );
|
||||||
// this check is HIGHLY specific to implementation in the batch loader
|
// this check is HIGHLY specific to implementation in the batch loader
|
||||||
// which puts existing managed entities first...
|
// which puts existing managed entities first...
|
||||||
assertSame( first, list.get( 0 ) );
|
assertSame( first, list.get( 0 ) );
|
||||||
session.getTransaction().commit();
|
}
|
||||||
session.close();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBasicMultiLoadWithManagedAndChecking() {
|
public void testBasicMultiLoadWithManagedAndChecking() {
|
||||||
Session session = openSession();
|
inTransaction(
|
||||||
session.getTransaction().begin();
|
session -> {
|
||||||
SimpleEntity first = session.byId( SimpleEntity.class ).load( 1 );
|
SimpleEntity first = session.byId( SimpleEntity.class ).load( 1 );
|
||||||
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).enableSessionCheck( true ).multiLoad( ids(56) );
|
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).enableSessionCheck( true ).multiLoad( ids(56) );
|
||||||
assertEquals( 56, list.size() );
|
assertEquals( 56, list.size() );
|
||||||
// this check is HIGHLY specific to implementation in the batch loader
|
// this check is HIGHLY specific to implementation in the batch loader
|
||||||
// which puts existing managed entities first...
|
// which puts existing managed entities first...
|
||||||
assertSame( first, list.get( 0 ) );
|
assertSame( first, list.get( 0 ) );
|
||||||
session.getTransaction().commit();
|
}
|
||||||
session.close();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestForIssue(jiraKey = "HHH-12944")
|
@TestForIssue(jiraKey = "HHH-12944")
|
||||||
public void testMultiLoadFrom2ndLevelCache() {
|
public void testMultiLoadFrom2ndLevelCache() {
|
||||||
Statistics statistics = sessionFactory().getStatistics();
|
|
||||||
sessionFactory().getCache().evictAll();
|
sessionFactory().getCache().evictAll();
|
||||||
|
|
||||||
|
final Statistics statistics = sessionFactory().getStatistics();
|
||||||
statistics.clear();
|
statistics.clear();
|
||||||
|
|
||||||
doInHibernate( this::sessionFactory, session -> {
|
inTransaction(
|
||||||
|
session -> {
|
||||||
// Load 1 of the items directly
|
// Load 1 of the items directly
|
||||||
SimpleEntity entity = session.get( SimpleEntity.class, 2 );
|
SimpleEntity entity = session.get( SimpleEntity.class, 2 );
|
||||||
assertNotNull( entity );
|
assertNotNull( entity );
|
||||||
|
@ -214,11 +217,13 @@ public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
assertEquals( 0, statistics.getSecondLevelCacheHitCount() );
|
assertEquals( 0, statistics.getSecondLevelCacheHitCount() );
|
||||||
assertEquals( 1, statistics.getSecondLevelCachePutCount() );
|
assertEquals( 1, statistics.getSecondLevelCachePutCount() );
|
||||||
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
|
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
|
||||||
} );
|
}
|
||||||
|
);
|
||||||
|
|
||||||
statistics.clear();
|
statistics.clear();
|
||||||
|
|
||||||
doInHibernate( this::sessionFactory, session -> {
|
inTransaction(
|
||||||
|
session -> {
|
||||||
// Validate that the entity is still in the Level 2 cache
|
// Validate that the entity is still in the Level 2 cache
|
||||||
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
|
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
|
||||||
|
|
||||||
|
@ -229,38 +234,45 @@ public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
.with( CacheMode.NORMAL )
|
.with( CacheMode.NORMAL )
|
||||||
.enableSessionCheck( true )
|
.enableSessionCheck( true )
|
||||||
.multiLoad( ids( 3 ) );
|
.multiLoad( ids( 3 ) );
|
||||||
|
|
||||||
assertEquals( 3, entities.size() );
|
assertEquals( 3, entities.size() );
|
||||||
assertEquals( 1, statistics.getSecondLevelCacheHitCount() );
|
assertEquals( 1, statistics.getSecondLevelCacheHitCount() );
|
||||||
|
|
||||||
for(SimpleEntity entity: entities) {
|
for ( SimpleEntity entity: entities ) {
|
||||||
assertTrue( session.contains( entity ) );
|
assertTrue( session.contains( entity ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?)" ) );
|
final int paramCount = StringHelper.countUnquoted( sqlStatementInterceptor.getSqlQueries().getFirst(), '?' );
|
||||||
} );
|
assertThat( paramCount, is( 2 ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestForIssue(jiraKey = "HHH-12944")
|
@TestForIssue(jiraKey = "HHH-12944")
|
||||||
public void testUnorderedMultiLoadFrom2ndLevelCache() {
|
public void testUnorderedMultiLoadFrom2ndLevelCache() {
|
||||||
Statistics statistics = sessionFactory().getStatistics();
|
|
||||||
sessionFactory().getCache().evictAll();
|
sessionFactory().getCache().evictAll();
|
||||||
|
|
||||||
|
final Statistics statistics = sessionFactory().getStatistics();
|
||||||
statistics.clear();
|
statistics.clear();
|
||||||
|
|
||||||
doInHibernate( this::sessionFactory, session -> {
|
inTransaction(
|
||||||
|
session -> {
|
||||||
// Load 1 of the items directly
|
// Load 1 of the items directly
|
||||||
SimpleEntity entity = session.get( SimpleEntity.class, 2 );
|
final SimpleEntity entity = session.get( SimpleEntity.class, 2 );
|
||||||
assertNotNull( entity );
|
assertNotNull( entity );
|
||||||
|
|
||||||
assertEquals( 1, statistics.getSecondLevelCacheMissCount() );
|
assertEquals( 1, statistics.getSecondLevelCacheMissCount() );
|
||||||
assertEquals( 0, statistics.getSecondLevelCacheHitCount() );
|
assertEquals( 0, statistics.getSecondLevelCacheHitCount() );
|
||||||
assertEquals( 1, statistics.getSecondLevelCachePutCount() );
|
assertEquals( 1, statistics.getSecondLevelCachePutCount() );
|
||||||
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
|
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
|
||||||
} );
|
}
|
||||||
|
);
|
||||||
|
|
||||||
statistics.clear();
|
statistics.clear();
|
||||||
|
|
||||||
doInHibernate( this::sessionFactory, session -> {
|
inTransaction(
|
||||||
|
session -> {
|
||||||
// Validate that the entity is still in the Level 2 cache
|
// Validate that the entity is still in the Level 2 cache
|
||||||
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
|
assertTrue( session.getSessionFactory().getCache().containsEntity( SimpleEntity.class, 2 ) );
|
||||||
|
|
||||||
|
@ -279,20 +291,22 @@ public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
assertTrue( session.contains( entity ) );
|
assertTrue( session.contains( entity ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?)" ) );
|
final int paramCount = StringHelper.countUnquoted( sqlStatementInterceptor.getSqlQueries().getFirst(), '?' );
|
||||||
} );
|
assertThat( paramCount, is( 2 ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestForIssue(jiraKey = "HHH-12944")
|
@TestForIssue(jiraKey = "HHH-12944")
|
||||||
public void testOrderedMultiLoadFrom2ndLevelCachePendingDelete() {
|
public void testOrderedMultiLoadFrom2ndLevelCachePendingDelete() {
|
||||||
|
inTransaction(
|
||||||
doInHibernate( this::sessionFactory, session -> {
|
session -> {
|
||||||
session.remove( session.find( SimpleEntity.class, 2 ) );
|
session.remove( session.find( SimpleEntity.class, 2 ) );
|
||||||
|
|
||||||
sqlStatementInterceptor.getSqlQueries().clear();
|
sqlStatementInterceptor.getSqlQueries().clear();
|
||||||
|
|
||||||
// Multiload 3 items and ensure that multiload pulls 2 from the database & 1 from the cache.
|
// Multi-load 3 items and ensure that it pulls 2 from the database & 1 from the cache.
|
||||||
List<SimpleEntity> entities = session.byMultipleIds( SimpleEntity.class )
|
List<SimpleEntity> entities = session.byMultipleIds( SimpleEntity.class )
|
||||||
.with( CacheMode.NORMAL )
|
.with( CacheMode.NORMAL )
|
||||||
.enableSessionCheck( true )
|
.enableSessionCheck( true )
|
||||||
|
@ -302,15 +316,17 @@ public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
|
||||||
assertNull( entities.get(1) );
|
assertNull( entities.get(1) );
|
||||||
|
|
||||||
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?)" ) );
|
final int paramCount = StringHelper.countUnquoted( sqlStatementInterceptor.getSqlQueries().getFirst(), '?' );
|
||||||
} );
|
assertThat( paramCount, is( 2 ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestForIssue(jiraKey = "HHH-12944")
|
@TestForIssue(jiraKey = "HHH-12944")
|
||||||
public void testOrderedMultiLoadFrom2ndLevelCachePendingDeleteReturnRemoved() {
|
public void testOrderedMultiLoadFrom2ndLevelCachePendingDeleteReturnRemoved() {
|
||||||
|
inTransaction(
|
||||||
doInHibernate( this::sessionFactory, session -> {
|
session -> {
|
||||||
session.remove( session.find( SimpleEntity.class, 2 ) );
|
session.remove( session.find( SimpleEntity.class, 2 ) );
|
||||||
|
|
||||||
sqlStatementInterceptor.getSqlQueries().clear();
|
sqlStatementInterceptor.getSqlQueries().clear();
|
||||||
|
@ -330,15 +346,17 @@ public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
final EntityEntry entry = ((SharedSessionContractImplementor) session).getPersistenceContext().getEntry( deletedEntity );
|
final EntityEntry entry = ((SharedSessionContractImplementor) session).getPersistenceContext().getEntry( deletedEntity );
|
||||||
assertTrue( entry.getStatus() == Status.DELETED || entry.getStatus() == Status.GONE );
|
assertTrue( entry.getStatus() == Status.DELETED || entry.getStatus() == Status.GONE );
|
||||||
|
|
||||||
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?)" ) );
|
final int paramCount = StringHelper.countUnquoted( sqlStatementInterceptor.getSqlQueries().getFirst(), '?' );
|
||||||
} );
|
assertThat( paramCount, is( 2 ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestForIssue(jiraKey = "HHH-12944")
|
@TestForIssue(jiraKey = "HHH-12944")
|
||||||
public void testUnorderedMultiLoadFrom2ndLevelCachePendingDelete() {
|
public void testUnorderedMultiLoadFrom2ndLevelCachePendingDelete() {
|
||||||
|
inTransaction(
|
||||||
doInHibernate( this::sessionFactory, session -> {
|
session -> {
|
||||||
session.remove( session.find( SimpleEntity.class, 2 ) );
|
session.remove( session.find( SimpleEntity.class, 2 ) );
|
||||||
|
|
||||||
sqlStatementInterceptor.getSqlQueries().clear();
|
sqlStatementInterceptor.getSqlQueries().clear();
|
||||||
|
@ -353,15 +371,17 @@ public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
|
||||||
assertTrue( entities.stream().anyMatch( Objects::isNull ) );
|
assertTrue( entities.stream().anyMatch( Objects::isNull ) );
|
||||||
|
|
||||||
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?)" ) );
|
final int paramCount = StringHelper.countUnquoted( sqlStatementInterceptor.getSqlQueries().getFirst(), '?' );
|
||||||
} );
|
assertThat( paramCount, is( 2 ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestForIssue(jiraKey = "HHH-12944")
|
@TestForIssue(jiraKey = "HHH-12944")
|
||||||
public void testUnorderedMultiLoadFrom2ndLevelCachePendingDeleteReturnRemoved() {
|
public void testUnorderedMultiLoadFrom2ndLevelCachePendingDeleteReturnRemoved() {
|
||||||
|
inTransaction(
|
||||||
doInHibernate( this::sessionFactory, session -> {
|
session -> {
|
||||||
session.remove( session.find( SimpleEntity.class, 2 ) );
|
session.remove( session.find( SimpleEntity.class, 2 ) );
|
||||||
|
|
||||||
sqlStatementInterceptor.getSqlQueries().clear();
|
sqlStatementInterceptor.getSqlQueries().clear();
|
||||||
|
@ -381,37 +401,39 @@ public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
final EntityEntry entry = ((SharedSessionContractImplementor) session).getPersistenceContext().getEntry( deletedEntity );
|
final EntityEntry entry = ((SharedSessionContractImplementor) session).getPersistenceContext().getEntry( deletedEntity );
|
||||||
assertTrue( entry.getStatus() == Status.DELETED || entry.getStatus() == Status.GONE );
|
assertTrue( entry.getStatus() == Status.DELETED || entry.getStatus() == Status.GONE );
|
||||||
|
|
||||||
assertTrue( sqlStatementInterceptor.getSqlQueries().getFirst().endsWith( "id in (?,?)" ) );
|
final int paramCount = StringHelper.countUnquoted( sqlStatementInterceptor.getSqlQueries().getFirst(), '?' );
|
||||||
} );
|
assertThat( paramCount, is( 2 ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMultiLoadWithCacheModeIgnore() {
|
public void testMultiLoadWithCacheModeIgnore() {
|
||||||
// do the multi-load, telling Hibernate to IGNORE the L2 cache -
|
// do the multi-load, telling Hibernate to IGNORE the L2 cache -
|
||||||
// the end result should be that the cache is (still) empty afterwards
|
// the end result should be that the cache is (still) empty afterwards
|
||||||
Session session = openSession();
|
inTransaction(
|
||||||
session.getTransaction().begin();
|
session -> {
|
||||||
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class )
|
final List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class )
|
||||||
.with( CacheMode.IGNORE )
|
.with( CacheMode.IGNORE )
|
||||||
.multiLoad( ids(56) );
|
.multiLoad( ids(56) );
|
||||||
session.getTransaction().commit();
|
|
||||||
session.close();
|
|
||||||
|
|
||||||
assertEquals( 56, list.size() );
|
assertEquals( 56, list.size() );
|
||||||
for ( SimpleEntity entity : list ) {
|
for ( SimpleEntity entity : list ) {
|
||||||
assertFalse( sessionFactory().getCache().containsEntity( SimpleEntity.class, entity.getId() ) );
|
assertFalse( sessionFactory().getCache().containsEntity( SimpleEntity.class, entity.getId() ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMultiLoadClearsBatchFetchQueue() {
|
public void testMultiLoadClearsBatchFetchQueue() {
|
||||||
final EntityKey entityKey = new EntityKey(
|
final EntityKey entityKey = new EntityKey(
|
||||||
1,
|
1,
|
||||||
sessionFactory().getEntityPersister( SimpleEntity.class.getName() )
|
sessionFactory().getMetamodel().entityPersister( SimpleEntity.class.getName() )
|
||||||
);
|
);
|
||||||
|
|
||||||
Session session = openSession();
|
inTransaction(
|
||||||
session.getTransaction().begin();
|
session -> {
|
||||||
// create a proxy, which should add an entry to the BatchFetchQueue
|
// create a proxy, which should add an entry to the BatchFetchQueue
|
||||||
SimpleEntity first = session.byId( SimpleEntity.class ).getReference( 1 );
|
SimpleEntity first = session.byId( SimpleEntity.class ).getReference( 1 );
|
||||||
assertTrue( ( (SessionImplementor) session ).getPersistenceContext().getBatchFetchQueue().containsEntityKey( entityKey ) );
|
assertTrue( ( (SessionImplementor) session ).getPersistenceContext().getBatchFetchQueue().containsEntityKey( entityKey ) );
|
||||||
|
@ -421,9 +443,8 @@ public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
|
||||||
assertEquals( 56, list.size() );
|
assertEquals( 56, list.size() );
|
||||||
assertFalse( ( (SessionImplementor) session ).getPersistenceContext().getBatchFetchQueue().containsEntityKey( entityKey ) );
|
assertFalse( ( (SessionImplementor) session ).getPersistenceContext().getBatchFetchQueue().containsEntityKey( entityKey ) );
|
||||||
|
}
|
||||||
session.getTransaction().commit();
|
);
|
||||||
session.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer[] ids(int count) {
|
private Integer[] ids(int count) {
|
||||||
|
|
Loading…
Reference in New Issue