HHH-7746 - Investigate alternative batch loading algorithms

This commit is contained in:
Steve Ebersole 2012-11-02 13:27:54 -05:00
parent d41545e13b
commit 06b0faaf57
30 changed files with 1742 additions and 244 deletions

View File

@ -579,4 +579,11 @@ public interface AvailableSettings {
public static final String ENABLE_LAZY_LOAD_NO_TRANS = "hibernate.enable_lazy_load_no_trans";
public static final String HQL_BULK_ID_STRATEGY = "hibernate.hql.bulk_id_strategy";
/**
* Names the {@link org.hibernate.loader.BatchFetchStyle} to use. Can specify either the
* {@link org.hibernate.loader.BatchFetchStyle} name (insensitively), or a
* {@link org.hibernate.loader.BatchFetchStyle} instance.
*/
public static final String BATCH_FETCH_STYLE = "hibernate.batch_fetch_style";
}

View File

@ -33,6 +33,7 @@ import org.hibernate.cache.spi.RegionFactory;
import org.hibernate.hql.spi.MultiTableBulkIdStrategy;
import org.hibernate.hql.spi.QueryTranslatorFactory;
import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
import org.hibernate.loader.BatchFetchStyle;
import org.hibernate.tuple.entity.EntityTuplizerFactory;
/**
@ -91,6 +92,7 @@ public final class Settings {
private JtaPlatform jtaPlatform;
private MultiTableBulkIdStrategy multiTableBulkIdStrategy;
private BatchFetchStyle batchFetchStyle;
/**
@ -480,4 +482,12 @@ public final class Settings {
void setMultiTableBulkIdStrategy(MultiTableBulkIdStrategy multiTableBulkIdStrategy) {
this.multiTableBulkIdStrategy = multiTableBulkIdStrategy;
}
public BatchFetchStyle getBatchFetchStyle() {
return batchFetchStyle;
}
void setBatchFetchStyle(BatchFetchStyle batchFetchStyle) {
this.batchFetchStyle = batchFetchStyle;
}
}

View File

@ -49,6 +49,7 @@ import org.hibernate.hql.spi.TemporaryTableBulkIdStrategy;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.loader.BatchFetchStyle;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
@ -199,6 +200,11 @@ public class SettingsFactory implements Serializable {
}
settings.setConnectionReleaseMode( releaseMode );
final BatchFetchStyle batchFetchStyle = BatchFetchStyle.interpret( properties.get( AvailableSettings.BATCH_FETCH_STYLE ) );
LOG.debugf( "Using BatchFetchStyle : " + batchFetchStyle.name() );
settings.setBatchFetchStyle( batchFetchStyle );
//SQL Generation settings:
String defaultSchema = properties.getProperty( AvailableSettings.DEFAULT_SCHEMA );

View File

@ -24,6 +24,7 @@
*/
package org.hibernate.internal.util;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
@ -66,6 +67,17 @@ public final class StringHelper {
return buf.toString();
}
public static String joinWithQualifier(String[] values, String qualifier, String deliminator) {
int length = values.length;
if ( length == 0 ) return "";
StringBuilder buf = new StringBuilder( length * values[0].length() )
.append( qualify( qualifier, values[0] ) );
for ( int i = 1; i < length; i++ ) {
buf.append( deliminator ).append( qualify( qualifier, values[i] ) );
}
return buf.toString();
}
public static String join(String seperator, Iterator objects) {
StringBuilder buf = new StringBuilder();
if ( objects.hasNext() ) buf.append( objects.next() );
@ -89,6 +101,15 @@ public final class StringHelper {
return buf.toString();
}
public static String repeat(String string, int times, String deliminator) {
StringBuilder buf = new StringBuilder( ( string.length() * times ) + ( deliminator.length() * (times-1) ) )
.append( string );
for ( int i = 1; i < times; i++ ) {
buf.append( deliminator ).append( string );
}
return buf.toString();
}
public static String repeat(char character, int times) {
char[] buffer = new char[times];
Arrays.fill( buffer, character );
@ -661,4 +682,69 @@ public final class StringHelper {
}
return unquoted;
}
public static final String BATCH_ID_PLACEHOLDER = "$$BATCH_ID_PLACEHOLDER$$";
public static StringBuilder buildBatchFetchRestrictionFragment(
String alias,
String[] columnNames,
Dialect dialect) {
// the general idea here is to just insert a placeholder that we can easily find later...
if ( columnNames.length == 1 ) {
// non-composite key
return new StringBuilder( StringHelper.qualify( alias, columnNames[0] ) )
.append( " in (" ).append( BATCH_ID_PLACEHOLDER ).append( ")" );
}
else {
// composite key - the form to use here depends on what the dialect supports.
if ( dialect.supportsRowValueConstructorSyntaxInInList() ) {
// use : (col1, col2) in ( (?,?), (?,?), ... )
StringBuilder builder = new StringBuilder();
builder.append( "(" );
boolean firstPass = true;
String deliminator = "";
for ( String columnName : columnNames ) {
builder.append( deliminator ).append( StringHelper.qualify( alias, columnName ) );
if ( firstPass ) {
firstPass = false;
deliminator = ",";
}
}
builder.append( ") in (" );
builder.append( BATCH_ID_PLACEHOLDER );
builder.append( ")" );
return builder;
}
else {
// use : ( (col1 = ? and col2 = ?) or (col1 = ? and col2 = ?) or ... )
// unfortunately most of this building needs to be held off until we know
// the exact number of ids :(
return new StringBuilder( "(" ).append( BATCH_ID_PLACEHOLDER ).append( ")" );
}
}
}
public static String expandBatchIdPlaceholder(
String sql,
Serializable[] ids,
String alias,
String[] keyColumnNames,
Dialect dialect) {
if ( keyColumnNames.length == 1 ) {
// non-composite
return StringHelper.replace( sql, BATCH_ID_PLACEHOLDER, repeat( "?", ids.length, "," ) );
}
else {
// composite
if ( dialect.supportsRowValueConstructorSyntaxInInList() ) {
final String tuple = "(" + StringHelper.repeat( "?", keyColumnNames.length, "," );
return StringHelper.replace( sql, BATCH_ID_PLACEHOLDER, repeat( tuple, ids.length, "," ) );
}
else {
final String keyCheck = joinWithQualifier( keyColumnNames, alias, " and " );
return replace( sql, BATCH_ID_PLACEHOLDER, repeat( keyCheck, ids.length, " or " ) );
}
}
}
}

View File

@ -24,6 +24,7 @@
*/
package org.hibernate.internal.util.collections;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
@ -31,6 +32,7 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.type.Type;
@ -372,10 +374,43 @@ public final class ArrayHelper {
}
return true;
}
public static Serializable[] extractNonNull(Serializable[] array) {
final int nonNullCount = countNonNull( array );
final Serializable[] result = new Serializable[nonNullCount];
int i = 0;
for ( Serializable element : array ) {
if ( element != null ) {
result[i++] = element;
}
}
if ( i != nonNullCount ) {
throw new HibernateException( "Number of non-null elements varied between iterations" );
}
return result;
}
public static int countNonNull(Serializable[] array) {
int i = 0;
for ( Serializable element : array ) {
if ( element != null ) {
i++;
}
}
return i;
}
public static void main(String... args) {
int[] batchSizes = ArrayHelper.getBatchSizes( 32 );
System.out.println( "Forward ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" );
for ( int i = 0; i < batchSizes.length; i++ ) {
System.out.println( "[" + i + "] -> " + batchSizes[i] );
}
System.out.println( "Backward ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" );
for ( int i = batchSizes.length-1; i >= 0; i-- ) {
System.out.println( "[" + i + "] -> " + batchSizes[i] );
}
}
}

