/*
 * 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.ArrayList;
import java.util.List;

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.loader.spi.AfterLoadAction;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.pretty.MessageHelper;

import org.jboss.logging.Logger;

A BatchingEntityLoaderBuilder that builds UniqueEntityLoader instances capable of dynamically building its batch-fetch SQL based on the actual number of entity ids waiting to be fetched.
Author:Steve Ebersole
/** * 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 List<AfterLoadAction> afterLoadActions = new ArrayList<AfterLoadAction>(); final SqlStatementWrapper wrapper = executeQueryStatement( sql, queryParameters, false, afterLoadActions, session ); final ResultSet rs = wrapper.getResultSet(); final Statement st = wrapper.getStatement(); try { return processResultSet( rs, queryParameters, session, false, null, maxRows, afterLoadActions ); } finally { session.getTransactionCoordinator().getJdbcCoordinator().release( st ); } } } }