Package org.hibernate.loader.collection

Source Code of org.hibernate.loader.collection.DynamicBatchingCollectionInitializerBuilder

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

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 List<AfterLoadAction> afterLoadActions = Collections.emptyList();
      final SqlStatementWrapper wrapper = executeQueryStatement( sql, queryParameters, false, afterLoadActions, session );
      final ResultSet rs = wrapper.getResultSet();
      final Statement st = wrapper.getStatement();
      try {
        processResultSet( rs, queryParameters, session, true, null, maxRows, afterLoadActions );
      }
      finally {
        session.getTransactionCoordinator().getJdbcCoordinator().release( st );
      }
    }

  }

}
TOP

Related Classes of org.hibernate.loader.collection.DynamicBatchingCollectionInitializerBuilder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.