View File

@ -187,10 +187,7 @@ public abstract class AbstractEntityJoinWalker extends JoinWalker {
public abstract String getComment();
@Override
protected boolean isDuplicateAssociation(
final String foreignKeyTable,
final String[] foreignKeyColumns
) {
protected boolean isDuplicateAssociation(final String foreignKeyTable, final String[] foreignKeyColumns) {
//disable a join back to this same association
final boolean isSameJoin =
persister.getTableName().equals( foreignKeyTable ) &&
@ -201,11 +198,11 @@ public abstract class AbstractEntityJoinWalker extends JoinWalker {
protected final Loadable getPersister() {
public final Loadable getPersister() {
return persister;
}
protected final String getAlias() {
public final String getAlias() {
return alias;
}

View File

@ -0,0 +1,90 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.loader;
import org.jboss.logging.Logger;
/**
* Defines the style that should be used to perform batch loading. Which style to use is declared using
* the "{@value org.hibernate.cfg.AvailableSettings#BATCH_FETCH_STYLE}"
* ({@link org.hibernate.cfg.AvailableSettings#BATCH_FETCH_STYLE}) setting
*
* @author Steve Ebersole
*/
public enum BatchFetchStyle {
/**
* The legacy algorithm where we keep a set of pre-built batch sizes based on
* {@link org.hibernate.internal.util.collections.ArrayHelper#getBatchSizes}. Batches are performed
* using the next-smaller pre-built batch size from the number of existing batchable identifiers.
* <p/>
* For example, with a batch-size setting of 32 the pre-built batch sizes would be [32, 16, 10, 9, 8, 7, .., 1].
* An attempt to batch load 31 identifiers would result in batches of 16, 10, and 5.
*/
LEGACY,
/**
* Still keeps the concept of pre-built batch sizes, but uses the next-bigger batch size and pads the extra
* identifier placeholders.
* <p/>
* Using the same example of a batch-size setting of 32 the pre-built batch sizes would be the same. However, the
* attempt to batch load 31 identifiers would result just a single batch of size 32. The identifiers to load would
* be "padded" (aka, repeated) to make up the difference.
*/
PADDED,
/**
* Dynamically builds its SQL based on the actual number of available ids. Does still limit to the batch-size
* defined on the entity/collection
*/
DYNAMIC;
private static final Logger log = Logger.getLogger( BatchFetchStyle.class );
public static BatchFetchStyle byName(String name) {
return valueOf( name.toUpperCase() );
}
public static BatchFetchStyle interpret(Object setting) {
log.tracef( "Interpreting BatchFetchStyle from setting : %s", setting );
if ( setting == null ) {
return LEGACY; // as default
}
if ( BatchFetchStyle.class.isInstance( setting ) ) {
return (BatchFetchStyle) setting;
}
try {
final BatchFetchStyle byName = byName( setting.toString() );
if ( byName != null ) {
return byName;
}
}
catch (Exception ignore) {
}
log.debugf( "Unable to interpret given setting [%s] as BatchFetchStyle", setting );
return LEGACY; // again as default.
}
}

View File

@ -119,7 +119,7 @@ public abstract class Loader {
*
* @return The sql command this loader should use to get its {@link ResultSet}.
*/
protected abstract String getSQLString();
public abstract String getSQLString();
/**
* An array of persisters of entity classes contained in each row of results;
@ -256,7 +256,7 @@ public abstract class Loader {
* persister from each row of the <tt>ResultSet</tt>. If an object is supplied, will attempt to
* initialize that object. If a collection is supplied, attempt to initialize that collection.
*/
private List doQueryAndInitializeNonLazyCollections(
public List doQueryAndInitializeNonLazyCollections(
final SessionImplementor session,
final QueryParameters queryParameters,
final boolean returnProxies) throws HibernateException, SQLException {
@ -268,7 +268,7 @@ public abstract class Loader {
);
}
private List doQueryAndInitializeNonLazyCollections(
public List doQueryAndInitializeNonLazyCollections(
final SessionImplementor session,
final QueryParameters queryParameters,
final boolean returnProxies,
@ -1722,8 +1722,17 @@ public abstract class Loader {
final QueryParameters queryParameters,
final boolean scroll,
final SessionImplementor session) throws SQLException {
return executeQueryStatement( getSQLString(), queryParameters, scroll, session );
}
protected ResultSet executeQueryStatement(
final String sqlStatement,
final QueryParameters queryParameters,
final boolean scroll,
final SessionImplementor session) throws SQLException {
// Processing query filters.
queryParameters.processFilters( getSQLString(), session );
queryParameters.processFilters( sqlStatement, session );
// Applying LIMIT clause.
final LimitHandler limitHandler = getLimitHandler(

View File

@ -75,7 +75,8 @@ public abstract class OuterJoinLoader extends BasicLoader {
return collectionSuffixes;
}
protected final String getSQLString() {
@Override
public final String getSQLString() {
return sql;
}

View File

@ -1,10 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* Copyright (c) 2008, 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,106 +20,35 @@
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package org.hibernate.loader.collection;
import java.io.Serializable;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.Loader;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.QueryableCollection;
/**
* "Batch" loads collections, using multiple foreign key values in the
* SQL <tt>where</tt> clause.
* The base contract for loaders capable of performing batch-fetch loading of collections using multiple foreign key
* values in the SQL <tt>WHERE</tt> clause.
*
* @author Gavin King
* @author Steve Ebersole
*
* @see BatchingCollectionInitializerBuilder
* @see BasicCollectionLoader
* @see OneToManyLoader
* @author Gavin King
*/
public class BatchingCollectionInitializer implements CollectionInitializer {
private final Loader[] loaders;
private final int[] batchSizes;
private final CollectionPersister collectionPersister;
public abstract class BatchingCollectionInitializer implements CollectionInitializer {
private final QueryableCollection collectionPersister;
public BatchingCollectionInitializer(CollectionPersister collPersister, int[] batchSizes, Loader[] loaders) {
this.loaders = loaders;
this.batchSizes = batchSizes;
this.collectionPersister = collPersister;
public BatchingCollectionInitializer(QueryableCollection collectionPersister) {
this.collectionPersister = collectionPersister;
}
public CollectionPersister getCollectionPersister() {
return collectionPersister;
}
public Loader[] getLoaders() {
return loaders;
public QueryableCollection collectionPersister() {
return collectionPersister;
}
public int[] getBatchSizes() {
return batchSizes;
}
public void initialize(Serializable id, SessionImplementor session)
throws HibernateException {
Serializable[] batch = session.getPersistenceContext().getBatchFetchQueue()
.getCollectionBatch( collectionPersister, id, batchSizes[0] );
for ( int i=0; i<batchSizes.length-1; i++) {
final int smallBatchSize = batchSizes[i];
if ( batch[smallBatchSize-1]!=null ) {
Serializable[] smallBatch = new Serializable[smallBatchSize];
System.arraycopy(batch, 0, smallBatch, 0, smallBatchSize);
loaders[i].loadCollectionBatch( session, smallBatch, collectionPersister.getKeyType() );
return; //EARLY EXIT!
}
}
loaders[batchSizes.length-1].loadCollection( session, id, collectionPersister.getKeyType() );
}
public static CollectionInitializer createBatchingOneToManyInitializer(
final QueryableCollection persister,
final int maxBatchSize,
final SessionFactoryImplementor factory,
final LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
if ( maxBatchSize > 1 ) {
int[] batchSizesToCreate = ArrayHelper.getBatchSizes(maxBatchSize);
Loader[] loadersToCreate = new Loader[ batchSizesToCreate.length ];
for ( int i=0; i<batchSizesToCreate.length; i++ ) {
loadersToCreate[i] = new OneToManyLoader( persister, batchSizesToCreate[i], factory, loadQueryInfluencers );
}
return new BatchingCollectionInitializer( persister, batchSizesToCreate, loadersToCreate );
}
else {
return new OneToManyLoader( persister, factory, loadQueryInfluencers );
}
}
public static CollectionInitializer createBatchingCollectionInitializer(
final QueryableCollection persister,
final int maxBatchSize,
final SessionFactoryImplementor factory,
final LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
if ( maxBatchSize > 1 ) {
int[] batchSizesToCreate = ArrayHelper.getBatchSizes( maxBatchSize );
Loader[] loadersToCreate = new Loader[ batchSizesToCreate.length ];
for ( int i=0; i<batchSizesToCreate.length; i++ ) {
loadersToCreate[i] = new BasicCollectionLoader( persister, batchSizesToCreate[i], factory, loadQueryInfluencers );
}
return new BatchingCollectionInitializer(persister, batchSizesToCreate, loadersToCreate);
}
else {
return new BasicCollectionLoader( persister, factory, loadQueryInfluencers );
}
}
}

View File

@ -0,0 +1,112 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.loader.collection;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.persister.collection.QueryableCollection;
/**
* Contract for building {@link CollectionInitializer} instances capable of performing batch-fetch loading.
*
* @author Steve Ebersole
*
* @see org.hibernate.loader.BatchFetchStyle
*/
public abstract class BatchingCollectionInitializerBuilder {
public static BatchingCollectionInitializerBuilder getBuilder(SessionFactoryImplementor factory) {
switch ( factory.getSettings().getBatchFetchStyle() ) {
case PADDED: {
return PaddedBatchingCollectionInitializerBuilder.INSTANCE;
}
case DYNAMIC: {
return DynamicBatchingCollectionInitializerBuilder.INSTANCE;
}
default: {
return LegacyBatchingCollectionInitializerBuilder.INSTANCE;
}
}
}
/**
* Builds a batch-fetch capable CollectionInitializer for basic and many-to-many collections (collections with
* a dedicated collection table).
*
* @param persister THe collection persister
* @param maxBatchSize The maximum number of keys to batch-fetch together
* @param factory The SessionFactory
* @param influencers Any influencers that should affect the built query
*
* @return The batch-fetch capable collection initializer
*/
public CollectionInitializer createBatchingCollectionInitializer(
QueryableCollection persister,
int maxBatchSize,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
if ( maxBatchSize <= 1 ) {
// no batching
return new BasicCollectionLoader( persister, factory, influencers );
}
return createRealBatchingCollectionInitializer( persister, maxBatchSize, factory, influencers );
}
protected abstract CollectionInitializer createRealBatchingCollectionInitializer(
QueryableCollection persister,
int maxBatchSize,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers);
/**
* Builds a batch-fetch capable CollectionInitializer for one-to-many collections (collections without
* a dedicated collection table).
*
* @param persister THe collection persister
* @param maxBatchSize The maximum number of keys to batch-fetch together
* @param factory The SessionFactory
* @param influencers Any influencers that should affect the built query
*
* @return The batch-fetch capable collection initializer
*/
public CollectionInitializer createBatchingOneToManyInitializer(
QueryableCollection persister,
int maxBatchSize,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
if ( maxBatchSize <= 1 ) {
// no batching
return new OneToManyLoader( persister, factory, influencers );
}
return createRealBatchingOneToManyInitializer( persister, maxBatchSize, factory, influencers );
}
protected abstract CollectionInitializer createRealBatchingOneToManyInitializer(
QueryableCollection persister,
int maxBatchSize,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers);
}

View File

@ -44,7 +44,7 @@ public abstract class CollectionJoinWalker extends JoinWalker {
protected StringBuilder whereString(String alias, String[] columnNames, String subselect, int batchSize) {
if (subselect==null) {
return super.whereString(alias, columnNames, batchSize);
return whereString(alias, columnNames, batchSize);
}
else {
StringBuilder buf = new StringBuilder();

View File

@ -52,6 +52,10 @@ public class CollectionLoader extends OuterJoinLoader implements CollectionIniti
this.collectionPersister = collectionPersister;
}
protected QueryableCollection collectionPersister() {
return collectionPersister;
}
protected boolean isSubselectLoadingEnabled() {
return hasSubselectLoadableCollections();
}

View File

@ -0,0 +1,265 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.loader.collection;
import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import org.hibernate.HibernateException;
import org.hibernate.dialect.pagination.LimitHelper;
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.SessionImplementor;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.JoinWalker;
import org.hibernate.loader.Loader;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.type.Type;
/**
* A BatchingCollectionInitializerBuilder that builds CollectionInitializer instances capable of dynamically building
* its batch-fetch SQL based on the actual number of collections keys waiting to be fetched.
*
* @author Steve Ebersole
*/
public class DynamicBatchingCollectionInitializerBuilder extends BatchingCollectionInitializerBuilder {
public static final DynamicBatchingCollectionInitializerBuilder INSTANCE = new DynamicBatchingCollectionInitializerBuilder();
@Override
protected CollectionInitializer createRealBatchingCollectionInitializer(
QueryableCollection persister,
int maxBatchSize,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return new DynamicBatchingCollectionInitializer( persister, maxBatchSize, factory, influencers );
}
@Override
protected CollectionInitializer createRealBatchingOneToManyInitializer(
QueryableCollection persister,
int maxBatchSize,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return new DynamicBatchingCollectionInitializer( persister, maxBatchSize, factory, influencers );
}
public static class DynamicBatchingCollectionInitializer extends BatchingCollectionInitializer {
private final int maxBatchSize;
private final Loader singleKeyLoader;
private final DynamicBatchingCollectionLoader batchLoader;
public DynamicBatchingCollectionInitializer(
QueryableCollection collectionPersister,
int maxBatchSize,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
super( collectionPersister );
this.maxBatchSize = maxBatchSize;
if ( collectionPersister.isOneToMany() ) {
this.singleKeyLoader = new OneToManyLoader( collectionPersister, 1, factory, influencers );
}
else {
this.singleKeyLoader = new BasicCollectionLoader( collectionPersister, 1, factory, influencers );
}
this.batchLoader = new DynamicBatchingCollectionLoader( collectionPersister, factory, influencers );
}
@Override
public void initialize(Serializable id, SessionImplementor session) throws HibernateException {
// first, figure out how many batchable ids we have...
final Serializable[] batch = session.getPersistenceContext()
.getBatchFetchQueue()
.getCollectionBatch( collectionPersister(), id, maxBatchSize );
final int numberOfIds = ArrayHelper.countNonNull( batch );
if ( numberOfIds <= 1 ) {
singleKeyLoader.loadCollection( session, id, collectionPersister().getKeyType() );
return;
}
final Serializable[] idsToLoad = new Serializable[numberOfIds];
System.arraycopy( batch, 0, idsToLoad, 0, numberOfIds );
batchLoader.doBatchedCollectionLoad( session, idsToLoad, collectionPersister().getKeyType() );
}
}
private static class DynamicBatchingCollectionLoader extends CollectionLoader {
// todo : this represents another case where the current Loader contract is unhelpful
// the other recent case was stored procedure support. Really any place where the SQL
// generation is dynamic but the "loading plan" remains constant. The long term plan
// is to split Loader into (a) PreparedStatement generation/execution and (b) ResultSet
// processing.
//
// Same holds true for org.hibernate.loader.entity.DynamicBatchingEntityLoaderBuilder.DynamicBatchingEntityLoader
//
// for now I will essentially semi-re-implement the collection loader contract here to be able to alter
// the SQL (specifically to be able to dynamically build the WHERE-clause IN-condition) later, when
// we actually know the ids to batch fetch
private final String sqlTemplate;
private final String alias;
public DynamicBatchingCollectionLoader(
QueryableCollection collectionPersister,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
super( collectionPersister, factory, influencers );
JoinWalker walker = buildJoinWalker( collectionPersister, factory, influencers );
initFromWalker( walker );
this.sqlTemplate = walker.getSQLString();
this.alias = StringHelper.generateAlias( collectionPersister.getRole(), 0 );
postInstantiate();
if ( LOG.isDebugEnabled() ) {
LOG.debugf(
"SQL-template for dynamic collection [%s] batch-fetching : %s",
collectionPersister.getRole(),
sqlTemplate
);
}
}
private JoinWalker buildJoinWalker(
QueryableCollection collectionPersister,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
if ( collectionPersister.isOneToMany() ) {
return new OneToManyJoinWalker( collectionPersister, -1, null, factory, influencers ) {
@Override
protected StringBuilder whereString(String alias, String[] columnNames, String subselect, int batchSize) {
if ( subselect != null ) {
return super.whereString( alias, columnNames, subselect, batchSize );
}
return StringHelper.buildBatchFetchRestrictionFragment( alias, columnNames, getFactory().getDialect() );
}
};
}
else {
return new BasicCollectionJoinWalker( collectionPersister, -1, null, factory, influencers ) {
@Override
protected StringBuilder whereString(String alias, String[] columnNames, String subselect, int batchSize) {
if ( subselect != null ) {
return super.whereString( alias, columnNames, subselect, batchSize );
}
return StringHelper.buildBatchFetchRestrictionFragment( alias, columnNames, getFactory().getDialect() );
}
};
}
}
public final void doBatchedCollectionLoad(
final SessionImplementor session,
final Serializable[] ids,
final Type type) throws HibernateException {
if ( LOG.isDebugEnabled() )
LOG.debugf( "Batch loading collection: %s",
MessageHelper.collectionInfoString( getCollectionPersisters()[0], ids, getFactory() ) );
final Type[] idTypes = new Type[ids.length];
Arrays.fill( idTypes, type );
final QueryParameters queryParameters = new QueryParameters( idTypes, ids, ids );
final String sql = StringHelper.expandBatchIdPlaceholder(
sqlTemplate,
ids,
alias,
collectionPersister().getKeyColumnNames(),
getFactory().getDialect()
);
try {
final PersistenceContext persistenceContext = session.getPersistenceContext();
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();
try {
try {
doTheLoad( sql, queryParameters, session );
}
finally {
persistenceContext.afterLoad();
}
persistenceContext.initializeNonLazyCollections();
}
finally {
// Restore the original default
persistenceContext.setDefaultReadOnly( defaultReadOnlyOrig );
}
}
catch ( SQLException e ) {
throw getFactory().getSQLExceptionHelper().convert(
e,
"could not initialize a collection batch: " +
MessageHelper.collectionInfoString( collectionPersister(), ids, getFactory() ),
sql
);
}
LOG.debug( "Done batch load" );
}
private void doTheLoad(String sql, QueryParameters queryParameters, SessionImplementor session) throws SQLException {
final RowSelection selection = queryParameters.getRowSelection();
final int maxRows = LimitHelper.hasMaxRows( selection ) ?
selection.getMaxRows() :
Integer.MAX_VALUE;
final ResultSet rs = executeQueryStatement( sql, queryParameters, false, session );
final Statement st = rs.getStatement();
try {
processResultSet( rs, queryParameters, session, true, null, maxRows );
}
finally {
st.close();
}
}
}
}

View File

@ -0,0 +1,103 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.loader.collection;
import java.io.Serializable;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.Loader;
import org.hibernate.persister.collection.QueryableCollection;
/**
* @author Steve Ebersole
*/
public class LegacyBatchingCollectionInitializerBuilder extends BatchingCollectionInitializerBuilder {
public static final LegacyBatchingCollectionInitializerBuilder INSTANCE = new LegacyBatchingCollectionInitializerBuilder();
@Override
public CollectionInitializer createRealBatchingCollectionInitializer(
QueryableCollection persister,
int maxBatchSize,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
int[] batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
Loader[] loaders = new Loader[ batchSizes.length ];
for ( int i = 0; i < batchSizes.length; i++ ) {
loaders[i] = new BasicCollectionLoader( persister, batchSizes[i], factory, loadQueryInfluencers );
}
return new LegacyBatchingCollectionInitializer( persister, batchSizes, loaders );
}
@Override
public CollectionInitializer createRealBatchingOneToManyInitializer(
QueryableCollection persister,
int maxBatchSize,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
final int[] batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
final Loader[] loaders = new Loader[ batchSizes.length ];
for ( int i = 0; i < batchSizes.length; i++ ) {
loaders[i] = new OneToManyLoader( persister, batchSizes[i], factory, loadQueryInfluencers );
}
return new LegacyBatchingCollectionInitializer( persister, batchSizes, loaders );
}
public static class LegacyBatchingCollectionInitializer extends BatchingCollectionInitializer {
private final int[] batchSizes;
private final Loader[] loaders;
public LegacyBatchingCollectionInitializer(
QueryableCollection persister,
int[] batchSizes,
Loader[] loaders) {
super( persister );
this.batchSizes = batchSizes;
this.loaders = loaders;
}
@Override
public void initialize(Serializable id, SessionImplementor session) throws HibernateException {
Serializable[] batch = session.getPersistenceContext().getBatchFetchQueue()
.getCollectionBatch( collectionPersister(), id, batchSizes[0] );
for ( int i=0; i<batchSizes.length-1; i++) {
final int smallBatchSize = batchSizes[i];
if ( batch[smallBatchSize-1]!=null ) {
Serializable[] smallBatch = new Serializable[smallBatchSize];
System.arraycopy(batch, 0, smallBatch, 0, smallBatchSize);
loaders[i].loadCollectionBatch( session, smallBatch, collectionPersister().getKeyType() );
return; //EARLY EXIT!
}
}
loaders[batchSizes.length-1].loadCollection( session, id, collectionPersister().getKeyType() );
}
}
}

View File

@ -0,0 +1,119 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.loader.collection;
import java.io.Serializable;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.Loader;
import org.hibernate.persister.collection.QueryableCollection;
/**
* A batch-fetch capable CollectionInitializer that performs batch-fetching using the padded style. See
* {@link org.hibernate.loader.BatchFetchStyle} for a discussion of the different styles.
*
* @author Steve Ebersole
*
* @see org.hibernate.loader.BatchFetchStyle#PADDED
*/
public class PaddedBatchingCollectionInitializerBuilder extends BatchingCollectionInitializerBuilder {
public static final PaddedBatchingCollectionInitializerBuilder INSTANCE = new PaddedBatchingCollectionInitializerBuilder();
@Override
public CollectionInitializer createRealBatchingCollectionInitializer(
QueryableCollection persister,
int maxBatchSize,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
int[] batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
Loader[] loaders = new Loader[ batchSizes.length ];
for ( int i = 0; i < batchSizes.length; i++ ) {
loaders[i] = new BasicCollectionLoader( persister, batchSizes[i], factory, loadQueryInfluencers );
}
return new PaddedBatchingCollectionInitializer( persister, batchSizes, loaders );
}
@Override
public CollectionInitializer createRealBatchingOneToManyInitializer(
QueryableCollection persister,
int maxBatchSize,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
final int[] batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
final Loader[] loaders = new Loader[ batchSizes.length ];
for ( int i = 0; i < batchSizes.length; i++ ) {
loaders[i] = new OneToManyLoader( persister, batchSizes[i], factory, loadQueryInfluencers );
}
return new PaddedBatchingCollectionInitializer( persister, batchSizes, loaders );
}
private static class PaddedBatchingCollectionInitializer extends BatchingCollectionInitializer {
private final int[] batchSizes;
private final Loader[] loaders;
public PaddedBatchingCollectionInitializer(QueryableCollection persister, int[] batchSizes, Loader[] loaders) {
super( persister );
this.batchSizes = batchSizes;
this.loaders = loaders;
}
@Override
public void initialize(Serializable id, SessionImplementor session) throws HibernateException {
final Serializable[] batch = session.getPersistenceContext()
.getBatchFetchQueue()
.getCollectionBatch( collectionPersister(), id, batchSizes[0] );
final int numberOfIds = ArrayHelper.countNonNull( batch );
if ( numberOfIds <= 1 ) {
loaders[batchSizes.length-1].loadCollection( session, id, collectionPersister().getKeyType() );
return;
}
// Uses the first batch-size bigger than the number of actual ids in the batch
int indexToUse = batchSizes.length-1;
for ( int i = 0; i < batchSizes.length-1; i++ ) {
if ( batchSizes[i] >= numberOfIds ) {
indexToUse = i;
}
else {
break;
}
}
final Serializable[] idsToLoad = new Serializable[ batchSizes[indexToUse] ];
System.arraycopy( batch, 0, idsToLoad, 0, numberOfIds );
for ( int i = numberOfIds; i < batchSizes[indexToUse]; i++ ) {
idsToLoad[i] = id;
}
loaders[indexToUse].loadCollectionBatch( session, idsToLoad, collectionPersister().getKeyType() );
}
}
}

View File

@ -294,7 +294,7 @@ public class CustomLoader extends Loader {
}
@Override
protected String getSQLString() {
public String getSQLString() {
return sql;
}

View File

@ -1,10 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* Copyright (c) 2008, 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,135 +20,109 @@
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package org.hibernate.loader.entity;
import java.io.Serializable;
import java.util.Iterator;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import org.hibernate.LockMode;
import org.jboss.logging.Logger;
import org.hibernate.LockOptions;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.Loader;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.type.Type;
/**
* "Batch" loads entities, using multiple primary key values in the
* SQL <tt>where</tt> clause.
* 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.
*
* @see EntityLoader
* @author Gavin King
* @author Steve Ebersole
*
* @see BatchingEntityLoaderBuilder
* @see UniqueEntityLoader
*/
public class BatchingEntityLoader implements UniqueEntityLoader {
public abstract class BatchingEntityLoader implements UniqueEntityLoader {
private static final Logger log = Logger.getLogger( BatchingEntityLoader.class );
private final Loader[] loaders;
private final int[] batchSizes;
private final EntityPersister persister;
private final Type idType;
public BatchingEntityLoader(EntityPersister persister, int[] batchSizes, Loader[] loaders) {
this.batchSizes = batchSizes;
this.loaders = loaders;
public BatchingEntityLoader(EntityPersister persister) {
this.persister = persister;
idType = persister.getIdentifierType();
}
private Object getObjectFromList(List results, Serializable id, SessionImplementor session) {
// get the right object from the list ... would it be easier to just call getEntity() ??
Iterator iter = results.iterator();
while ( iter.hasNext() ) {
Object obj = iter.next();
final boolean equal = idType.isEqual(
public EntityPersister persister() {
return persister;
}
@Override
@Deprecated
public Object load(Serializable id, Object optionalObject, SessionImplementor session) {
return load( id, optionalObject, session, LockOptions.NONE );
}
protected QueryParameters buildQueryParameters(
Serializable id,
Serializable[] ids,
Object optionalObject,
LockOptions lockOptions) {
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 );
return qp;
}
protected Object getObjectFromList(List results, Serializable id, SessionImplementor session) {
for ( Object obj : results ) {
final boolean equal = persister.getIdentifierType().isEqual(
id,
session.getContextEntityIdentifier(obj),
session.getContextEntityIdentifier( obj ),
session.getFactory()
);
if ( equal ) return obj;
if ( equal ) {
return obj;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public Object load(Serializable id, Object optionalObject, SessionImplementor session) {
// this form is deprecated!
return load( id, optionalObject, session, LockOptions.NONE );
}
public Object load(Serializable id, Object optionalObject, SessionImplementor session, LockOptions lockOptions) {
Serializable[] batch = session.getPersistenceContext()
.getBatchFetchQueue()
.getEntityBatch( persister, id, batchSizes[0], persister.getEntityMode() );
for ( int i=0; i<batchSizes.length-1; i++) {
final int smallBatchSize = batchSizes[i];
if ( batch[smallBatchSize-1]!=null ) {
Serializable[] smallBatch = new Serializable[smallBatchSize];
System.arraycopy(batch, 0, smallBatch, 0, smallBatchSize);
final List results = loaders[i].loadEntityBatch(
session,
smallBatch,
idType,
optionalObject,
persister.getEntityName(),
id,
persister,
lockOptions
);
return getObjectFromList(results, id, session); //EARLY EXIT
}
protected Object doBatchLoad(
Serializable id,
Loader loaderToUse,
SessionImplementor session,
Serializable[] ids,
Object optionalObject,
LockOptions lockOptions) {
if ( log.isDebugEnabled() ) {
log.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister, ids, session.getFactory() ) );
}
return ( (UniqueEntityLoader) loaders[batchSizes.length-1] ).load(id, optionalObject, session);
QueryParameters qp = buildQueryParameters( id, ids, optionalObject, lockOptions );
}
public static UniqueEntityLoader createBatchingEntityLoader(
final OuterJoinLoadable persister,
final int maxBatchSize,
final LockMode lockMode,
final SessionFactoryImplementor factory,
final LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
if ( maxBatchSize>1 ) {
int[] batchSizesToCreate = ArrayHelper.getBatchSizes(maxBatchSize);
Loader[] loadersToCreate = new Loader[ batchSizesToCreate.length ];
for ( int i=0; i<batchSizesToCreate.length; i++ ) {
loadersToCreate[i] = new EntityLoader(persister, batchSizesToCreate[i], lockMode, factory, loadQueryInfluencers);
}
return new BatchingEntityLoader(persister, batchSizesToCreate, loadersToCreate);
try {
final List results = loaderToUse.doQueryAndInitializeNonLazyCollections( session, qp, false );
log.debug( "Done entity batch load" );
return getObjectFromList(results, id, session);
}
else {
return new EntityLoader(persister, lockMode, factory, loadQueryInfluencers);
}
}
public static UniqueEntityLoader createBatchingEntityLoader(
final OuterJoinLoadable persister,
final int maxBatchSize,
final LockOptions lockOptions,
final SessionFactoryImplementor factory,
final LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
if ( maxBatchSize>1 ) {
int[] batchSizesToCreate = ArrayHelper.getBatchSizes(maxBatchSize);
Loader[] loadersToCreate = new Loader[ batchSizesToCreate.length ];
for ( int i=0; i<batchSizesToCreate.length; i++ ) {
loadersToCreate[i] = new EntityLoader(persister, batchSizesToCreate[i], lockOptions, factory, loadQueryInfluencers);
}
return new BatchingEntityLoader(persister, batchSizesToCreate, loadersToCreate);
}
else {
return new EntityLoader(persister, lockOptions, factory, loadQueryInfluencers);
catch ( SQLException sqle ) {
throw session.getFactory().getSQLExceptionHelper().convert(
sqle,
"could not load an entity batch: " + MessageHelper.infoString( persister(), ids, session.getFactory() ),
loaderToUse.getSQLString()
);
}
}

View File

@ -0,0 +1,117 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.loader.entity;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.persister.entity.OuterJoinLoadable;
/**
* The contract for building {@link UniqueEntityLoader} capable of performing batch-fetch loading. Intention
* is to build these instances, by first calling the static {@link #getBuilder}, and then calling the appropriate
* {@link #buildLoader} method.
*
* @author Steve Ebersole
*
* @see org.hibernate.loader.BatchFetchStyle
*/
public abstract class BatchingEntityLoaderBuilder {
public static BatchingEntityLoaderBuilder getBuilder(SessionFactoryImplementor factory) {
switch ( factory.getSettings().getBatchFetchStyle() ) {
case PADDED: {
return PaddedBatchingEntityLoaderBuilder.INSTANCE;
}
case DYNAMIC: {
return DynamicBatchingEntityLoaderBuilder.INSTANCE;
}
default: {
return LegacyBatchingEntityLoaderBuilder.INSTANCE;
}
}
}
/**
* Builds a batch-fetch capable loader based on the given persister, lock-mode, etc.
*
* @param persister The entity persister
* @param batchSize The maximum number of ids to batch-fetch at once
* @param lockMode The lock mode
* @param factory The SessionFactory
* @param influencers Any influencers that should affect the built query
*
* @return The loader.
*/
public UniqueEntityLoader buildLoader(
OuterJoinLoadable persister,
int batchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
if ( batchSize <= 1 ) {
// no batching
return new EntityLoader( persister, lockMode, factory, influencers );
}
return buildBatchingLoader( persister, batchSize, lockMode, factory, influencers );
}
protected abstract UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers);
/**
* Builds a batch-fetch capable loader based on the given persister, lock-options, etc.
*
* @param persister The entity persister
* @param batchSize The maximum number of ids to batch-fetch at once
* @param lockOptions The lock options
* @param factory The SessionFactory
* @param influencers Any influencers that should affect the built query
*
* @return The loader.
*/
public UniqueEntityLoader buildLoader(
OuterJoinLoadable persister,
int batchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
if ( batchSize <= 1 ) {
// no batching
return new EntityLoader( persister, lockOptions, factory, influencers );
}
return buildBatchingLoader( persister, batchSize, lockOptions, factory, influencers );
}
protected abstract UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers);
}

View File

@ -0,0 +1,266 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.loader.entity;
import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import org.jboss.logging.Logger;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.dialect.pagination.LimitHelper;
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.SessionImplementor;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.pretty.MessageHelper;
/**
* 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();
@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,
SessionImplementor session,
LockOptions lockOptions) {
final Serializable[] batch = session.getPersistenceContext()
.getBatchFetchQueue()
.getEntityBatch( persister(), id, maxBatchSize, persister().getEntityMode() );
final int numberOfIds = ArrayHelper.countNonNull( batch );
if ( numberOfIds <= 1 ) {
return singleKeyLoader.load( id, optionalObject, session );
}
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 );
List results = dynamicLoader.doEntityBatchFetch( session, qp, idsToLoad );
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;
}
public List doEntityBatchFetch(
SessionImplementor session,
QueryParameters queryParameters,
Serializable[] ids) {
final String sql = StringHelper.expandBatchIdPlaceholder(
sqlTemplate,
ids,
alias,
persister.getKeyColumnNames(),
getFactory().getDialect()
);
try {
final PersistenceContext persistenceContext = session.getPersistenceContext();
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 session.getFactory().getSQLExceptionHelper().convert(
sqle,
"could not load an entity batch: " + MessageHelper.infoString(
getEntityPersisters()[0],
ids,
session.getFactory()
),
sql
);
}
}
private List doTheLoad(String sql, QueryParameters queryParameters, SessionImplementor session) throws SQLException {
final RowSelection selection = queryParameters.getRowSelection();
final int maxRows = LimitHelper.hasMaxRows( selection ) ?
selection.getMaxRows() :
Integer.MAX_VALUE;
final ResultSet rs = executeQueryStatement( sql, queryParameters, false, session );
final Statement st = rs.getStatement();
try {
return processResultSet( rs, queryParameters, session, false, null, maxRows );
}
finally {
st.close();
}
}
}
}

View File

@ -0,0 +1,125 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.loader.entity;
import java.io.Serializable;
import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.Loader;
import org.hibernate.persister.entity.OuterJoinLoadable;
/**
* @author Steve Ebersole
*/
public class LegacyBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder {
public static final LegacyBatchingEntityLoaderBuilder INSTANCE = new LegacyBatchingEntityLoaderBuilder();
@Override
protected UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return new LegacyBatchingEntityLoader( persister, batchSize, lockMode, factory, influencers );
}
@Override
protected UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return new LegacyBatchingEntityLoader( persister, batchSize, lockOptions, factory, influencers );
}
public static class LegacyBatchingEntityLoader extends BatchingEntityLoader implements UniqueEntityLoader {
private final int[] batchSizes;
private final Loader[] loaders;
public LegacyBatchingEntityLoader(
OuterJoinLoadable persister,
int maxBatchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) {
super( persister );
this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
this.loaders = new Loader[ batchSizes.length ];
for ( int i = 0; i < batchSizes.length; i++ ) {
this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockMode, factory, loadQueryInfluencers);
}
}
public LegacyBatchingEntityLoader(
OuterJoinLoadable persister,
int maxBatchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) {
super( persister );
this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
this.loaders = new Loader[ batchSizes.length ];
for ( int i = 0; i < batchSizes.length; i++ ) {
this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockOptions, factory, loadQueryInfluencers);
}
}
@Override
public Object load(Serializable id, Object optionalObject, SessionImplementor session, LockOptions lockOptions) {
final Serializable[] batch = session.getPersistenceContext()
.getBatchFetchQueue()
.getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() );
for ( int i = 0; i < batchSizes.length-1; i++) {
final int smallBatchSize = batchSizes[i];
if ( batch[smallBatchSize-1] != null ) {
Serializable[] smallBatch = new Serializable[smallBatchSize];
System.arraycopy(batch, 0, smallBatch, 0, smallBatchSize);
// for now...
final List results = loaders[i].loadEntityBatch(
session,
smallBatch,
persister().getIdentifierType(),
optionalObject,
persister().getEntityName(),
id,
persister(),
lockOptions
);
return getObjectFromList(results, id, session); //EARLY EXIT
}
}
return ( (UniqueEntityLoader) loaders[batchSizes.length-1] ).load(id, optionalObject, session);
}
}
}

View File

@ -0,0 +1,140 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.loader.entity;
import java.io.Serializable;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.Loader;
import org.hibernate.persister.entity.OuterJoinLoadable;
/**
* @author Steve Ebersole
*/
class PaddedBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder {
public static final PaddedBatchingEntityLoaderBuilder INSTANCE = new PaddedBatchingEntityLoaderBuilder();
@Override
protected UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return new PaddedBatchingEntityLoader( persister, batchSize, lockMode, factory, influencers );
}
@Override
protected UniqueEntityLoader buildBatchingLoader(
OuterJoinLoadable persister,
int batchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers influencers) {
return new PaddedBatchingEntityLoader( persister, batchSize, lockOptions, factory, influencers );
}
public static class PaddedBatchingEntityLoader extends BatchingEntityLoader {
private final int[] batchSizes;
private final Loader[] loaders;
public PaddedBatchingEntityLoader(
OuterJoinLoadable persister,
int maxBatchSize,
LockMode lockMode,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) {
super( persister );
this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
this.loaders = new Loader[ batchSizes.length ];
for ( int i = 0; i < batchSizes.length; i++ ) {
this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockMode, factory, loadQueryInfluencers);
}
validate( maxBatchSize );
}
private void validate(int max) {
// these are more indicative of internal problems then user error...
if ( batchSizes[0] != max ) {
throw new HibernateException( "Unexpected batch size spread" );
}
if ( batchSizes[batchSizes.length-1] != 1 ) {
throw new HibernateException( "Unexpected batch size spread" );
}
}
public PaddedBatchingEntityLoader(
OuterJoinLoadable persister,
int maxBatchSize,
LockOptions lockOptions,
SessionFactoryImplementor factory,
LoadQueryInfluencers loadQueryInfluencers) {
super( persister );
this.batchSizes = ArrayHelper.getBatchSizes( maxBatchSize );
this.loaders = new Loader[ batchSizes.length ];
for ( int i = 0; i < batchSizes.length; i++ ) {
this.loaders[i] = new EntityLoader( persister, batchSizes[i], lockOptions, factory, loadQueryInfluencers);
}
validate( maxBatchSize );
}
@Override
public Object load(Serializable id, Object optionalObject, SessionImplementor session, LockOptions lockOptions) {
final Serializable[] batch = session.getPersistenceContext()
.getBatchFetchQueue()
.getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() );
final int numberOfIds = ArrayHelper.countNonNull( batch );
if ( numberOfIds <= 1 ) {
return ( (UniqueEntityLoader) loaders[batchSizes.length-1] ).load( id, optionalObject, session );
}
// Uses the first batch-size bigger than the number of actual ids in the batch
int indexToUse = batchSizes.length-1;
for ( int i = 0; i < batchSizes.length-1; i++ ) {
if ( batchSizes[i] >= numberOfIds ) {
indexToUse = i;
}
else {
break;
}
}
final Serializable[] idsToLoad = new Serializable[ batchSizes[indexToUse] ];
System.arraycopy( batch, 0, idsToLoad, 0, numberOfIds );
for ( int i = numberOfIds; i < batchSizes[indexToUse]; i++ ) {
idsToLoad[i] = id;
}
return doBatchLoad( id, loaders[indexToUse], session, idsToLoad, optionalObject, lockOptions );
}
}
}

View File

@ -43,6 +43,7 @@ public interface UniqueEntityLoader {
* @deprecated use {@link #load(java.io.Serializable, Object, SessionImplementor, LockOptions)} instead.
*/
@SuppressWarnings( {"JavaDoc"})
@Deprecated
public Object load(Serializable id, Object optionalObject, SessionImplementor session) throws HibernateException;
/**

View File

@ -239,7 +239,7 @@ public class QueryLoader extends BasicLoader {
/**
* The SQL query string to be called.
*/
protected String getSQLString() {
public String getSQLString() {
return queryTranslator.getSQLString();
}

View File

@ -45,6 +45,7 @@ import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.jdbc.Expectation;
import org.hibernate.jdbc.Expectations;
import org.hibernate.loader.collection.BatchingCollectionInitializer;
import org.hibernate.loader.collection.BatchingCollectionInitializerBuilder;
import org.hibernate.loader.collection.CollectionInitializer;
import org.hibernate.loader.collection.SubselectCollectionLoader;
import org.hibernate.mapping.Collection;
@ -332,7 +333,8 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
@Override
protected CollectionInitializer createCollectionInitializer(LoadQueryInfluencers loadQueryInfluencers)
throws MappingException {
return BatchingCollectionInitializer.createBatchingCollectionInitializer( this, batchSize, getFactory(), loadQueryInfluencers );
return BatchingCollectionInitializerBuilder.getBuilder( getFactory() )
.createBatchingCollectionInitializer( this, batchSize, getFactory(), loadQueryInfluencers );
}
public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {

View File

@ -44,6 +44,7 @@ import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.jdbc.Expectation;
import org.hibernate.jdbc.Expectations;
import org.hibernate.loader.collection.BatchingCollectionInitializer;
import org.hibernate.loader.collection.BatchingCollectionInitializerBuilder;
import org.hibernate.loader.collection.CollectionInitializer;
import org.hibernate.loader.collection.SubselectOneToManyLoader;
import org.hibernate.loader.entity.CollectionElementLoader;
@ -359,7 +360,8 @@ public class OneToManyPersister extends AbstractCollectionPersister {
@Override
protected CollectionInitializer createCollectionInitializer(LoadQueryInfluencers loadQueryInfluencers)
throws MappingException {
return BatchingCollectionInitializer.createBatchingOneToManyInitializer( this, batchSize, getFactory(), loadQueryInfluencers );
return BatchingCollectionInitializerBuilder.getBuilder( getFactory() )
.createBatchingOneToManyInitializer( this, batchSize, getFactory(), loadQueryInfluencers );
}
public String fromJoinFragment(String alias,

View File

@ -90,6 +90,7 @@ import org.hibernate.jdbc.Expectation;
import org.hibernate.jdbc.Expectations;
import org.hibernate.jdbc.TooManyRowsAffectedException;
import org.hibernate.loader.entity.BatchingEntityLoader;
import org.hibernate.loader.entity.BatchingEntityLoaderBuilder;
import org.hibernate.loader.entity.CascadeEntityLoader;
import org.hibernate.loader.entity.EntityLoader;
import org.hibernate.loader.entity.UniqueEntityLoader;
@ -2440,26 +2441,16 @@ public abstract class AbstractEntityPersister
LockMode lockMode,
LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
//TODO: disable batch loading if lockMode > READ?
return BatchingEntityLoader.createBatchingEntityLoader(
this,
batchSize,
lockMode,
getFactory(),
loadQueryInfluencers
);
return BatchingEntityLoaderBuilder.getBuilder( getFactory() )
.buildLoader( this, batchSize, lockMode, getFactory(), loadQueryInfluencers );
}
protected UniqueEntityLoader createEntityLoader(
LockOptions lockOptions,
LoadQueryInfluencers loadQueryInfluencers) throws MappingException {
//TODO: disable batch loading if lockMode > READ?
return BatchingEntityLoader.createBatchingEntityLoader(
this,
batchSize,
lockOptions,
getFactory(),
loadQueryInfluencers
);
return BatchingEntityLoaderBuilder.getBuilder( getFactory() )
.buildLoader( this, batchSize, lockOptions, getFactory(), loadQueryInfluencers );
}
protected UniqueEntityLoader createEntityLoader(LockMode lockMode) throws MappingException {

View File

@ -31,6 +31,10 @@ import org.junit.Test;
import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.loader.BatchFetchStyle;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import static org.junit.Assert.assertEquals;
@ -46,6 +50,18 @@ public class BatchFetchTest extends BaseCoreFunctionalTestCase {
return new String[] { "batchfetch/ProductLine.hbm.xml" };
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] { BatchLoadableEntity.class };
}
@Override
protected void configure(Configuration configuration) {
super.configure( configuration );
configuration.setProperty( AvailableSettings.GENERATE_STATISTICS, "true" );
configuration.setProperty( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" );
}
@SuppressWarnings( {"unchecked"})
@Test
public void testBatchFetch() {
@ -136,5 +152,53 @@ public class BatchFetchTest extends BaseCoreFunctionalTestCase {
s.close();
}
@Test
@SuppressWarnings( {"unchecked"})
public void testBatchFetch2() {
Session s = openSession();
s.beginTransaction();
int size = 32+14;
for ( int i = 0; i < size; i++ ) {
s.save( new BatchLoadableEntity( i ) );
}
s.getTransaction().commit();
s.close();
s = openSession();
s.beginTransaction();
// load them all as proxies
for ( int i = 0; i < size; i++ ) {
BatchLoadableEntity entity = (BatchLoadableEntity) s.load( BatchLoadableEntity.class, i );
assertFalse( Hibernate.isInitialized( entity ) );
}
sessionFactory().getStatistics().clear();
// now start initializing them...
for ( int i = 0; i < size; i++ ) {
BatchLoadableEntity entity = (BatchLoadableEntity) s.load( BatchLoadableEntity.class, i );
Hibernate.initialize( entity );
assertTrue( Hibernate.isInitialized( entity ) );
}
// so at this point, all entities are initialized. see how many fetches were performed.
final int expectedFetchCount;
if ( sessionFactory().getSettings().getBatchFetchStyle() == BatchFetchStyle.LEGACY ) {
expectedFetchCount = 3; // (32 + 10 + 4)
}
else if ( sessionFactory().getSettings().getBatchFetchStyle() == BatchFetchStyle.DYNAMIC ) {
expectedFetchCount = 2; // (32 + 14) : because we limited batch-size to 32
}
else {
// PADDED
expectedFetchCount = 2; // (32 + 16*) with the 16 being padded
}
assertEquals( expectedFetchCount, sessionFactory().getStatistics().getEntityStatistics( BatchLoadableEntity.class.getName() ).getFetchCount() );
s.getTransaction().commit();
s.close();
s = openSession();
s.beginTransaction();
s.createQuery( "delete BatchLoadableEntity" ).executeUpdate();
s.getTransaction().commit();
s.close();
}
}

View File

@ -0,0 +1,64 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.batchfetch;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.BatchSize;
/**
* @author Steve Ebersole
*/
@Entity
@BatchSize( size = 32 )
public class BatchLoadableEntity {
private Integer id;
private String name;
public BatchLoadableEntity() {
}
public BatchLoadableEntity(int id) {
this.id = id;
this.name = "Entity #" + id;
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -25,9 +25,6 @@ package org.hibernate.test.manytomany.batchload;
import java.util.List;
import junit.framework.Assert;
import org.junit.Test;
import org.hibernate.EmptyInterceptor;
import org.hibernate.Hibernate;
import org.hibernate.Interceptor;
@ -39,12 +36,13 @@ import org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch;
import org.hibernate.engine.jdbc.batch.spi.Batch;
import org.hibernate.engine.jdbc.batch.spi.BatchKey;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.loader.collection.BatchingCollectionInitializer;
import org.hibernate.persister.collection.AbstractCollectionPersister;
import org.hibernate.stat.CollectionStatistics;
import org.junit.Test;
import junit.framework.Assert;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import static org.hibernate.testing.junit4.ExtraAssertions.assertClassAssignability;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@ -80,25 +78,6 @@ public class BatchedManyToManyTest extends BaseCoreFunctionalTestCase {
}
}
@Test
public void testProperLoaderSetup() {
AbstractCollectionPersister cp = ( AbstractCollectionPersister )
sessionFactory().getCollectionPersister( User.class.getName() + ".groups" );
assertClassAssignability( BatchingCollectionInitializer.class, cp.getInitializer().getClass() );
BatchingCollectionInitializer initializer = ( BatchingCollectionInitializer ) cp.getInitializer();
assertEquals( 50, findMaxBatchSize( initializer.getBatchSizes() ) );
}
private int findMaxBatchSize(int[] batchSizes) {
int max = 0;
for ( int size : batchSizes ) {
if ( size > max ) {
max = size;
}
}
return max;
}
@Test
public void testLoadingNonInverseSide() {
prepareTestData();