Skip to content

Commit

Permalink
HHH-7746 - Investigate alternative batch loading algorithms
Browse files Browse the repository at this point in the history
  • Loading branch information
sebersole committed Nov 2, 2012
1 parent d41545e commit 06b0faa
Show file tree
Hide file tree
Showing 30 changed files with 1,734 additions and 236 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
10 changes: 10 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/cfg/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
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;

/**
Expand Down Expand Up @@ -91,6 +92,7 @@ public final class Settings {
private JtaPlatform jtaPlatform;

private MultiTableBulkIdStrategy multiTableBulkIdStrategy;
private BatchFetchStyle batchFetchStyle;


/**
Expand Down Expand Up @@ -480,4 +482,12 @@ public MultiTableBulkIdStrategy getMultiTableBulkIdStrategy() {
void setMultiTableBulkIdStrategy(MultiTableBulkIdStrategy multiTableBulkIdStrategy) {
this.multiTableBulkIdStrategy = multiTableBulkIdStrategy;
}

public BatchFetchStyle getBatchFetchStyle() {
return batchFetchStyle;
}

void setBatchFetchStyle(BatchFetchStyle batchFetchStyle) {
this.batchFetchStyle = batchFetchStyle;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
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;
Expand Down Expand Up @@ -199,6 +200,11 @@ public Settings buildSettings(Properties props, ServiceRegistry serviceRegistry)
}
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 );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -66,6 +67,17 @@ public static String join(String seperator, String[] strings) {
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() );
Expand All @@ -89,6 +101,15 @@ public static String repeat(String string, int times) {
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 );
Expand Down Expand Up @@ -661,4 +682,69 @@ public static String[] unquote(String[] names, Dialect dialect) {
}
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 " ) );
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@
*/
package org.hibernate.internal.util.collections;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
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;
Expand Down Expand Up @@ -372,10 +374,43 @@ public static boolean isEquals(byte[] b1, byte[] b2) {
}
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] );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,7 @@ protected final boolean isJoinFetchEnabledByProfile(OuterJoinLoadable persister,
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 ) &&
Expand All @@ -201,11 +198,11 @@ protected boolean isDuplicateAssociation(



protected final Loadable getPersister() {
public final Loadable getPersister() {
return persister;
}

protected final String getAlias() {
public final String getAlias() {
return alias;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
}
}
17 changes: 13 additions & 4 deletions hibernate-core/src/main/java/org/hibernate/loader/Loader.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public Loader(SessionFactoryImplementor factory) {
*
* @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;
Expand Down Expand Up @@ -256,7 +256,7 @@ private String prependComment(String sql, QueryParameters parameters) {
* 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 {
Expand All @@ -268,7 +268,7 @@ private List doQueryAndInitializeNonLazyCollections(
);
}

private List doQueryAndInitializeNonLazyCollections(
public List doQueryAndInitializeNonLazyCollections(
final SessionImplementor session,
final QueryParameters queryParameters,
final boolean returnProxies,
Expand Down Expand Up @@ -1722,8 +1722,17 @@ protected ResultSet executeQueryStatement(
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(
Expand Down
Loading

0 comments on commit 06b0faa

Please sign in to comment.