HHH-7572 - Develop API for load-by-multiple-ids
This commit is contained in:
parent
2b563794c7
commit
134eb06fba
|
@ -22,7 +22,16 @@ public interface IdentifierLoadAccess<T> {
|
||||||
*
|
*
|
||||||
* @return {@code this}, for method chaining
|
* @return {@code this}, for method chaining
|
||||||
*/
|
*/
|
||||||
public IdentifierLoadAccess<T> with(LockOptions lockOptions);
|
IdentifierLoadAccess<T> with(LockOptions lockOptions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the {@link CacheMode} to use when retrieving the entity.
|
||||||
|
*
|
||||||
|
* @param cacheMode The CacheMode to use.
|
||||||
|
*
|
||||||
|
* @return {@code this}, for method chaining
|
||||||
|
*/
|
||||||
|
IdentifierLoadAccess<T> with(CacheMode cacheMode);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the persistent instance with the given identifier, assuming that the instance exists. This method
|
* Return the persistent instance with the given identifier, assuming that the instance exists. This method
|
||||||
|
@ -36,7 +45,7 @@ public interface IdentifierLoadAccess<T> {
|
||||||
*
|
*
|
||||||
* @return the persistent instance or proxy
|
* @return the persistent instance or proxy
|
||||||
*/
|
*/
|
||||||
public T getReference(Serializable id);
|
T getReference(Serializable id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the persistent instance with the given identifier, or null if there is no such persistent instance.
|
* Return the persistent instance with the given identifier, or null if there is no such persistent instance.
|
||||||
|
@ -47,5 +56,5 @@ public interface IdentifierLoadAccess<T> {
|
||||||
*
|
*
|
||||||
* @return The persistent instance or {@code null}
|
* @return The persistent instance or {@code null}
|
||||||
*/
|
*/
|
||||||
public T load(Serializable id);
|
T load(Serializable id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads multiple entities at once by identifiers
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public interface MultiIdentifierLoadAccess<T> {
|
||||||
|
/**
|
||||||
|
* Specify the {@link LockOptions} to use when retrieving the entity.
|
||||||
|
*
|
||||||
|
* @param lockOptions The lock options to use.
|
||||||
|
*
|
||||||
|
* @return {@code this}, for method chaining
|
||||||
|
*/
|
||||||
|
MultiIdentifierLoadAccess<T> with(LockOptions lockOptions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the {@link CacheMode} to use when retrieving the entity.
|
||||||
|
*
|
||||||
|
* @param cacheMode The CacheMode to use.
|
||||||
|
*
|
||||||
|
* @return {@code this}, for method chaining
|
||||||
|
*/
|
||||||
|
MultiIdentifierLoadAccess<T> with(CacheMode cacheMode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a batch size for loading the entities (how many at a time). The default is
|
||||||
|
* to use a batch sizing strategy defined by the Dialect in use. Any greater-than-one
|
||||||
|
* value here will override that default behavior. If giving an explicit value here,
|
||||||
|
* care should be taken to not exceed the capabilities of of the underlying database.
|
||||||
|
*
|
||||||
|
* @param batchSize The batch size
|
||||||
|
*
|
||||||
|
* @return {@code this}, for method chaining
|
||||||
|
*/
|
||||||
|
MultiIdentifierLoadAccess<T> withBatchSize(int batchSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should we check the Session to see whether it already contains any of the
|
||||||
|
* entities to be loaded in a managed state <b>for the purpose of not including those
|
||||||
|
* ids to the batch-load SQL</b>
|
||||||
|
*
|
||||||
|
* @param enabled {@code true} enables this checking; {@code false} disables it.
|
||||||
|
*
|
||||||
|
* @return {@code this}, for method chaining
|
||||||
|
*/
|
||||||
|
MultiIdentifierLoadAccess<T> enableSessionCheck(boolean enabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a load of multiple entities by identifiers
|
||||||
|
*
|
||||||
|
* @param ids The ids to load
|
||||||
|
* @param <K> The identifier type
|
||||||
|
*
|
||||||
|
* @return The persistent entities.
|
||||||
|
*/
|
||||||
|
<K extends Serializable> List<T> multiLoad(K... ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a load of multiple entities by identifiers
|
||||||
|
*
|
||||||
|
* @param ids The ids to load
|
||||||
|
* @param <K> The identifier type
|
||||||
|
*
|
||||||
|
* @return The persistent entities.
|
||||||
|
*/
|
||||||
|
<K extends Serializable> List<T> multiLoad(List<K> ids);
|
||||||
|
}
|
|
@ -782,6 +782,30 @@ public interface Session extends SharedSessionContract, java.io.Closeable {
|
||||||
*/
|
*/
|
||||||
IdentifierLoadAccess byId(String entityName);
|
IdentifierLoadAccess byId(String entityName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link MultiIdentifierLoadAccess} instance to retrieve multiple entities at once
|
||||||
|
* as specified by primary key values.
|
||||||
|
*
|
||||||
|
* @param entityClass The entity type to be retrieved
|
||||||
|
*
|
||||||
|
* @return load delegate for loading the specified entity type by primary key values
|
||||||
|
*
|
||||||
|
* @throws HibernateException If the specified Class cannot be resolved as a mapped entity
|
||||||
|
*/
|
||||||
|
<T> MultiIdentifierLoadAccess<T> byMultipleIds(Class<T> entityClass);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link MultiIdentifierLoadAccess} instance to retrieve multiple entities at once
|
||||||
|
* as specified by primary key values.
|
||||||
|
*
|
||||||
|
* @param entityName The entity name of the entity type to be retrieved
|
||||||
|
*
|
||||||
|
* @return load delegate for loading the specified entity type by primary key values
|
||||||
|
*
|
||||||
|
* @throws HibernateException If the specified entity name cannot be resolved as an entity name
|
||||||
|
*/
|
||||||
|
MultiIdentifierLoadAccess byMultipleIds(String entityName);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an {@link IdentifierLoadAccess} instance to retrieve the specified entity by
|
* Create an {@link IdentifierLoadAccess} instance to retrieve the specified entity by
|
||||||
* primary key.
|
* primary key.
|
||||||
|
|
|
@ -77,6 +77,7 @@ import org.hibernate.internal.util.ReflectHelper;
|
||||||
import org.hibernate.internal.util.StringHelper;
|
import org.hibernate.internal.util.StringHelper;
|
||||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||||
import org.hibernate.internal.util.io.StreamCopier;
|
import org.hibernate.internal.util.io.StreamCopier;
|
||||||
|
import org.hibernate.loader.BatchLoadSizingStrategy;
|
||||||
import org.hibernate.mapping.Column;
|
import org.hibernate.mapping.Column;
|
||||||
import org.hibernate.mapping.Constraint;
|
import org.hibernate.mapping.Constraint;
|
||||||
import org.hibernate.mapping.ForeignKey;
|
import org.hibernate.mapping.ForeignKey;
|
||||||
|
@ -2813,4 +2814,15 @@ public abstract class Dialect implements ConversionContext {
|
||||||
public NameQualifierSupport getNameQualifierSupport() {
|
public NameQualifierSupport getNameQualifierSupport() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected final BatchLoadSizingStrategy STANDARD_DEFAULT_BATCH_LOAD_SIZING_STRATEGY = new BatchLoadSizingStrategy() {
|
||||||
|
@Override
|
||||||
|
public int determineOptimalBatchLoadSize(int numberOfKeyColumns, int numberOfKeys) {
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public BatchLoadSizingStrategy getDefaultBatchLoadSizingStrategy() {
|
||||||
|
return STANDARD_DEFAULT_BATCH_LOAD_SIZING_STRATEGY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.hibernate.Interceptor;
|
||||||
import org.hibernate.LobHelper;
|
import org.hibernate.LobHelper;
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
import org.hibernate.LockOptions;
|
import org.hibernate.LockOptions;
|
||||||
|
import org.hibernate.MultiIdentifierLoadAccess;
|
||||||
import org.hibernate.NaturalIdLoadAccess;
|
import org.hibernate.NaturalIdLoadAccess;
|
||||||
import org.hibernate.Query;
|
import org.hibernate.Query;
|
||||||
import org.hibernate.ReplicationMode;
|
import org.hibernate.ReplicationMode;
|
||||||
|
@ -662,6 +663,16 @@ public class SessionDelegatorBaseImpl implements SessionImplementor, Session {
|
||||||
return session.byId( entityName );
|
return session.byId( entityName );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> MultiIdentifierLoadAccess<T> byMultipleIds(Class<T> entityClass) {
|
||||||
|
return session.byMultipleIds( entityClass );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultiIdentifierLoadAccess byMultipleIds(String entityName) {
|
||||||
|
return session.byMultipleIds( entityName );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> IdentifierLoadAccess<T> byId(Class<T> entityClass) {
|
public <T> IdentifierLoadAccess<T> byId(Class<T> entityClass) {
|
||||||
return session.byId( entityClass );
|
return session.byId( entityClass );
|
||||||
|
|
|
@ -42,6 +42,7 @@ import org.hibernate.LobHelper;
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
import org.hibernate.LockOptions;
|
import org.hibernate.LockOptions;
|
||||||
import org.hibernate.MappingException;
|
import org.hibernate.MappingException;
|
||||||
|
import org.hibernate.MultiIdentifierLoadAccess;
|
||||||
import org.hibernate.NaturalIdLoadAccess;
|
import org.hibernate.NaturalIdLoadAccess;
|
||||||
import org.hibernate.ObjectDeletedException;
|
import org.hibernate.ObjectDeletedException;
|
||||||
import org.hibernate.ObjectNotFoundException;
|
import org.hibernate.ObjectNotFoundException;
|
||||||
|
@ -129,6 +130,7 @@ import org.hibernate.jdbc.WorkExecutorVisitable;
|
||||||
import org.hibernate.loader.criteria.CriteriaLoader;
|
import org.hibernate.loader.criteria.CriteriaLoader;
|
||||||
import org.hibernate.loader.custom.CustomLoader;
|
import org.hibernate.loader.custom.CustomLoader;
|
||||||
import org.hibernate.loader.custom.CustomQuery;
|
import org.hibernate.loader.custom.CustomQuery;
|
||||||
|
import org.hibernate.loader.entity.DynamicBatchingEntityLoaderBuilder;
|
||||||
import org.hibernate.persister.collection.CollectionPersister;
|
import org.hibernate.persister.collection.CollectionPersister;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
import org.hibernate.persister.entity.OuterJoinLoadable;
|
import org.hibernate.persister.entity.OuterJoinLoadable;
|
||||||
|
@ -1044,6 +1046,16 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
|
||||||
return new IdentifierLoadAccessImpl<T>( entityClass );
|
return new IdentifierLoadAccessImpl<T>( entityClass );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> MultiIdentifierLoadAccess<T> byMultipleIds(Class<T> entityClass) {
|
||||||
|
return new MultiIdentifierLoadAccessImpl<T>( locateEntityPersister( entityClass ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultiIdentifierLoadAccess byMultipleIds(String entityName) {
|
||||||
|
return new MultiIdentifierLoadAccessImpl( locateEntityPersister( entityName ) );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NaturalIdLoadAccess byNaturalId(String entityName) {
|
public NaturalIdLoadAccess byNaturalId(String entityName) {
|
||||||
return new NaturalIdLoadAccessImpl( entityName );
|
return new NaturalIdLoadAccessImpl( entityName );
|
||||||
|
@ -2577,6 +2589,7 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
|
||||||
private class IdentifierLoadAccessImpl<T> implements IdentifierLoadAccess<T> {
|
private class IdentifierLoadAccessImpl<T> implements IdentifierLoadAccess<T> {
|
||||||
private final EntityPersister entityPersister;
|
private final EntityPersister entityPersister;
|
||||||
private LockOptions lockOptions;
|
private LockOptions lockOptions;
|
||||||
|
private CacheMode cacheMode;
|
||||||
|
|
||||||
private IdentifierLoadAccessImpl(EntityPersister entityPersister) {
|
private IdentifierLoadAccessImpl(EntityPersister entityPersister) {
|
||||||
this.entityPersister = entityPersister;
|
this.entityPersister = entityPersister;
|
||||||
|
@ -2597,8 +2610,37 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
public IdentifierLoadAccess<T> with(CacheMode cacheMode) {
|
||||||
|
this.cacheMode = cacheMode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public final T getReference(Serializable id) {
|
public final T getReference(Serializable id) {
|
||||||
|
CacheMode sessionCacheMode = getCacheMode();
|
||||||
|
boolean cacheModeChanged = false;
|
||||||
|
if ( cacheMode != null ) {
|
||||||
|
// naive check for now...
|
||||||
|
// todo : account for "conceptually equal"
|
||||||
|
if ( cacheMode != sessionCacheMode ) {
|
||||||
|
setCacheMode( cacheMode );
|
||||||
|
cacheModeChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return doGetReference( id );
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if ( cacheModeChanged ) {
|
||||||
|
// change it back
|
||||||
|
setCacheMode( sessionCacheMode );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected T doGetReference(Serializable id) {
|
||||||
if ( this.lockOptions != null ) {
|
if ( this.lockOptions != null ) {
|
||||||
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, SessionImpl.this );
|
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, SessionImpl.this );
|
||||||
fireLoad( event, LoadEventListener.LOAD );
|
fireLoad( event, LoadEventListener.LOAD );
|
||||||
|
@ -2624,8 +2666,31 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public final T load(Serializable id) {
|
public final T load(Serializable id) {
|
||||||
|
CacheMode sessionCacheMode = getCacheMode();
|
||||||
|
boolean cacheModeChanged = false;
|
||||||
|
if ( cacheMode != null ) {
|
||||||
|
// naive check for now...
|
||||||
|
// todo : account for "conceptually equal"
|
||||||
|
if ( cacheMode != sessionCacheMode ) {
|
||||||
|
setCacheMode( cacheMode );
|
||||||
|
cacheModeChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return doLoad( id );
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if ( cacheModeChanged ) {
|
||||||
|
// change it back
|
||||||
|
setCacheMode( sessionCacheMode );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected final T doLoad(Serializable id) {
|
||||||
if ( this.lockOptions != null ) {
|
if ( this.lockOptions != null ) {
|
||||||
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, SessionImpl.this );
|
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, SessionImpl.this );
|
||||||
fireLoad( event, LoadEventListener.GET );
|
fireLoad( event, LoadEventListener.GET );
|
||||||
|
@ -2648,6 +2713,109 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class MultiIdentifierLoadAccessImpl<T> implements MultiIdentifierLoadAccess<T> {
|
||||||
|
private final EntityPersister entityPersister;
|
||||||
|
private LockOptions lockOptions;
|
||||||
|
private CacheMode cacheMode;
|
||||||
|
private Integer batchSize;
|
||||||
|
private boolean sessionCheckingEnabled;
|
||||||
|
|
||||||
|
public MultiIdentifierLoadAccessImpl(EntityPersister entityPersister) {
|
||||||
|
this.entityPersister = entityPersister;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final MultiIdentifierLoadAccessImpl<T> with(LockOptions lockOptions) {
|
||||||
|
this.lockOptions = lockOptions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultiIdentifierLoadAccessImpl<T> with(CacheMode cacheMode) {
|
||||||
|
this.cacheMode = cacheMode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultiIdentifierLoadAccess<T> withBatchSize(int batchSize) {
|
||||||
|
if ( batchSize < 1 ) {
|
||||||
|
this.batchSize = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.batchSize = batchSize;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MultiIdentifierLoadAccess<T> enableSessionCheck(boolean enabled) {
|
||||||
|
this.sessionCheckingEnabled = enabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <K extends Serializable> List<T> multiLoad(K... ids) {
|
||||||
|
CacheMode sessionCacheMode = getCacheMode();
|
||||||
|
boolean cacheModeChanged = false;
|
||||||
|
if ( cacheMode != null ) {
|
||||||
|
// naive check for now...
|
||||||
|
// todo : account for "conceptually equal"
|
||||||
|
if ( cacheMode != sessionCacheMode ) {
|
||||||
|
setCacheMode( cacheMode );
|
||||||
|
cacheModeChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return DynamicBatchingEntityLoaderBuilder.INSTANCE.multiLoad(
|
||||||
|
(OuterJoinLoadable) entityPersister,
|
||||||
|
ids,
|
||||||
|
lockOptions,
|
||||||
|
batchSize,
|
||||||
|
sessionCheckingEnabled,
|
||||||
|
SessionImpl.this
|
||||||
|
);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if ( cacheModeChanged ) {
|
||||||
|
// change it back
|
||||||
|
setCacheMode( sessionCacheMode );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <K extends Serializable> List<T> multiLoad(List<K> ids) {
|
||||||
|
CacheMode sessionCacheMode = getCacheMode();
|
||||||
|
boolean cacheModeChanged = false;
|
||||||
|
if ( cacheMode != null ) {
|
||||||
|
// naive check for now...
|
||||||
|
// todo : account for "conceptually equal"
|
||||||
|
if ( cacheMode != sessionCacheMode ) {
|
||||||
|
setCacheMode( cacheMode );
|
||||||
|
cacheModeChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return DynamicBatchingEntityLoaderBuilder.INSTANCE.multiLoad(
|
||||||
|
(OuterJoinLoadable) entityPersister,
|
||||||
|
ids.toArray( new Serializable[ ids.size() ] ),
|
||||||
|
lockOptions,
|
||||||
|
batchSize,
|
||||||
|
sessionCheckingEnabled,
|
||||||
|
SessionImpl.this
|
||||||
|
);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if ( cacheModeChanged ) {
|
||||||
|
// change it back
|
||||||
|
setCacheMode( sessionCacheMode );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private EntityPersister locateEntityPersister(Class entityClass) {
|
private EntityPersister locateEntityPersister(Class entityClass) {
|
||||||
return factory.locateEntityPersister( entityClass );
|
return factory.locateEntityPersister( entityClass );
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strategy (pluggable) for determining an optimal size for batch loads.
|
||||||
|
*
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public interface BatchLoadSizingStrategy {
|
||||||
|
int determineOptimalBatchLoadSize(int numberOfKeyColumns, int numberOfKeys);
|
||||||
|
}
|
|
@ -7,15 +7,18 @@
|
||||||
package org.hibernate.loader.entity;
|
package org.hibernate.loader.entity;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
import org.hibernate.LockOptions;
|
import org.hibernate.LockOptions;
|
||||||
import org.hibernate.dialect.pagination.LimitHelper;
|
import org.hibernate.dialect.pagination.LimitHelper;
|
||||||
|
import org.hibernate.engine.spi.EntityKey;
|
||||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
import org.hibernate.engine.spi.QueryParameters;
|
import org.hibernate.engine.spi.QueryParameters;
|
||||||
|
@ -24,9 +27,11 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SessionImplementor;
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
import org.hibernate.internal.util.StringHelper;
|
import org.hibernate.internal.util.StringHelper;
|
||||||
import org.hibernate.internal.util.collections.ArrayHelper;
|
import org.hibernate.internal.util.collections.ArrayHelper;
|
||||||
|
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||||
import org.hibernate.loader.spi.AfterLoadAction;
|
import org.hibernate.loader.spi.AfterLoadAction;
|
||||||
import org.hibernate.persister.entity.OuterJoinLoadable;
|
import org.hibernate.persister.entity.OuterJoinLoadable;
|
||||||
import org.hibernate.pretty.MessageHelper;
|
import org.hibernate.pretty.MessageHelper;
|
||||||
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
@ -41,6 +46,114 @@ public class DynamicBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuil
|
||||||
|
|
||||||
public static final DynamicBatchingEntityLoaderBuilder INSTANCE = new DynamicBatchingEntityLoaderBuilder();
|
public static final DynamicBatchingEntityLoaderBuilder INSTANCE = new DynamicBatchingEntityLoaderBuilder();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T, K extends Serializable> List<T> multiLoad(
|
||||||
|
OuterJoinLoadable persister,
|
||||||
|
K[] ids,
|
||||||
|
LockOptions lockOptions,
|
||||||
|
Integer explicitBatchSize,
|
||||||
|
boolean sessionCheckingEnabled,
|
||||||
|
SessionImplementor session) {
|
||||||
|
List<T> result = CollectionHelper.arrayList( ids.length );
|
||||||
|
|
||||||
|
if ( sessionCheckingEnabled ) {
|
||||||
|
// 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<K> nonManagedIds = new ArrayList<K>();
|
||||||
|
for ( K id : ids ) {
|
||||||
|
final EntityKey entityKey = new EntityKey( id, persister );
|
||||||
|
final T managedEntity = (T) session.getPersistenceContext().getEntity( 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(
|
||||||
|
(K[]) Array.newInstance(
|
||||||
|
ids.getClass().getComponentType(),
|
||||||
|
nonManagedIds.size()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ( lockOptions == null ) {
|
||||||
|
lockOptions = new LockOptions( LockMode.NONE );
|
||||||
|
}
|
||||||
|
|
||||||
|
int numberOfIdsLeft = ids.length;
|
||||||
|
|
||||||
|
final int maxBatchSize;
|
||||||
|
if ( explicitBatchSize != null && explicitBatchSize > 0 ) {
|
||||||
|
maxBatchSize = explicitBatchSize;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
maxBatchSize = session.getFactory().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
|
@Override
|
||||||
protected UniqueEntityLoader buildBatchingLoader(
|
protected UniqueEntityLoader buildBatchingLoader(
|
||||||
OuterJoinLoadable persister,
|
OuterJoinLoadable persister,
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
* 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 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.boot.MetadataBuilder;
|
||||||
|
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||||
|
import org.hibernate.cache.spi.access.AccessType;
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
|
||||||
|
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class MultiLoadTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
@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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
@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() {
|
||||||
|
Session session = openSession();
|
||||||
|
session.getTransaction().begin();
|
||||||
|
List<SimpleEntity> list = session.byMultipleIds( SimpleEntity.class ).multiLoad( ids(56) );
|
||||||
|
assertEquals( 56, list.size() );
|
||||||
|
session.getTransaction().commit();
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
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() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue