Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » loader » [javadoc | source]
    1   /*
    2    * Hibernate, Relational Persistence for Idiomatic Java
    3    *
    4    * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
    5    * indicated by the @author tags or express copyright attribution
    6    * statements applied by the authors.  All third-party contributions are
    7    * distributed under license by Red Hat Middleware LLC.
    8    *
    9    * This copyrighted material is made available to anyone wishing to use, modify,
   10    * copy, or redistribute it subject to the terms and conditions of the GNU
   11    * Lesser General Public License, as published by the Free Software Foundation.
   12    *
   13    * This program is distributed in the hope that it will be useful,
   14    * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   15    * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
   16    * for more details.
   17    *
   18    * You should have received a copy of the GNU Lesser General Public License
   19    * along with this distribution; if not, write to:
   20    * Free Software Foundation, Inc.
   21    * 51 Franklin Street, Fifth Floor
   22    * Boston, MA  02110-1301  USA
   23    *
   24    */
   25   package org.hibernate.loader;
   26   
   27   import java.io.Serializable;
   28   import java.sql.CallableStatement;
   29   import java.sql.PreparedStatement;
   30   import java.sql.ResultSet;
   31   import java.sql.SQLException;
   32   import java.util.ArrayList;
   33   import java.util.Arrays;
   34   import java.util.HashMap;
   35   import java.util.HashSet;
   36   import java.util.Iterator;
   37   import java.util.List;
   38   import java.util.Map;
   39   import java.util.Set;
   40   
   41   import org.slf4j.Logger;
   42   import org.slf4j.LoggerFactory;
   43   import org.hibernate.AssertionFailure;
   44   import org.hibernate.HibernateException;
   45   import org.hibernate.LockMode;
   46   import org.hibernate.QueryException;
   47   import org.hibernate.ScrollMode;
   48   import org.hibernate.ScrollableResults;
   49   import org.hibernate.StaleObjectStateException;
   50   import org.hibernate.WrongClassException;
   51   import org.hibernate.cache.FilterKey;
   52   import org.hibernate.cache.QueryCache;
   53   import org.hibernate.cache.QueryKey;
   54   import org.hibernate.collection.PersistentCollection;
   55   import org.hibernate.dialect.Dialect;
   56   import org.hibernate.engine.EntityKey;
   57   import org.hibernate.engine.EntityUniqueKey;
   58   import org.hibernate.engine.PersistenceContext;
   59   import org.hibernate.engine.QueryParameters;
   60   import org.hibernate.engine.RowSelection;
   61   import org.hibernate.engine.SessionFactoryImplementor;
   62   import org.hibernate.engine.SessionImplementor;
   63   import org.hibernate.engine.SubselectFetch;
   64   import org.hibernate.engine.TwoPhaseLoad;
   65   import org.hibernate.engine.TypedValue;
   66   import org.hibernate.event.EventSource;
   67   import org.hibernate.event.PostLoadEvent;
   68   import org.hibernate.event.PreLoadEvent;
   69   import org.hibernate.exception.JDBCExceptionHelper;
   70   import org.hibernate.hql.HolderInstantiator;
   71   import org.hibernate.impl.FetchingScrollableResultsImpl;
   72   import org.hibernate.impl.ScrollableResultsImpl;
   73   import org.hibernate.jdbc.ColumnNameCache;
   74   import org.hibernate.jdbc.ResultSetWrapper;
   75   import org.hibernate.persister.collection.CollectionPersister;
   76   import org.hibernate.persister.entity.EntityPersister;
   77   import org.hibernate.persister.entity.Loadable;
   78   import org.hibernate.persister.entity.UniqueKeyLoadable;
   79   import org.hibernate.pretty.MessageHelper;
   80   import org.hibernate.proxy.HibernateProxy;
   81   import org.hibernate.transform.ResultTransformer;
   82   import org.hibernate.type.AssociationType;
   83   import org.hibernate.type.EntityType;
   84   import org.hibernate.type.Type;
   85   import org.hibernate.type.VersionType;
   86   import org.hibernate.util.StringHelper;
   87   
   88   /**
   89    * Abstract superclass of object loading (and querying) strategies. This class implements
   90    * useful common functionality that concrete loaders delegate to. It is not intended that this
   91    * functionality would be directly accessed by client code. (Hence, all methods of this class
   92    * are declared <tt>protected</tt> or <tt>private</tt>.) This class relies heavily upon the
   93    * <tt>Loadable</tt> interface, which is the contract between this class and
   94    * <tt>EntityPersister</tt>s that may be loaded by it.<br>
   95    * <br>
   96    * The present implementation is able to load any number of columns of entities and at most
   97    * one collection role per query.
   98    *
   99    * @author Gavin King
  100    * @see org.hibernate.persister.entity.Loadable
  101    */
  102   public abstract class Loader {
  103   
  104   	private static final Logger log = LoggerFactory.getLogger( Loader.class );
  105   
  106   	private final SessionFactoryImplementor factory;
  107   	private ColumnNameCache columnNameCache;
  108   
  109   	public Loader(SessionFactoryImplementor factory) {
  110   		this.factory = factory;
  111   	}
  112   
  113   	/**
  114   	 * The SQL query string to be called; implemented by all subclasses
  115   	 *
  116   	 * @return The sql command this loader should use to get its {@link ResultSet}.
  117   	 */
  118   	protected abstract String getSQLString();
  119   
  120   	/**
  121   	 * An array of persisters of entity classes contained in each row of results;
  122   	 * implemented by all subclasses
  123   	 *
  124   	 * @return The entity persisters.
  125   	 */
  126   	protected abstract Loadable[] getEntityPersisters();
  127   	
  128   	/**
  129   	 * An array indicating whether the entities have eager property fetching
  130   	 * enabled.
  131   	 *
  132   	 * @return Eager property fetching indicators.
  133   	 */
  134   	protected boolean[] getEntityEagerPropertyFetches() {
  135   		return null;
  136   	}
  137   
  138   	/**
  139   	 * An array of indexes of the entity that owns a one-to-one association
  140   	 * to the entity at the given index (-1 if there is no "owner").  The
  141   	 * indexes contained here are relative to the result of
  142   	 * {@link #getEntityPersisters}.
  143   	 *
  144   	 * @return The owner indicators (see discussion above).
  145   	 */
  146   	protected int[] getOwners() {
  147   		return null;
  148   	}
  149   
  150   	/**
  151   	 * An array of the owner types corresponding to the {@link #getOwners()}
  152   	 * returns.  Indices indicating no owner would be null here.
  153   	 *
  154   	 * @return The types for the owners.
  155   	 */
  156   	protected EntityType[] getOwnerAssociationTypes() {
  157   		return null;
  158   	}
  159   
  160   	/**
  161   	 * An (optional) persister for a collection to be initialized; only 
  162   	 * collection loaders return a non-null value
  163   	 */
  164   	protected CollectionPersister[] getCollectionPersisters() {
  165   		return null;
  166   	}
  167   
  168   	/**
  169   	 * Get the index of the entity that owns the collection, or -1
  170   	 * if there is no owner in the query results (ie. in the case of a
  171   	 * collection initializer) or no collection.
  172   	 */
  173   	protected int[] getCollectionOwners() {
  174   		return null;
  175   	}
  176   
  177   	/**
  178   	 * What lock mode does this load entities with?
  179   	 *
  180   	 * @param lockModes a collection of lock modes specified dynamically via the Query interface
  181   	 */
  182   	protected abstract LockMode[] getLockModes(Map lockModes);
  183   
  184   	/**
  185   	 * Append <tt>FOR UPDATE OF</tt> clause, if necessary. This
  186   	 * empty superclass implementation merely returns its first
  187   	 * argument.
  188   	 */
  189   	protected String applyLocks(String sql, Map lockModes, Dialect dialect) throws HibernateException {
  190   		return sql;
  191   	}
  192   
  193   	/**
  194   	 * Does this query return objects that might be already cached
  195   	 * by the session, whose lock mode may need upgrading
  196   	 */
  197   	protected boolean upgradeLocks() {
  198   		return false;
  199   	}
  200   
  201   	/**
  202   	 * Return false is this loader is a batch entity loader
  203   	 */
  204   	protected boolean isSingleRowLoader() {
  205   		return false;
  206   	}
  207   
  208   	/**
  209   	 * Get the SQL table aliases of entities whose
  210   	 * associations are subselect-loadable, returning
  211   	 * null if this loader does not support subselect
  212   	 * loading
  213   	 */
  214   	protected String[] getAliases() {
  215   		return null;
  216   	}
  217   
  218   	/**
  219   	 * Modify the SQL, adding lock hints and comments, if necessary
  220   	 */
  221   	protected String preprocessSQL(String sql, QueryParameters parameters, Dialect dialect)
  222   			throws HibernateException {
  223   		
  224   		sql = applyLocks( sql, parameters.getLockModes(), dialect );
  225   		
  226   		return getFactory().getSettings().isCommentsEnabled() ?
  227   				prependComment( sql, parameters ) : sql;
  228   	}
  229   
  230   	private String prependComment(String sql, QueryParameters parameters) {
  231   		String comment = parameters.getComment();
  232   		if ( comment == null ) {
  233   			return sql;
  234   		}
  235   		else {
  236   			return new StringBuffer( comment.length() + sql.length() + 5 )
  237   					.append( "/* " )
  238   					.append( comment )
  239   					.append( " */ " )
  240   					.append( sql )
  241   					.toString();
  242   		}
  243   	}
  244   
  245   	/**
  246   	 * Execute an SQL query and attempt to instantiate instances of the class mapped by the given
  247   	 * persister from each row of the <tt>ResultSet</tt>. If an object is supplied, will attempt to
  248   	 * initialize that object. If a collection is supplied, attempt to initialize that collection.
  249   	 */
  250   	private List doQueryAndInitializeNonLazyCollections(final SessionImplementor session,
  251   														final QueryParameters queryParameters,
  252   														final boolean returnProxies) 
  253   		throws HibernateException, SQLException {
  254   
  255   		final PersistenceContext persistenceContext = session.getPersistenceContext();
  256   		persistenceContext.beforeLoad();
  257   		List result;
  258   		try {
  259   			result = doQuery( session, queryParameters, returnProxies );
  260   		}
  261   		finally {
  262   			persistenceContext.afterLoad();
  263   		}
  264   		persistenceContext.initializeNonLazyCollections();
  265   		return result;
  266   	}
  267   
  268   	/**
  269   	 * Loads a single row from the result set.  This is the processing used from the
  270   	 * ScrollableResults where no collection fetches were encountered.
  271   	 *
  272   	 * @param resultSet The result set from which to do the load.
  273   	 * @param session The session from which the request originated.
  274   	 * @param queryParameters The query parameters specified by the user.
  275   	 * @param returnProxies Should proxies be generated
  276   	 * @return The loaded "row".
  277   	 * @throws HibernateException
  278   	 */
  279   	public Object loadSingleRow(
  280   	        final ResultSet resultSet,
  281   	        final SessionImplementor session,
  282   	        final QueryParameters queryParameters,
  283   	        final boolean returnProxies) throws HibernateException {
  284   
  285   		final int entitySpan = getEntityPersisters().length;
  286   		final List hydratedObjects = entitySpan == 0 ? 
  287   				null : new ArrayList( entitySpan );
  288   
  289   		final Object result;
  290   		try {
  291   			result = getRowFromResultSet(
  292   			        resultSet,
  293   					session,
  294   					queryParameters,
  295   					getLockModes( queryParameters.getLockModes() ),
  296   					null,
  297   					hydratedObjects,
  298   					new EntityKey[entitySpan],
  299   					returnProxies
  300   				);
  301   		}
  302   		catch ( SQLException sqle ) {
  303   			throw JDBCExceptionHelper.convert(
  304   			        factory.getSQLExceptionConverter(),
  305   			        sqle,
  306   			        "could not read next row of results",
  307   			        getSQLString()
  308   				);
  309   		}
  310   
  311   		initializeEntitiesAndCollections( 
  312   				hydratedObjects, 
  313   				resultSet, 
  314   				session, 
  315   				queryParameters.isReadOnly() 
  316   			);
  317   		session.getPersistenceContext().initializeNonLazyCollections();
  318   		return result;
  319   	}
  320   
  321   	private Object sequentialLoad(
  322   	        final ResultSet resultSet,
  323   	        final SessionImplementor session,
  324   	        final QueryParameters queryParameters,
  325   	        final boolean returnProxies,
  326   	        final EntityKey keyToRead) throws HibernateException {
  327   
  328   		final int entitySpan = getEntityPersisters().length;
  329   		final List hydratedObjects = entitySpan == 0 ? 
  330   				null : new ArrayList( entitySpan );
  331   
  332   		Object result = null;
  333   		final EntityKey[] loadedKeys = new EntityKey[entitySpan];
  334   
  335   		try {
  336   			do {
  337   				Object loaded = getRowFromResultSet(
  338   						resultSet,
  339   						session,
  340   						queryParameters,
  341   						getLockModes( queryParameters.getLockModes() ),
  342   						null,
  343   						hydratedObjects,
  344   						loadedKeys,
  345   						returnProxies
  346   					);
  347   				if ( result == null ) {
  348   					result = loaded;
  349   				}
  350   			} 
  351   			while ( keyToRead.equals( loadedKeys[0] ) && resultSet.next() );
  352   		}
  353   		catch ( SQLException sqle ) {
  354   			throw JDBCExceptionHelper.convert(
  355   			        factory.getSQLExceptionConverter(),
  356   			        sqle,
  357   			        "could not perform sequential read of results (forward)",
  358   			        getSQLString()
  359   				);
  360   		}
  361   
  362   		initializeEntitiesAndCollections( 
  363   				hydratedObjects, 
  364   				resultSet, 
  365   				session, 
  366   				queryParameters.isReadOnly() 
  367   			);
  368   		session.getPersistenceContext().initializeNonLazyCollections();
  369   		return result;
  370   	}
  371   
  372   	/**
  373   	 * Loads a single logical row from the result set moving forward.  This is the
  374   	 * processing used from the ScrollableResults where there were collection fetches
  375   	 * encountered; thus a single logical row may have multiple rows in the underlying
  376   	 * result set.
  377   	 *
  378   	 * @param resultSet The result set from which to do the load.
  379   	 * @param session The session from which the request originated.
  380   	 * @param queryParameters The query parameters specified by the user.
  381   	 * @param returnProxies Should proxies be generated
  382   	 * @return The loaded "row".
  383   	 * @throws HibernateException
  384   	 */
  385   	public Object loadSequentialRowsForward(
  386   	        final ResultSet resultSet,
  387   	        final SessionImplementor session,
  388   	        final QueryParameters queryParameters,
  389   	        final boolean returnProxies) throws HibernateException {
  390   
  391   		// note that for sequential scrolling, we make the assumption that
  392   		// the first persister element is the "root entity"
  393   
  394   		try {
  395   			if ( resultSet.isAfterLast() ) {
  396   				// don't even bother trying to read further
  397   				return null;
  398   			}
  399   
  400   			if ( resultSet.isBeforeFirst() ) {
  401   				resultSet.next();
  402   			}
  403   
  404   			// We call getKeyFromResultSet() here so that we can know the
  405   			// key value upon which to perform the breaking logic.  However,
  406   			// it is also then called from getRowFromResultSet() which is certainly
  407   			// not the most efficient.  But the call here is needed, and there
  408   			// currently is no other way without refactoring of the doQuery()/getRowFromResultSet()
  409   			// methods
  410   			final EntityKey currentKey = getKeyFromResultSet(
  411   					0,
  412   					getEntityPersisters()[0],
  413   					null,
  414   					resultSet,
  415   					session
  416   				);
  417   
  418   			return sequentialLoad( resultSet, session, queryParameters, returnProxies, currentKey );
  419   		}
  420   		catch ( SQLException sqle ) {
  421   			throw JDBCExceptionHelper.convert(
  422   			        factory.getSQLExceptionConverter(),
  423   			        sqle,
  424   			        "could not perform sequential read of results (forward)",
  425   			        getSQLString()
  426   				);
  427   		}
  428   	}
  429   
  430   	/**
  431   	 * Loads a single logical row from the result set moving forward.  This is the
  432   	 * processing used from the ScrollableResults where there were collection fetches
  433   	 * encountered; thus a single logical row may have multiple rows in the underlying
  434   	 * result set.
  435   	 *
  436   	 * @param resultSet The result set from which to do the load.
  437   	 * @param session The session from which the request originated.
  438   	 * @param queryParameters The query parameters specified by the user.
  439   	 * @param returnProxies Should proxies be generated
  440   	 * @return The loaded "row".
  441   	 * @throws HibernateException
  442   	 */
  443   	public Object loadSequentialRowsReverse(
  444   	        final ResultSet resultSet,
  445   	        final SessionImplementor session,
  446   	        final QueryParameters queryParameters,
  447   	        final boolean returnProxies,
  448   	        final boolean isLogicallyAfterLast) throws HibernateException {
  449   
  450   		// note that for sequential scrolling, we make the assumption that
  451   		// the first persister element is the "root entity"
  452   
  453   		try {
  454   			if ( resultSet.isFirst() ) {
  455   				// don't even bother trying to read any further
  456   				return null;
  457   			}
  458   
  459   			EntityKey keyToRead = null;
  460   			// This check is needed since processing leaves the cursor
  461   			// after the last physical row for the current logical row;
  462   			// thus if we are after the last physical row, this might be
  463   			// caused by either:
  464   			//      1) scrolling to the last logical row
  465   			//      2) scrolling past the last logical row
  466   			// In the latter scenario, the previous logical row
  467   			// really is the last logical row.
  468   			//
  469   			// In all other cases, we should process back two
  470   			// logical records (the current logic row, plus the
  471   			// previous logical row).
  472   			if ( resultSet.isAfterLast() && isLogicallyAfterLast ) {
  473   				// position cursor to the last row
  474   				resultSet.last();
  475   				keyToRead = getKeyFromResultSet(
  476   						0,
  477   						getEntityPersisters()[0],
  478   						null,
  479   						resultSet,
  480   						session
  481   					);
  482   			}
  483   			else {
  484   				// Since the result set cursor is always left at the first
  485   				// physical row after the "last processed", we need to jump
  486   				// back one position to get the key value we are interested
  487   				// in skipping
  488   				resultSet.previous();
  489   
  490   				// sequentially read the result set in reverse until we recognize
  491   				// a change in the key value.  At that point, we are pointed at
  492   				// the last physical sequential row for the logical row in which
  493   				// we are interested in processing
  494   				boolean firstPass = true;
  495   				final EntityKey lastKey = getKeyFromResultSet(
  496   						0,
  497   						getEntityPersisters()[0],
  498   						null,
  499   						resultSet,
  500   						session
  501   					);
  502   				while ( resultSet.previous() ) {
  503   					EntityKey checkKey = getKeyFromResultSet(
  504   							0,
  505   							getEntityPersisters()[0],
  506   							null,
  507   							resultSet,
  508   							session
  509   						);
  510   
  511   					if ( firstPass ) {
  512   						firstPass = false;
  513   						keyToRead = checkKey;
  514   					}
  515   
  516   					if ( !lastKey.equals( checkKey ) ) {
  517   						break;
  518   					}
  519   				}
  520   
  521   			}
  522   
  523   			// Read backwards until we read past the first physical sequential
  524   			// row with the key we are interested in loading
  525   			while ( resultSet.previous() ) {
  526   				EntityKey checkKey = getKeyFromResultSet(
  527   						0,
  528   						getEntityPersisters()[0],
  529   						null,
  530   						resultSet,
  531   						session
  532   					);
  533   
  534   				if ( !keyToRead.equals( checkKey ) ) {
  535   					break;
  536   				}
  537   			}
  538   
  539   			// Finally, read ahead one row to position result set cursor
  540   			// at the first physical row we are interested in loading
  541   			resultSet.next();
  542   
  543   			// and perform the load
  544   			return sequentialLoad( resultSet, session, queryParameters, returnProxies, keyToRead );
  545   		}
  546   		catch ( SQLException sqle ) {
  547   			throw JDBCExceptionHelper.convert(
  548   			        factory.getSQLExceptionConverter(),
  549   			        sqle,
  550   			        "could not perform sequential read of results (forward)",
  551   			        getSQLString()
  552   				);
  553   		}
  554   	}
  555   
  556   	private static EntityKey getOptionalObjectKey(QueryParameters queryParameters, SessionImplementor session) {
  557   		final Object optionalObject = queryParameters.getOptionalObject();
  558   		final Serializable optionalId = queryParameters.getOptionalId();
  559   		final String optionalEntityName = queryParameters.getOptionalEntityName();
  560   
  561   		if ( optionalObject != null && optionalEntityName != null ) {
  562   			return new EntityKey( 
  563   					optionalId,
  564   					session.getEntityPersister( optionalEntityName, optionalObject ), 
  565   					session.getEntityMode()
  566   				);
  567   		}
  568   		else {
  569   			return null;
  570   		}
  571   
  572   	}
  573   
  574   	private Object getRowFromResultSet(
  575   	        final ResultSet resultSet,
  576   	        final SessionImplementor session,
  577   	        final QueryParameters queryParameters,
  578   	        final LockMode[] lockModeArray,
  579   	        final EntityKey optionalObjectKey,
  580   	        final List hydratedObjects,
  581   	        final EntityKey[] keys,
  582   	        boolean returnProxies) throws SQLException, HibernateException {
  583   
  584   		final Loadable[] persisters = getEntityPersisters();
  585   		final int entitySpan = persisters.length;
  586   
  587   		for ( int i = 0; i < entitySpan; i++ ) {
  588   			keys[i] = getKeyFromResultSet(
  589   			        i,
  590   					persisters[i],
  591   					i == entitySpan - 1 ?
  592   							queryParameters.getOptionalId() :
  593   							null,
  594   					resultSet,
  595   					session
  596   				);
  597   			//TODO: the i==entitySpan-1 bit depends upon subclass implementation (very bad)
  598   		}
  599   
  600   		registerNonExists( keys, persisters, session );
  601   
  602   		// this call is side-effecty
  603   		Object[] row = getRow(
  604   		        resultSet,
  605   				persisters,
  606   				keys,
  607   				queryParameters.getOptionalObject(),
  608   				optionalObjectKey,
  609   				lockModeArray,
  610   				hydratedObjects,
  611   				session
  612   		);
  613   
  614   		readCollectionElements( row, resultSet, session );
  615   
  616   		if ( returnProxies ) {
  617   			// now get an existing proxy for each row element (if there is one)
  618   			for ( int i = 0; i < entitySpan; i++ ) {
  619   				Object entity = row[i];
  620   				Object proxy = session.getPersistenceContext().proxyFor( persisters[i], keys[i], entity );
  621   				if ( entity != proxy ) {
  622   					// force the proxy to resolve itself
  623   					( (HibernateProxy) proxy ).getHibernateLazyInitializer().setImplementation(entity);
  624   					row[i] = proxy;
  625   				}
  626   			}
  627   		}
  628   
  629   		return getResultColumnOrRow( row, queryParameters.getResultTransformer(), resultSet, session );
  630   
  631   	}
  632   
  633   	/**
  634   	 * Read any collection elements contained in a single row of the result set
  635   	 */
  636   	private void readCollectionElements(Object[] row, ResultSet resultSet, SessionImplementor session)
  637   			throws SQLException, HibernateException {
  638   
  639   		//TODO: make this handle multiple collection roles!
  640   
  641   		final CollectionPersister[] collectionPersisters = getCollectionPersisters();
  642   		if ( collectionPersisters != null ) {
  643   
  644   			final CollectionAliases[] descriptors = getCollectionAliases();
  645   			final int[] collectionOwners = getCollectionOwners();
  646   
  647   			for ( int i=0; i<collectionPersisters.length; i++ ) {
  648   
  649   				final boolean hasCollectionOwners = collectionOwners !=null && 
  650   						collectionOwners[i] > -1;
  651   				//true if this is a query and we are loading multiple instances of the same collection role
  652   				//otherwise this is a CollectionInitializer and we are loading up a single collection or batch
  653   				
  654   				final Object owner = hasCollectionOwners ?
  655   						row[ collectionOwners[i] ] :
  656   						null; //if null, owner will be retrieved from session
  657   
  658   				final CollectionPersister collectionPersister = collectionPersisters[i];
  659   				final Serializable key;
  660   				if ( owner == null ) {
  661   					key = null;
  662   				}
  663   				else {
  664   					key = collectionPersister.getCollectionType().getKeyOfOwner( owner, session );
  665   					//TODO: old version did not require hashmap lookup:
  666   					//keys[collectionOwner].getIdentifier()
  667   				}
  668   	
  669   				readCollectionElement( 
  670   						owner, 
  671   						key, 
  672   						collectionPersister, 
  673   						descriptors[i], 
  674   						resultSet, 
  675   						session 
  676   					);
  677   				
  678   			}
  679   
  680   		}
  681   	}
  682   
  683   	private List doQuery(
  684   			final SessionImplementor session,
  685   			final QueryParameters queryParameters,
  686   			final boolean returnProxies) throws SQLException, HibernateException {
  687   
  688   		final RowSelection selection = queryParameters.getRowSelection();
  689   		final int maxRows = hasMaxRows( selection ) ?
  690   				selection.getMaxRows().intValue() :
  691   				Integer.MAX_VALUE;
  692   
  693   		final int entitySpan = getEntityPersisters().length;
  694   
  695   		final ArrayList hydratedObjects = entitySpan == 0 ? null : new ArrayList( entitySpan * 10 );
  696   		final PreparedStatement st = prepareQueryStatement( queryParameters, false, session );
  697   		final ResultSet rs = getResultSet( st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), selection, session );
  698   
  699   // would be great to move all this below here into another method that could also be used
  700   // from the new scrolling stuff.
  701   //
  702   // Would need to change the way the max-row stuff is handled (i.e. behind an interface) so
  703   // that I could do the control breaking at the means to know when to stop
  704   		final LockMode[] lockModeArray = getLockModes( queryParameters.getLockModes() );
  705   		final EntityKey optionalObjectKey = getOptionalObjectKey( queryParameters, session );
  706   
  707   		final boolean createSubselects = isSubselectLoadingEnabled();
  708   		final List subselectResultKeys = createSubselects ? new ArrayList() : null;
  709   		final List results = new ArrayList();
  710   
  711   		try {
  712   
  713   			handleEmptyCollections( queryParameters.getCollectionKeys(), rs, session );
  714   
  715   			EntityKey[] keys = new EntityKey[entitySpan]; //we can reuse it for each row
  716   
  717   			if ( log.isTraceEnabled() ) log.trace( "processing result set" );
  718   
  719   			int count;
  720   			for ( count = 0; count < maxRows && rs.next(); count++ ) {
  721   				
  722   				if ( log.isTraceEnabled() ) log.debug("result set row: " + count);
  723   
  724   				Object result = getRowFromResultSet( 
  725   						rs,
  726   						session,
  727   						queryParameters,
  728   						lockModeArray,
  729   						optionalObjectKey,
  730   						hydratedObjects,
  731   						keys,
  732   						returnProxies 
  733   				);
  734   				results.add( result );
  735   
  736   				if ( createSubselects ) {
  737   					subselectResultKeys.add(keys);
  738   					keys = new EntityKey[entitySpan]; //can't reuse in this case
  739   				}
  740   				
  741   			}
  742   
  743   			if ( log.isTraceEnabled() ) {
  744   				log.trace( "done processing result set (" + count + " rows)" );
  745   			}
  746   
  747   		}
  748   		finally {
  749   			session.getBatcher().closeQueryStatement( st, rs );
  750   		}
  751   
  752   		initializeEntitiesAndCollections( hydratedObjects, rs, session, queryParameters.isReadOnly() );
  753   
  754   		if ( createSubselects ) createSubselects( subselectResultKeys, queryParameters, session );
  755   
  756   		return results; //getResultList(results);
  757   
  758   	}
  759   
  760   	protected boolean isSubselectLoadingEnabled() {
  761   		return false;
  762   	}
  763   	
  764   	protected boolean hasSubselectLoadableCollections() {
  765   		final Loadable[] loadables = getEntityPersisters();
  766   		for (int i=0; i<loadables.length; i++ ) {
  767   			if ( loadables[i].hasSubselectLoadableCollections() ) return true;
  768   		}
  769   		return false;
  770   	}
  771   	
  772   	private static Set[] transpose( List keys ) {
  773   		Set[] result = new Set[ ( ( EntityKey[] ) keys.get(0) ).length ];
  774   		for ( int j=0; j<result.length; j++ ) {
  775   			result[j] = new HashSet( keys.size() );
  776   			for ( int i=0; i<keys.size(); i++ ) {
  777   				result[j].add( ( ( EntityKey[] ) keys.get(i) ) [j] );
  778   			}
  779   		}
  780   		return result;
  781   	}
  782   
  783   	private void createSubselects(List keys, QueryParameters queryParameters, SessionImplementor session) {
  784   		if ( keys.size() > 1 ) { //if we only returned one entity, query by key is more efficient
  785   			
  786   			Set[] keySets = transpose(keys);
  787   			
  788   			Map namedParameterLocMap = buildNamedParameterLocMap( queryParameters );
  789   			
  790   			final Loadable[] loadables = getEntityPersisters();
  791   			final String[] aliases = getAliases();
  792   			final Iterator iter = keys.iterator();
  793   			while ( iter.hasNext() ) {
  794   				
  795   				final EntityKey[] rowKeys = (EntityKey[]) iter.next();
  796   				for ( int i=0; i<rowKeys.length; i++ ) {
  797   					
  798   					if ( rowKeys[i]!=null && loadables[i].hasSubselectLoadableCollections() ) {
  799   						
  800   						SubselectFetch subselectFetch = new SubselectFetch( 
  801   								//getSQLString(), 
  802   								aliases[i], 
  803   								loadables[i], 
  804   								queryParameters, 
  805   								keySets[i],
  806   								namedParameterLocMap
  807   							);
  808   						
  809   						session.getPersistenceContext()
  810   								.getBatchFetchQueue()
  811   								.addSubselect( rowKeys[i], subselectFetch );
  812   					}
  813   					
  814   				}
  815   				
  816   			}
  817   		}
  818   	}
  819   
  820   	private Map buildNamedParameterLocMap(QueryParameters queryParameters) {
  821   		if ( queryParameters.getNamedParameters()!=null ) {
  822   			final Map namedParameterLocMap = new HashMap();
  823   			Iterator piter = queryParameters.getNamedParameters().keySet().iterator();
  824   			while ( piter.hasNext() ) {
  825   				String name = (String) piter.next();
  826   				namedParameterLocMap.put(
  827   						name,
  828   						getNamedParameterLocs(name)
  829   					);
  830   			}
  831   			return namedParameterLocMap;
  832   		}
  833   		else {
  834   			return null;
  835   		}
  836   	}
  837   
  838   	private void initializeEntitiesAndCollections(
  839   			final List hydratedObjects,
  840   			final Object resultSetId,
  841   			final SessionImplementor session,
  842   			final boolean readOnly) 
  843   	throws HibernateException {
  844   		
  845   		final CollectionPersister[] collectionPersisters = getCollectionPersisters();
  846   		if ( collectionPersisters != null ) {
  847   			for ( int i=0; i<collectionPersisters.length; i++ ) {
  848   				if ( collectionPersisters[i].isArray() ) {
  849   					//for arrays, we should end the collection load before resolving
  850   					//the entities, since the actual array instances are not instantiated
  851   					//during loading
  852   					//TODO: or we could do this polymorphically, and have two
  853   					//      different operations implemented differently for arrays
  854   					endCollectionLoad( resultSetId, session, collectionPersisters[i] );
  855   				}
  856   			}
  857   		}
  858   
  859   		//important: reuse the same event instances for performance!
  860   		final PreLoadEvent pre;
  861   		final PostLoadEvent post;
  862   		if ( session.isEventSource() ) {
  863   			pre = new PreLoadEvent( (EventSource) session );
  864   			post = new PostLoadEvent( (EventSource) session );
  865   		}
  866   		else {
  867   			pre = null;
  868   			post = null;
  869   		}
  870   		
  871   		if ( hydratedObjects!=null ) {
  872   			int hydratedObjectsSize = hydratedObjects.size();
  873   			if ( log.isTraceEnabled() ) {
  874   				log.trace( "total objects hydrated: " + hydratedObjectsSize );
  875   			}
  876   			for ( int i = 0; i < hydratedObjectsSize; i++ ) {
  877   				TwoPhaseLoad.initializeEntity( hydratedObjects.get(i), readOnly, session, pre, post );
  878   			}
  879   		}
  880   		
  881   		if ( collectionPersisters != null ) {
  882   			for ( int i=0; i<collectionPersisters.length; i++ ) {
  883   				if ( !collectionPersisters[i].isArray() ) {
  884   					//for sets, we should end the collection load after resolving
  885   					//the entities, since we might call hashCode() on the elements
  886   					//TODO: or we could do this polymorphically, and have two
  887   					//      different operations implemented differently for arrays
  888   					endCollectionLoad( resultSetId, session, collectionPersisters[i] );
  889   				}
  890   			}
  891   		}
  892   		
  893   	}
  894   
  895   	private void endCollectionLoad(
  896   			final Object resultSetId, 
  897   			final SessionImplementor session, 
  898   			final CollectionPersister collectionPersister) {
  899   		//this is a query and we are loading multiple instances of the same collection role
  900   		session.getPersistenceContext()
  901   				.getLoadContexts()
  902   				.getCollectionLoadContext( ( ResultSet ) resultSetId )
  903   				.endLoadingCollections( collectionPersister );
  904   	}
  905   
  906   	protected List getResultList(List results, ResultTransformer resultTransformer) throws QueryException {
  907   		return results;
  908   	}
  909   
  910   	/**
  911   	 * Get the actual object that is returned in the user-visible result list.
  912   	 * This empty implementation merely returns its first argument. This is
  913   	 * overridden by some subclasses.
  914   	 */
  915   	protected Object getResultColumnOrRow(Object[] row, ResultTransformer transformer, ResultSet rs, SessionImplementor session)
  916   			throws SQLException, HibernateException {
  917   		return row;
  918   	}
  919   
  920   	/**
  921   	 * For missing objects associated by one-to-one with another object in the
  922   	 * result set, register the fact that the the object is missing with the
  923   	 * session.
  924   	 */
  925   	private void registerNonExists(
  926   	        final EntityKey[] keys,
  927   	        final Loadable[] persisters,
  928   	        final SessionImplementor session) {
  929   		
  930   		final int[] owners = getOwners();
  931   		if ( owners != null ) {
  932   			
  933   			EntityType[] ownerAssociationTypes = getOwnerAssociationTypes();
  934   			for ( int i = 0; i < keys.length; i++ ) {
  935   				
  936   				int owner = owners[i];
  937   				if ( owner > -1 ) {
  938   					EntityKey ownerKey = keys[owner];
  939   					if ( keys[i] == null && ownerKey != null ) {
  940   						
  941   						final PersistenceContext persistenceContext = session.getPersistenceContext();
  942   						
  943   						/*final boolean isPrimaryKey;
  944   						final boolean isSpecialOneToOne;
  945   						if ( ownerAssociationTypes == null || ownerAssociationTypes[i] == null ) {
  946   							isPrimaryKey = true;
  947   							isSpecialOneToOne = false;
  948   						}
  949   						else {
  950   							isPrimaryKey = ownerAssociationTypes[i].getRHSUniqueKeyPropertyName()==null;
  951   							isSpecialOneToOne = ownerAssociationTypes[i].getLHSPropertyName()!=null;
  952   						}*/
  953   						
  954   						//TODO: can we *always* use the "null property" approach for everything?
  955   						/*if ( isPrimaryKey && !isSpecialOneToOne ) {
  956   							persistenceContext.addNonExistantEntityKey( 
  957   									new EntityKey( ownerKey.getIdentifier(), persisters[i], session.getEntityMode() ) 
  958   							);
  959   						}
  960   						else if ( isSpecialOneToOne ) {*/
  961   						boolean isOneToOneAssociation = ownerAssociationTypes!=null && 
  962   								ownerAssociationTypes[i]!=null && 
  963   								ownerAssociationTypes[i].isOneToOne();
  964   						if ( isOneToOneAssociation ) {
  965   							persistenceContext.addNullProperty( ownerKey, 
  966   									ownerAssociationTypes[i].getPropertyName() );
  967   						}
  968   						/*}
  969   						else {
  970   							persistenceContext.addNonExistantEntityUniqueKey( new EntityUniqueKey( 
  971   									persisters[i].getEntityName(),
  972   									ownerAssociationTypes[i].getRHSUniqueKeyPropertyName(),
  973   									ownerKey.getIdentifier(),
  974   									persisters[owner].getIdentifierType(),
  975   									session.getEntityMode()
  976   							) );
  977   						}*/
  978   					}
  979   				}
  980   			}
  981   		}
  982   	}
  983   
  984   	/**
  985   	 * Read one collection element from the current row of the JDBC result set
  986   	 */
  987   	private void readCollectionElement(
  988   	        final Object optionalOwner,
  989   	        final Serializable optionalKey,
  990   	        final CollectionPersister persister,
  991   	        final CollectionAliases descriptor,
  992   	        final ResultSet rs,
  993   	        final SessionImplementor session) 
  994   	throws HibernateException, SQLException {
  995   
  996   		final PersistenceContext persistenceContext = session.getPersistenceContext();
  997   
  998   		final Serializable collectionRowKey = (Serializable) persister.readKey( 
  999   				rs, 
 1000   				descriptor.getSuffixedKeyAliases(), 
 1001   				session 
 1002   			);
 1003   		
 1004   		if ( collectionRowKey != null ) {
 1005   			// we found a collection element in the result set
 1006   
 1007   			if ( log.isDebugEnabled() ) {
 1008   				log.debug( 
 1009   						"found row of collection: " +
 1010   						MessageHelper.collectionInfoString( persister, collectionRowKey, getFactory() ) 
 1011   					);
 1012   			}
 1013   
 1014   			Object owner = optionalOwner;
 1015   			if ( owner == null ) {
 1016   				owner = persistenceContext.getCollectionOwner( collectionRowKey, persister );
 1017   				if ( owner == null ) {
 1018   					//TODO: This is assertion is disabled because there is a bug that means the
 1019   					//	  original owner of a transient, uninitialized collection is not known
 1020   					//	  if the collection is re-referenced by a different object associated
 1021   					//	  with the current Session
 1022   					//throw new AssertionFailure("bug loading unowned collection");
 1023   				}
 1024   			}
 1025   
 1026   			PersistentCollection rowCollection = persistenceContext.getLoadContexts()
 1027   					.getCollectionLoadContext( rs )
 1028   					.getLoadingCollection( persister, collectionRowKey );
 1029   
 1030   			if ( rowCollection != null ) {
 1031   				rowCollection.readFrom( rs, persister, descriptor, owner );
 1032   			}
 1033   
 1034   		}
 1035   		else if ( optionalKey != null ) {
 1036   			// we did not find a collection element in the result set, so we
 1037   			// ensure that a collection is created with the owner's identifier,
 1038   			// since what we have is an empty collection
 1039   
 1040   			if ( log.isDebugEnabled() ) {
 1041   				log.debug( 
 1042   						"result set contains (possibly empty) collection: " +
 1043   						MessageHelper.collectionInfoString( persister, optionalKey, getFactory() ) 
 1044   					);
 1045   			}
 1046   
 1047   			persistenceContext.getLoadContexts()
 1048   					.getCollectionLoadContext( rs )
 1049   					.getLoadingCollection( persister, optionalKey ); // handle empty collection
 1050   
 1051   		}
 1052   
 1053   		// else no collection element, but also no owner
 1054   
 1055   	}
 1056   
 1057   	/**
 1058   	 * If this is a collection initializer, we need to tell the session that a collection
 1059   	 * is being initilized, to account for the possibility of the collection having
 1060   	 * no elements (hence no rows in the result set).
 1061   	 */
 1062   	private void handleEmptyCollections(
 1063   	        final Serializable[] keys,
 1064   	        final Object resultSetId,
 1065   	        final SessionImplementor session) {
 1066   
 1067   		if ( keys != null ) {
 1068   			// this is a collection initializer, so we must create a collection
 1069   			// for each of the passed-in keys, to account for the possibility
 1070   			// that the collection is empty and has no rows in the result set
 1071   
 1072   			CollectionPersister[] collectionPersisters = getCollectionPersisters();
 1073   			for ( int j=0; j<collectionPersisters.length; j++ ) {
 1074   				for ( int i = 0; i < keys.length; i++ ) {
 1075   					//handle empty collections
 1076   	
 1077   					if ( log.isDebugEnabled() ) {
 1078   						log.debug( 
 1079   								"result set contains (possibly empty) collection: " +
 1080   								MessageHelper.collectionInfoString( collectionPersisters[j], keys[i], getFactory() ) 
 1081   							);
 1082   					}
 1083   
 1084   					session.getPersistenceContext()
 1085   							.getLoadContexts()
 1086   							.getCollectionLoadContext( ( ResultSet ) resultSetId )
 1087   							.getLoadingCollection( collectionPersisters[j], keys[i] );
 1088   				}
 1089   			}
 1090   		}
 1091   
 1092   		// else this is not a collection initializer (and empty collections will
 1093   		// be detected by looking for the owner's identifier in the result set)
 1094   	}
 1095   
 1096   	/**
 1097   	 * Read a row of <tt>Key</tt>s from the <tt>ResultSet</tt> into the given array.
 1098   	 * Warning: this method is side-effecty.
 1099   	 * <p/>
 1100   	 * If an <tt>id</tt> is given, don't bother going to the <tt>ResultSet</tt>.
 1101   	 */
 1102   	private EntityKey getKeyFromResultSet(
 1103   	        final int i,
 1104   	        final Loadable persister,
 1105   	        final Serializable id,
 1106   	        final ResultSet rs,
 1107   	        final SessionImplementor session) throws HibernateException, SQLException {
 1108   
 1109   		Serializable resultId;
 1110   
 1111   		// if we know there is exactly 1 row, we can skip.
 1112   		// it would be great if we could _always_ skip this;
 1113   		// it is a problem for <key-many-to-one>
 1114   
 1115   		if ( isSingleRowLoader() && id != null ) {
 1116   			resultId = id;
 1117   		}
 1118   		else {
 1119   			
 1120   			Type idType = persister.getIdentifierType();
 1121   			resultId = (Serializable) idType.nullSafeGet(
 1122   					rs,
 1123   					getEntityAliases()[i].getSuffixedKeyAliases(),
 1124   					session,
 1125   					null //problematic for <key-many-to-one>!
 1126   				);
 1127   			
 1128   			final boolean idIsResultId = id != null && 
 1129   					resultId != null && 
 1130   					idType.isEqual( id, resultId, session.getEntityMode(), factory );
 1131   			
 1132   			if ( idIsResultId ) resultId = id; //use the id passed in
 1133   		}
 1134   
 1135   		return resultId == null ?
 1136   				null :
 1137   				new EntityKey( resultId, persister, session.getEntityMode() );
 1138   	}
 1139   
 1140   	/**
 1141   	 * Check the version of the object in the <tt>ResultSet</tt> against
 1142   	 * the object version in the session cache, throwing an exception
 1143   	 * if the version numbers are different
 1144   	 */
 1145   	private void checkVersion(
 1146   	        final int i,
 1147   	        final Loadable persister,
 1148   	        final Serializable id,
 1149   	        final Object entity,
 1150   	        final ResultSet rs,
 1151   	        final SessionImplementor session) 
 1152   	throws HibernateException, SQLException {
 1153   
 1154   		Object version = session.getPersistenceContext().getEntry( entity ).getVersion();
 1155   
 1156   		if ( version != null ) { //null version means the object is in the process of being loaded somewhere else in the ResultSet
 1157   			VersionType versionType = persister.getVersionType();
 1158   			Object currentVersion = versionType.nullSafeGet(
 1159   					rs,
 1160   					getEntityAliases()[i].getSuffixedVersionAliases(),
 1161   					session,
 1162   					null
 1163   				);
 1164   			if ( !versionType.isEqual(version, currentVersion) ) {
 1165   				if ( session.getFactory().getStatistics().isStatisticsEnabled() ) {
 1166   					session.getFactory().getStatisticsImplementor()
 1167   							.optimisticFailure( persister.getEntityName() );
 1168   				}
 1169   				throw new StaleObjectStateException( persister.getEntityName(), id );
 1170   			}
 1171   		}
 1172   
 1173   	}
 1174   
 1175   	/**
 1176   	 * Resolve any ids for currently loaded objects, duplications within the
 1177   	 * <tt>ResultSet</tt>, etc. Instantiate empty objects to be initialized from the
 1178   	 * <tt>ResultSet</tt>. Return an array of objects (a row of results) and an
 1179   	 * array of booleans (by side-effect) that determine whether the corresponding
 1180   	 * object should be initialized.
 1181   	 */
 1182   	private Object[] getRow(
 1183   	        final ResultSet rs,
 1184   	        final Loadable[] persisters,
 1185   	        final EntityKey[] keys,
 1186   	        final Object optionalObject,
 1187   	        final EntityKey optionalObjectKey,
 1188   	        final LockMode[] lockModes,
 1189   	        final List hydratedObjects,
 1190   	        final SessionImplementor session) 
 1191   	throws HibernateException, SQLException {
 1192   
 1193   		final int cols = persisters.length;
 1194   		final EntityAliases[] descriptors = getEntityAliases();
 1195   
 1196   		if ( log.isDebugEnabled() ) {
 1197   			log.debug( 
 1198   					"result row: " + 
 1199   					StringHelper.toString( keys ) 
 1200   				);
 1201   		}
 1202   
 1203   		final Object[] rowResults = new Object[cols];
 1204   
 1205   		for ( int i = 0; i < cols; i++ ) {
 1206   
 1207   			Object object = null;
 1208   			EntityKey key = keys[i];
 1209   
 1210   			if ( keys[i] == null ) {
 1211   				//do nothing
 1212   			}
 1213   			else {
 1214   
 1215   				//If the object is already loaded, return the loaded one
 1216   				object = session.getEntityUsingInterceptor( key );
 1217   				if ( object != null ) {
 1218   					//its already loaded so don't need to hydrate it
 1219   					instanceAlreadyLoaded( 
 1220   							rs,
 1221   							i,
 1222   							persisters[i],
 1223   							key,
 1224   							object,
 1225   							lockModes[i],
 1226   							session 
 1227   						);
 1228   				}
 1229   				else {
 1230   					object = instanceNotYetLoaded( 
 1231   							rs,
 1232   							i,
 1233   							persisters[i],
 1234   							descriptors[i].getRowIdAlias(),
 1235   							key,
 1236   							lockModes[i],
 1237   							optionalObjectKey,
 1238   							optionalObject,
 1239   							hydratedObjects,
 1240   							session 
 1241   						);
 1242   				}
 1243   
 1244   			}
 1245   
 1246   			rowResults[i] = object;
 1247   
 1248   		}
 1249   
 1250   		return rowResults;
 1251   	}
 1252   
 1253   	/**
 1254   	 * The entity instance is already in the session cache
 1255   	 */
 1256   	private void instanceAlreadyLoaded(
 1257   	        final ResultSet rs,
 1258   	        final int i,
 1259   	        final Loadable persister,
 1260   	        final EntityKey key,
 1261   	        final Object object,
 1262   	        final LockMode lockMode,
 1263   	        final SessionImplementor session) 
 1264   	throws HibernateException, SQLException {
 1265   
 1266   		if ( !persister.isInstance( object, session.getEntityMode() ) ) {
 1267   			throw new WrongClassException( 
 1268   					"loaded object was of wrong class " + object.getClass(), 
 1269   					key.getIdentifier(), 
 1270   					persister.getEntityName() 
 1271   				);
 1272   		}
 1273   
 1274   		if ( LockMode.NONE != lockMode && upgradeLocks() ) { //no point doing this if NONE was requested
 1275   
 1276   			final boolean isVersionCheckNeeded = persister.isVersioned() &&
 1277   					session.getPersistenceContext().getEntry(object)
 1278   							.getLockMode().lessThan( lockMode );
 1279   			// we don't need to worry about existing version being uninitialized
 1280   			// because this block isn't called by a re-entrant load (re-entrant
 1281   			// loads _always_ have lock mode NONE)
 1282   			if (isVersionCheckNeeded) {
 1283   				//we only check the version when _upgrading_ lock modes
 1284   				checkVersion( i, persister, key.getIdentifier(), object, rs, session );
 1285   				//we need to upgrade the lock mode to the mode requested
 1286   				session.getPersistenceContext().getEntry(object)
 1287   						.setLockMode(lockMode);
 1288   			}
 1289   		}
 1290   	}
 1291   
 1292   	/**
 1293   	 * The entity instance is not in the session cache
 1294   	 */
 1295   	private Object instanceNotYetLoaded(
 1296   	        final ResultSet rs,
 1297   	        final int i,
 1298   	        final Loadable persister,
 1299   	        final String rowIdAlias,
 1300   	        final EntityKey key,
 1301   	        final LockMode lockMode,
 1302   	        final EntityKey optionalObjectKey,
 1303   	        final Object optionalObject,
 1304   	        final List hydratedObjects,
 1305   	        final SessionImplementor session) 
 1306   	throws HibernateException, SQLException {
 1307   
 1308   		final String instanceClass = getInstanceClass( 
 1309   				rs, 
 1310   				i, 
 1311   				persister, 
 1312   				key.getIdentifier(), 
 1313   				session 
 1314   			);
 1315   
 1316   		final Object object;
 1317   		if ( optionalObjectKey != null && key.equals( optionalObjectKey ) ) {
 1318   			//its the given optional object
 1319   			object = optionalObject;
 1320   		}
 1321   		else {
 1322   			// instantiate a new instance
 1323   			object = session.instantiate( instanceClass, key.getIdentifier() );
 1324   		}
 1325   
 1326   		//need to hydrate it.
 1327   
 1328   		// grab its state from the ResultSet and keep it in the Session
 1329   		// (but don't yet initialize the object itself)
 1330   		// note that we acquire LockMode.READ even if it was not requested
 1331   		LockMode acquiredLockMode = lockMode == LockMode.NONE ? LockMode.READ : lockMode;
 1332   		loadFromResultSet( 
 1333   				rs, 
 1334   				i, 
 1335   				object, 
 1336   				instanceClass, 
 1337   				key, 
 1338   				rowIdAlias, 
 1339   				acquiredLockMode, 
 1340   				persister, 
 1341   				session 
 1342   			);
 1343   
 1344   		//materialize associations (and initialize the object) later
 1345   		hydratedObjects.add( object );
 1346   
 1347   		return object;
 1348   	}
 1349   	
 1350   	private boolean isEagerPropertyFetchEnabled(int i) {
 1351   		boolean[] array = getEntityEagerPropertyFetches();
 1352   		return array!=null && array[i];
 1353   	}
 1354   
 1355   
 1356   	/**
 1357   	 * Hydrate the state an object from the SQL <tt>ResultSet</tt>, into
 1358   	 * an array or "hydrated" values (do not resolve associations yet),
 1359   	 * and pass the hydrates state to the session.
 1360   	 */
 1361   	private void loadFromResultSet(
 1362   	        final ResultSet rs,
 1363   	        final int i,
 1364   	        final Object object,
 1365   	        final String instanceEntityName,
 1366   	        final EntityKey key,
 1367   	        final String rowIdAlias,
 1368   	        final LockMode lockMode,
 1369   	        final Loadable rootPersister,
 1370   	        final SessionImplementor session) 
 1371   	throws SQLException, HibernateException {
 1372   
 1373   		final Serializable id = key.getIdentifier();
 1374   
 1375   		// Get the persister for the _subclass_
 1376   		final Loadable persister = (Loadable) getFactory().getEntityPersister( instanceEntityName );
 1377   
 1378   		if ( log.isTraceEnabled() ) {
 1379   			log.trace( 
 1380   					"Initializing object from ResultSet: " + 
 1381   					MessageHelper.infoString( persister, id, getFactory() ) 
 1382   				);
 1383   		}
 1384   		
 1385   		boolean eagerPropertyFetch = isEagerPropertyFetchEnabled(i);
 1386   
 1387   		// add temp entry so that the next step is circular-reference
 1388   		// safe - only needed because some types don't take proper
 1389   		// advantage of two-phase-load (esp. components)
 1390   		TwoPhaseLoad.addUninitializedEntity( 
 1391   				key, 
 1392   				object, 
 1393   				persister, 
 1394   				lockMode, 
 1395   				!eagerPropertyFetch, 
 1396   				session 
 1397   			);
 1398   
 1399   		//This is not very nice (and quite slow):
 1400   		final String[][] cols = persister == rootPersister ?
 1401   				getEntityAliases()[i].getSuffixedPropertyAliases() :
 1402   				getEntityAliases()[i].getSuffixedPropertyAliases(persister);
 1403   
 1404   		final Object[] values = persister.hydrate( 
 1405   				rs, 
 1406   				id, 
 1407   				object, 
 1408   				rootPersister, 
 1409   				cols, 
 1410   				eagerPropertyFetch, 
 1411   				session 
 1412   			);
 1413   
 1414   		final Object rowId = persister.hasRowId() ? rs.getObject(rowIdAlias) : null;
 1415   
 1416   		final AssociationType[] ownerAssociationTypes = getOwnerAssociationTypes();
 1417   		if ( ownerAssociationTypes != null && ownerAssociationTypes[i] != null ) {
 1418   			String ukName = ownerAssociationTypes[i].getRHSUniqueKeyPropertyName();
 1419   			if (ukName!=null) {
 1420   				final int index = ( (UniqueKeyLoadable) persister ).getPropertyIndex(ukName);
 1421   				final Type type = persister.getPropertyTypes()[index];
 1422   	
 1423   				// polymorphism not really handled completely correctly,
 1424   				// perhaps...well, actually its ok, assuming that the
 1425   				// entity name used in the lookup is the same as the
 1426   				// the one used here, which it will be
 1427   	
 1428   				EntityUniqueKey euk = new EntityUniqueKey( 
 1429   						rootPersister.getEntityName(), //polymorphism comment above
 1430   						ukName,
 1431   						type.semiResolve( values[index], session, object ),
 1432   						type,
 1433   						session.getEntityMode(), session.getFactory()
 1434   					);
 1435   				session.getPersistenceContext().addEntity( euk, object );
 1436   			}
 1437   		}
 1438   
 1439   		TwoPhaseLoad.postHydrate( 
 1440   				persister, 
 1441   				id, 
 1442   				values, 
 1443   				rowId, 
 1444   				object, 
 1445   				lockMode, 
 1446   				!eagerPropertyFetch, 
 1447   				session 
 1448   			);
 1449   
 1450   	}
 1451   
 1452   	/**
 1453   	 * Determine the concrete class of an instance in the <tt>ResultSet</tt>
 1454   	 */
 1455   	private String getInstanceClass(
 1456   	        final ResultSet rs,
 1457   	        final int i,
 1458   	        final Loadable persister,
 1459   	        final Serializable id,
 1460   	        final SessionImplementor session) 
 1461   	throws HibernateException, SQLException {
 1462   
 1463   		if ( persister.hasSubclasses() ) {
 1464   
 1465   			// Code to handle subclasses of topClass
 1466   			Object discriminatorValue = persister.getDiscriminatorType().nullSafeGet(
 1467   					rs,
 1468   					getEntityAliases()[i].getSuffixedDiscriminatorAlias(),
 1469   					session,
 1470   					null
 1471   				);
 1472   
 1473   			final String result = persister.getSubclassForDiscriminatorValue( discriminatorValue );
 1474   
 1475   			if ( result == null ) {
 1476   				//woops we got an instance of another class hierarchy branch
 1477   				throw new WrongClassException( 
 1478   						"Discriminator: " + discriminatorValue,
 1479   						id,
 1480   						persister.getEntityName() 
 1481   					);
 1482   			}
 1483   
 1484   			return result;
 1485   
 1486   		}
 1487   		else {
 1488   			return persister.getEntityName();
 1489   		}
 1490   	}
 1491   
 1492   	/**
 1493   	 * Advance the cursor to the first required row of the <tt>ResultSet</tt>
 1494   	 */
 1495   	private void advance(final ResultSet rs, final RowSelection selection)
 1496   			throws SQLException {
 1497   
 1498   		final int firstRow = getFirstRow( selection );
 1499   		if ( firstRow != 0 ) {
 1500   			if ( getFactory().getSettings().isScrollableResultSetsEnabled() ) {
 1501   				// we can go straight to the first required row
 1502   				rs.absolute( firstRow );
 1503   			}
 1504   			else {
 1505   				// we need to step through the rows one row at a time (slow)
 1506   				for ( int m = 0; m < firstRow; m++ ) rs.next();
 1507   			}
 1508   		}
 1509   	}
 1510   
 1511   	private static boolean hasMaxRows(RowSelection selection) {
 1512   		return selection != null && selection.getMaxRows() != null;
 1513   	}
 1514   
 1515   	private static int getFirstRow(RowSelection selection) {
 1516   		if ( selection == null || selection.getFirstRow() == null ) {
 1517   			return 0;
 1518   		}
 1519   		else {
 1520   			return selection.getFirstRow().intValue();
 1521   		}
 1522   	}
 1523   
 1524   	/**
 1525   	 * Should we pre-process the SQL string, adding a dialect-specific
 1526   	 * LIMIT clause.
 1527   	 */
 1528   	private static boolean useLimit(final RowSelection selection, final Dialect dialect) {
 1529   		return dialect.supportsLimit() && hasMaxRows( selection );
 1530   	}
 1531   
 1532   	/**
 1533   	 * Obtain a <tt>PreparedStatement</tt> with all parameters pre-bound.
 1534   	 * Bind JDBC-style <tt>?</tt> parameters, named parameters, and
 1535   	 * limit parameters.
 1536   	 */
 1537   	protected final PreparedStatement prepareQueryStatement(
 1538   	        final QueryParameters queryParameters,
 1539   	        final boolean scroll,
 1540   	        final SessionImplementor session) throws SQLException, HibernateException {
 1541   
 1542   		queryParameters.processFilters( getSQLString(), session );
 1543   		String sql = queryParameters.getFilteredSQL();
 1544   		final Dialect dialect = getFactory().getDialect();
 1545   		final RowSelection selection = queryParameters.getRowSelection();
 1546   		boolean useLimit = useLimit( selection, dialect );
 1547   		boolean hasFirstRow = getFirstRow( selection ) > 0;
 1548   		boolean useOffset = hasFirstRow && useLimit && dialect.supportsLimitOffset();
 1549   		boolean callable = queryParameters.isCallable();
 1550   		
 1551   		boolean useScrollableResultSetToSkip = hasFirstRow &&
 1552   				!useOffset &&
 1553   				getFactory().getSettings().isScrollableResultSetsEnabled();
 1554   		ScrollMode scrollMode = scroll ? queryParameters.getScrollMode() : ScrollMode.SCROLL_INSENSITIVE;
 1555   
 1556   		if ( useLimit ) {
 1557   			sql = dialect.getLimitString( 
 1558   					sql.trim(), //use of trim() here is ugly?
 1559   					useOffset ? getFirstRow(selection) : 0, 
 1560   					getMaxOrLimit(selection, dialect) 
 1561   				);
 1562   		}
 1563   
 1564   		sql = preprocessSQL( sql, queryParameters, dialect );
 1565   		
 1566   		PreparedStatement st = null;
 1567   		
 1568   		if (callable) {
 1569   			st = session.getBatcher()
 1570   				.prepareCallableQueryStatement( sql, scroll || useScrollableResultSetToSkip, scrollMode );
 1571   		} 
 1572   		else {
 1573   			st = session.getBatcher()
 1574   				.prepareQueryStatement( sql, scroll || useScrollableResultSetToSkip, scrollMode );
 1575   		}
 1576   				
 1577   
 1578   		try {
 1579   
 1580   			int col = 1;
 1581   			//TODO: can we limit stored procedures ?!
 1582   			if ( useLimit && dialect.bindLimitParametersFirst() ) {
 1583   				col += bindLimitParameters( st, col, selection );
 1584   			}
 1585   			if (callable) {
 1586   				col = dialect.registerResultSetOutParameter( (CallableStatement)st, col );
 1587   			}
 1588   
 1589   			col += bindParameterValues( st, queryParameters, col, session );
 1590   
 1591   			if ( useLimit && !dialect.bindLimitParametersFirst() ) {
 1592   				col += bindLimitParameters( st, col, selection );
 1593   			}
 1594   
 1595   			if ( !useLimit ) {
 1596   				setMaxRows( st, selection );
 1597   			}
 1598   
 1599   			if ( selection != null ) {
 1600   				if ( selection.getTimeout() != null ) {
 1601   					st.setQueryTimeout( selection.getTimeout().intValue() );
 1602   				}
 1603   				if ( selection.getFetchSize() != null ) {
 1604   					st.setFetchSize( selection.getFetchSize().intValue() );
 1605   				}
 1606   			}
 1607   		}
 1608   		catch ( SQLException sqle ) {
 1609   			session.getBatcher().closeQueryStatement( st, null );
 1610   			throw sqle;
 1611   		}
 1612   		catch ( HibernateException he ) {
 1613   			session.getBatcher().closeQueryStatement( st, null );
 1614   			throw he;
 1615   		}
 1616   
 1617   		return st;
 1618   	}
 1619   
 1620   	/**
 1621   	 * Some dialect-specific LIMIT clauses require the maximium last row number
 1622   	 * (aka, first_row_number + total_row_count), while others require the maximum
 1623   	 * returned row count (the total maximum number of rows to return).
 1624   	 *
 1625   	 * @param selection The selection criteria
 1626   	 * @param dialect The dialect
 1627   	 * @return The appropriate value to bind into the limit clause.
 1628   	 */
 1629   	private static int getMaxOrLimit(final RowSelection selection, final Dialect dialect) {
 1630   		final int firstRow = getFirstRow( selection );
 1631   		final int lastRow = selection.getMaxRows().intValue();
 1632   		if ( dialect.useMaxForLimit() ) {
 1633   			return lastRow + firstRow;
 1634   		}
 1635   		else {
 1636   			return lastRow;
 1637   		}
 1638   	}
 1639   
 1640   	/**
 1641   	 * Bind parameter values needed by the dialect-specific LIMIT clause.
 1642   	 *
 1643   	 * @param statement The statement to which to bind limit param values.
 1644   	 * @param index The bind position from which to start binding
 1645   	 * @param selection The selection object containing the limit information.
 1646   	 * @return The number of parameter values bound.
 1647   	 * @throws java.sql.SQLException Indicates problems binding parameter values.
 1648   	 */
 1649   	private int bindLimitParameters(
 1650   			final PreparedStatement statement,
 1651   			final int index,
 1652   			final RowSelection selection) throws SQLException {
 1653   		Dialect dialect = getFactory().getDialect();
 1654   		if ( !dialect.supportsVariableLimit() ) {
 1655   			return 0;
 1656   		}
 1657   		if ( !hasMaxRows( selection ) ) {
 1658   			throw new AssertionFailure( "no max results set" );
 1659   		}
 1660   		int firstRow = getFirstRow( selection );
 1661   		int lastRow = getMaxOrLimit( selection, dialect );
 1662   		boolean hasFirstRow = firstRow > 0 && dialect.supportsLimitOffset();
 1663   		boolean reverse = dialect.bindLimitParametersInReverseOrder();
 1664   		if ( hasFirstRow ) {
 1665   			statement.setInt( index + ( reverse ? 1 : 0 ), firstRow );
 1666   		}
 1667   		statement.setInt( index + ( reverse || !hasFirstRow ? 0 : 1 ), lastRow );
 1668   		return hasFirstRow ? 2 : 1;
 1669   	}
 1670   
 1671   	/**
 1672   	 * Use JDBC API to limit the number of rows returned by the SQL query if necessary
 1673   	 */
 1674   	private void setMaxRows(
 1675   			final PreparedStatement st,
 1676   			final RowSelection selection) throws SQLException {
 1677   		if ( hasMaxRows( selection ) ) {
 1678   			st.setMaxRows( selection.getMaxRows().intValue() + getFirstRow( selection ) );
 1679   		}
 1680   	}
 1681   
 1682   	/**
 1683   	 * Bind all parameter values into the prepared statement in preparation
 1684   	 * for execution.
 1685   	 *
 1686   	 * @param statement The JDBC prepared statement
 1687   	 * @param queryParameters The encapsulation of the parameter values to be bound.
 1688   	 * @param startIndex The position from which to start binding parameter values.
 1689   	 * @param session The originating session.
 1690   	 * @return The number of JDBC bind positions actually bound during this method execution.
 1691   	 * @throws SQLException Indicates problems performing the binding.
 1692   	 */
 1693   	protected int bindParameterValues(
 1694   			PreparedStatement statement,
 1695   			QueryParameters queryParameters,
 1696   			int startIndex,
 1697   			SessionImplementor session) throws SQLException {
 1698   		int span = 0;
 1699   		span += bindPositionalParameters( statement, queryParameters, startIndex, session );
 1700   		span += bindNamedParameters( statement, queryParameters.getNamedParameters(), startIndex + span, session );
 1701   		return span;
 1702   	}
 1703   
 1704   	/**
 1705   	 * Bind positional parameter values to the JDBC prepared statement.
 1706   	 * <p/>
 1707   	 * Postional parameters are those specified by JDBC-style ? parameters
 1708   	 * in the source query.  It is (currently) expected that these come
 1709   	 * before any named parameters in the source query.
 1710   	 *
 1711   	 * @param statement The JDBC prepared statement
 1712   	 * @param queryParameters The encapsulation of the parameter values to be bound.
 1713   	 * @param startIndex The position from which to start binding parameter values.
 1714   	 * @param session The originating session.
 1715   	 * @return The number of JDBC bind positions actually bound during this method execution.
 1716   	 * @throws SQLException Indicates problems performing the binding.
 1717   	 * @throws org.hibernate.HibernateException Indicates problems delegating binding to the types.
 1718   	 */
 1719   	protected int bindPositionalParameters(
 1720   	        final PreparedStatement statement,
 1721   	        final QueryParameters queryParameters,
 1722   	        final int startIndex,
 1723   	        final SessionImplementor session) throws SQLException, HibernateException {
 1724   		final Object[] values = queryParameters.getFilteredPositionalParameterValues();
 1725   		final Type[] types = queryParameters.getFilteredPositionalParameterTypes();
 1726   		int span = 0;
 1727   		for ( int i = 0; i < values.length; i++ ) {
 1728   			types[i].nullSafeSet( statement, values[i], startIndex + span, session );
 1729   			span += types[i].getColumnSpan( getFactory() );
 1730   		}
 1731   		return span;
 1732   	}
 1733   
 1734   	/**
 1735   	 * Bind named parameters to the JDBC prepared statement.
 1736   	 * <p/>
 1737   	 * This is a generic implementation, the problem being that in the
 1738   	 * general case we do not know enough information about the named
 1739   	 * parameters to perform this in a complete manner here.  Thus this
 1740   	 * is generally overridden on subclasses allowing named parameters to
 1741   	 * apply the specific behavior.  The most usual limitation here is that
 1742   	 * we need to assume the type span is always one...
 1743   	 *
 1744   	 * @param statement The JDBC prepared statement
 1745   	 * @param namedParams A map of parameter names to values
 1746   	 * @param startIndex The position from which to start binding parameter values.
 1747   	 * @param session The originating session.
 1748   	 * @return The number of JDBC bind positions actually bound during this method execution.
 1749   	 * @throws SQLException Indicates problems performing the binding.
 1750   	 * @throws org.hibernate.HibernateException Indicates problems delegating binding to the types.
 1751   	 */
 1752   	protected int bindNamedParameters(
 1753   			final PreparedStatement statement,
 1754   			final Map namedParams,
 1755   			final int startIndex,
 1756   			final SessionImplementor session) throws SQLException, HibernateException {
 1757   		if ( namedParams != null ) {
 1758   			// assumes that types are all of span 1
 1759   			Iterator iter = namedParams.entrySet().iterator();
 1760   			int result = 0;
 1761   			while ( iter.hasNext() ) {
 1762   				Map.Entry e = ( Map.Entry ) iter.next();
 1763   				String name = ( String ) e.getKey();
 1764   				TypedValue typedval = ( TypedValue ) e.getValue();
 1765   				int[] locs = getNamedParameterLocs( name );
 1766   				for ( int i = 0; i < locs.length; i++ ) {
 1767   					if ( log.isDebugEnabled() ) {
 1768   						log.debug(
 1769   								"bindNamedParameters() " +
 1770   								typedval.getValue() + " -> " + name +
 1771   								" [" + ( locs[i] + startIndex ) + "]"
 1772   							);
 1773   					}
 1774   					typedval.getType().nullSafeSet( statement, typedval.getValue(), locs[i] + startIndex, session );
 1775   				}
 1776   				result += locs.length;
 1777   			}
 1778   			return result;
 1779   		}
 1780   		else {
 1781   			return 0;
 1782   		}
 1783   	}
 1784   
 1785   	public int[] getNamedParameterLocs(String name) {
 1786   		throw new AssertionFailure("no named parameters");
 1787   	}
 1788   
 1789   	/**
 1790   	 * Fetch a <tt>PreparedStatement</tt>, call <tt>setMaxRows</tt> and then execute it,
 1791   	 * advance to the first result and return an SQL <tt>ResultSet</tt>
 1792   	 */
 1793   	protected final ResultSet getResultSet(
 1794   	        final PreparedStatement st,
 1795   	        final boolean autodiscovertypes,
 1796   	        final boolean callable,
 1797   	        final RowSelection selection,
 1798   	        final SessionImplementor session) 
 1799   	throws SQLException, HibernateException {
 1800   	
 1801   		ResultSet rs = null;
 1802   		try {
 1803   			Dialect dialect = getFactory().getDialect();
 1804   			if (callable) {
 1805   				rs = session.getBatcher().getResultSet( (CallableStatement) st, dialect );
 1806   			} 
 1807   			else {
 1808   				rs = session.getBatcher().getResultSet( st );
 1809   			}
 1810   			rs = wrapResultSetIfEnabled( rs , session );
 1811   			
 1812   			if ( !dialect.supportsLimitOffset() || !useLimit( selection, dialect ) ) {
 1813   				advance( rs, selection );
 1814   			}
 1815   			
 1816   			if ( autodiscovertypes ) {
 1817   				autoDiscoverTypes( rs );
 1818   			}
 1819   			return rs;
 1820   		}
 1821   		catch ( SQLException sqle ) {
 1822   			session.getBatcher().closeQueryStatement( st, rs );
 1823   			throw sqle;
 1824   		}
 1825   	}
 1826   
 1827   	protected void autoDiscoverTypes(ResultSet rs) {
 1828   		throw new AssertionFailure("Auto discover types not supported in this loader");
 1829   		
 1830   	}
 1831   
 1832   	private synchronized ResultSet wrapResultSetIfEnabled(final ResultSet rs, final SessionImplementor session) {
 1833   		// synchronized to avoid multi-thread access issues; defined as method synch to avoid
 1834   		// potential deadlock issues due to nature of code.
 1835   		if ( session.getFactory().getSettings().isWrapResultSetsEnabled() ) {
 1836   			try {
 1837   				log.debug("Wrapping result set [" + rs + "]");
 1838   				return new ResultSetWrapper( rs, retreiveColumnNameToIndexCache( rs ) );
 1839   			}
 1840   			catch(SQLException e) {
 1841   				log.info("Error wrapping result set", e);
 1842   				return rs;
 1843   			}
 1844   		}
 1845   		else {
 1846   			return rs;
 1847   		}
 1848   	}
 1849   
 1850   	private ColumnNameCache retreiveColumnNameToIndexCache(ResultSet rs) throws SQLException {
 1851   		if ( columnNameCache == null ) {
 1852   			log.trace("Building columnName->columnIndex cache");
 1853   			columnNameCache = new ColumnNameCache( rs.getMetaData().getColumnCount() );
 1854   		}
 1855   
 1856   		return columnNameCache;
 1857   	}
 1858   
 1859   	/**
 1860   	 * Called by subclasses that load entities
 1861   	 * @param persister only needed for logging
 1862   	 */
 1863   	protected final List loadEntity(
 1864   	        final SessionImplementor session,
 1865   	        final Object id,
 1866   	        final Type identifierType,
 1867   	        final Object optionalObject,
 1868   	        final String optionalEntityName,
 1869   	        final Serializable optionalIdentifier,
 1870   	        final EntityPersister persister) throws HibernateException {
 1871   		
 1872   		if ( log.isDebugEnabled() ) {
 1873   			log.debug( 
 1874   					"loading entity: " + 
 1875   					MessageHelper.infoString( persister, id, identifierType, getFactory() ) 
 1876   				);
 1877   		}
 1878   
 1879   		List result;
 1880   		try {
 1881   			result = doQueryAndInitializeNonLazyCollections( 
 1882   					session,
 1883   					new QueryParameters( 
 1884   							new Type[] { identifierType },
 1885   							new Object[] { id },
 1886   							optionalObject,
 1887   							optionalEntityName,
 1888   							optionalIdentifier 
 1889   						),
 1890   					false 
 1891   				);
 1892   		}
 1893   		catch ( SQLException sqle ) {
 1894   			final Loadable[] persisters = getEntityPersisters();
 1895   			throw JDBCExceptionHelper.convert(
 1896   			        factory.getSQLExceptionConverter(),
 1897   			        sqle,
 1898   			        "could not load an entity: " + 
 1899   			        MessageHelper.infoString( persisters[persisters.length-1], id, identifierType, getFactory() ),
 1900   			        getSQLString()
 1901   				);
 1902   		}
 1903   
 1904   		log.debug("done entity load");
 1905   		
 1906   		return result;
 1907   		
 1908   	}
 1909   
 1910   	/**
 1911   	 * Called by subclasses that load entities
 1912   	 * @param persister only needed for logging
 1913   	 */
 1914   	protected final List loadEntity(
 1915   	        final SessionImplementor session,
 1916   	        final Object key,
 1917   	        final Object index,
 1918   	        final Type keyType,
 1919   	        final Type indexType,
 1920   	        final EntityPersister persister) throws HibernateException {
 1921   		
 1922   		if ( log.isDebugEnabled() ) {
 1923   			log.debug( "loading collection element by index" );
 1924   		}
 1925   
 1926   		List result;
 1927   		try {
 1928   			result = doQueryAndInitializeNonLazyCollections( 
 1929   					session,
 1930   					new QueryParameters( 
 1931   							new Type[] { keyType, indexType },
 1932   							new Object[] { key, index }
 1933   						),
 1934   					false 
 1935   				);
 1936   		}
 1937   		catch ( SQLException sqle ) {
 1938   			throw JDBCExceptionHelper.convert(
 1939   			        factory.getSQLExceptionConverter(),
 1940   			        sqle,
 1941   			        "could not collection element by index",
 1942   			        getSQLString()
 1943   				);
 1944   		}
 1945   
 1946   		log.debug("done entity load");
 1947   		
 1948   		return result;
 1949   		
 1950   	}
 1951   
 1952   	/**
 1953   	 * Called by wrappers that batch load entities
 1954   	 * @param persister only needed for logging
 1955   	 */
 1956   	public final List loadEntityBatch(
 1957   	        final SessionImplementor session,
 1958   	        final Serializable[] ids,
 1959   	        final Type idType,
 1960   	        final Object optionalObject,
 1961   	        final String optionalEntityName,
 1962   	        final Serializable optionalId,
 1963   	        final EntityPersister persister) throws HibernateException {
 1964   
 1965   		if ( log.isDebugEnabled() ) {
 1966   			log.debug( 
 1967   					"batch loading entity: " + 
 1968   					MessageHelper.infoString(persister, ids, getFactory() ) 
 1969   				);
 1970   		}
 1971   
 1972   		Type[] types = new Type[ids.length];
 1973   		Arrays.fill( types, idType );
 1974   		List result;
 1975   		try {
 1976   			result = doQueryAndInitializeNonLazyCollections( 
 1977   					session,
 1978   					new QueryParameters( types, ids, optionalObject, optionalEntityName, optionalId ),
 1979   					false 
 1980   				);
 1981   		}
 1982   		catch ( SQLException sqle ) {
 1983   			throw JDBCExceptionHelper.convert(
 1984   			        factory.getSQLExceptionConverter(),
 1985   			        sqle,
 1986   			        "could not load an entity batch: " + 
 1987   			        MessageHelper.infoString( getEntityPersisters()[0], ids, getFactory() ),
 1988   			        getSQLString()
 1989   				);
 1990   		}
 1991   
 1992   		log.debug("done entity batch load");
 1993   		
 1994   		return result;
 1995   
 1996   	}
 1997   
 1998   	/**
 1999   	 * Called by subclasses that initialize collections
 2000   	 */
 2001   	public final void loadCollection(
 2002   	        final SessionImplementor session,
 2003   	        final Serializable id,
 2004   	        final Type type) throws HibernateException {
 2005   
 2006   		if ( log.isDebugEnabled() ) {
 2007   			log.debug( 
 2008   					"loading collection: "+ 
 2009   					MessageHelper.collectionInfoString( getCollectionPersisters()[0], id, getFactory() )
 2010   				);
 2011   		}
 2012   
 2013   		Serializable[] ids = new Serializable[]{id};
 2014   		try {
 2015   			doQueryAndInitializeNonLazyCollections( 
 2016   					session,
 2017   					new QueryParameters( new Type[]{type}, ids, ids ),
 2018   					true 
 2019   				);
 2020   		}
 2021   		catch ( SQLException sqle ) {
 2022   			throw JDBCExceptionHelper.convert(
 2023   					factory.getSQLExceptionConverter(),
 2024   					sqle,
 2025   					"could not initialize a collection: " + 
 2026   					MessageHelper.collectionInfoString( getCollectionPersisters()[0], id, getFactory() ),
 2027   					getSQLString()
 2028   				);
 2029   		}
 2030   	
 2031   		log.debug("done loading collection");
 2032   
 2033   	}
 2034   
 2035   	/**
 2036   	 * Called by wrappers that batch initialize collections
 2037   	 */
 2038   	public final void loadCollectionBatch(
 2039   	        final SessionImplementor session,
 2040   	        final Serializable[] ids,
 2041   	        final Type type) throws HibernateException {
 2042   
 2043   		if ( log.isDebugEnabled() ) {
 2044   			log.debug( 
 2045   					"batch loading collection: "+ 
 2046   					MessageHelper.collectionInfoString( getCollectionPersisters()[0], ids, getFactory() )
 2047   				);
 2048   		}
 2049   
 2050   		Type[] idTypes = new Type[ids.length];
 2051   		Arrays.fill( idTypes, type );
 2052   		try {
 2053   			doQueryAndInitializeNonLazyCollections( 
 2054   					session,
 2055   					new QueryParameters( idTypes, ids, ids ),
 2056   					true 
 2057   				);
 2058   		}
 2059   		catch ( SQLException sqle ) {
 2060   			throw JDBCExceptionHelper.convert(
 2061   			        factory.getSQLExceptionConverter(),
 2062   			        sqle,
 2063   			        "could not initialize a collection batch: " + 
 2064   			        MessageHelper.collectionInfoString( getCollectionPersisters()[0], ids, getFactory() ),
 2065   			        getSQLString()
 2066   				);
 2067   		}
 2068   		
 2069   		log.debug("done batch load");
 2070   
 2071   	}
 2072   
 2073   	/**
 2074   	 * Called by subclasses that batch initialize collections
 2075   	 */
 2076   	protected final void loadCollectionSubselect(
 2077   	        final SessionImplementor session,
 2078   	        final Serializable[] ids,
 2079   	        final Object[] parameterValues,
 2080   	        final Type[] parameterTypes,
 2081   	        final Map namedParameters,
 2082   	        final Type type) throws HibernateException {
 2083   
 2084   		Type[] idTypes = new Type[ids.length];
 2085   		Arrays.fill( idTypes, type );
 2086   		try {
 2087   			doQueryAndInitializeNonLazyCollections( session,
 2088   					new QueryParameters( parameterTypes, parameterValues, namedParameters, ids ),
 2089   					true 
 2090   				);
 2091   		}
 2092   		catch ( SQLException sqle ) {
 2093   			throw JDBCExceptionHelper.convert(
 2094   			        factory.getSQLExceptionConverter(),
 2095   			        sqle,
 2096   			        "could not load collection by subselect: " + 
 2097   			        MessageHelper.collectionInfoString( getCollectionPersisters()[0], ids, getFactory() ),
 2098   			        getSQLString()
 2099   				);
 2100   		}
 2101   	}
 2102   
 2103   	/**
 2104   	 * Return the query results, using the query cache, called
 2105   	 * by subclasses that implement cacheable queries
 2106   	 */
 2107   	protected List list(
 2108   	        final SessionImplementor session,
 2109   	        final QueryParameters queryParameters,
 2110   	        final Set querySpaces,
 2111   	        final Type[] resultTypes) throws HibernateException {
 2112   
 2113   		final boolean cacheable = factory.getSettings().isQueryCacheEnabled() && 
 2114   			queryParameters.isCacheable();
 2115   
 2116   		if ( cacheable ) {
 2117   			return listUsingQueryCache( session, queryParameters, querySpaces, resultTypes );
 2118   		}
 2119   		else {
 2120   			return listIgnoreQueryCache( session, queryParameters );
 2121   		}
 2122   	}
 2123   
 2124   	private List listIgnoreQueryCache(SessionImplementor session, QueryParameters queryParameters) {
 2125   		return getResultList( doList( session, queryParameters ), queryParameters.getResultTransformer() );
 2126   	}
 2127   
 2128   	private List listUsingQueryCache(
 2129   			final SessionImplementor session, 
 2130   			final QueryParameters queryParameters, 
 2131   			final Set querySpaces, 
 2132   			final Type[] resultTypes) {
 2133   	
 2134   		QueryCache queryCache = factory.getQueryCache( queryParameters.getCacheRegion() );
 2135   		
 2136   		Set filterKeys = FilterKey.createFilterKeys( 
 2137   				session.getEnabledFilters(), 
 2138   				session.getEntityMode() 
 2139   			);
 2140   		QueryKey key = new QueryKey( 
 2141   				getSQLString(), 
 2142   				queryParameters, 
 2143   				filterKeys, 
 2144   				session.getEntityMode() 
 2145   			);
 2146   		
 2147   		List result = getResultFromQueryCache( 
 2148   				session, 
 2149   				queryParameters, 
 2150   				querySpaces, 
 2151   				resultTypes, 
 2152   				queryCache, 
 2153   				key 
 2154   			);
 2155   
 2156   		if ( result == null ) {
 2157   			result = doList( session, queryParameters );
 2158   
 2159   			putResultInQueryCache( 
 2160   					session, 
 2161   					queryParameters, 
 2162   					resultTypes,
 2163   					queryCache, 
 2164   					key, 
 2165   					result 
 2166   				);
 2167   		}
 2168   
 2169   		return getResultList( result, queryParameters.getResultTransformer() );
 2170   	}
 2171   
 2172   	private List getResultFromQueryCache(
 2173   			final SessionImplementor session,
 2174   			final QueryParameters queryParameters,
 2175   			final Set querySpaces,
 2176   			final Type[] resultTypes,
 2177   			final QueryCache queryCache,
 2178   			final QueryKey key) {
 2179   		List result = null;
 2180   
 2181   		if ( session.getCacheMode().isGetEnabled() ) {
 2182   			boolean isImmutableNaturalKeyLookup = queryParameters.isNaturalKeyLookup()
 2183   					&& getEntityPersisters()[0].getEntityMetamodel().hasImmutableNaturalId();
 2184   			result = queryCache.get( key, resultTypes, isImmutableNaturalKeyLookup, querySpaces, session );
 2185   			if ( factory.getStatistics().isStatisticsEnabled() ) {
 2186   				if ( result == null ) {
 2187   					factory.getStatisticsImplementor()
 2188   							.queryCacheMiss( getQueryIdentifier(), queryCache.getRegion().getName() );
 2189   				}
 2190   				else {
 2191   					factory.getStatisticsImplementor()
 2192   							.queryCacheHit( getQueryIdentifier(), queryCache.getRegion().getName() );
 2193   				}
 2194   			}
 2195   		}
 2196   
 2197   		return result;
 2198   	}
 2199   
 2200   	private void putResultInQueryCache(
 2201   			final SessionImplementor session,
 2202   			final QueryParameters queryParameters,
 2203   			final Type[] resultTypes,
 2204   			final QueryCache queryCache,
 2205   			final QueryKey key,
 2206   			final List result) {
 2207   		if ( session.getCacheMode().isPutEnabled() ) {
 2208   			boolean put = queryCache.put( key, resultTypes, result, queryParameters.isNaturalKeyLookup(), session );
 2209   			if ( put && factory.getStatistics().isStatisticsEnabled() ) {
 2210   				factory.getStatisticsImplementor()
 2211   						.queryCachePut( getQueryIdentifier(), queryCache.getRegion().getName() );
 2212   			}
 2213   		}
 2214   	}
 2215   
 2216   	/**
 2217   	 * Actually execute a query, ignoring the query cache
 2218   	 */
 2219   	protected List doList(final SessionImplementor session, final QueryParameters queryParameters)
 2220   			throws HibernateException {
 2221   
 2222   		final boolean stats = getFactory().getStatistics().isStatisticsEnabled();
 2223   		long startTime = 0;
 2224   		if ( stats ) startTime = System.currentTimeMillis();
 2225   
 2226   		List result;
 2227   		try {
 2228   			result = doQueryAndInitializeNonLazyCollections( session, queryParameters, true );
 2229   		}
 2230   		catch ( SQLException sqle ) {
 2231   			throw JDBCExceptionHelper.convert(
 2232   			        factory.getSQLExceptionConverter(),
 2233   			        sqle,
 2234   			        "could not execute query",
 2235   			        getSQLString()
 2236   				);
 2237   		}
 2238   
 2239   		if ( stats ) {
 2240   			getFactory().getStatisticsImplementor().queryExecuted(
 2241   					getQueryIdentifier(),
 2242   					result.size(),
 2243   					System.currentTimeMillis() - startTime
 2244   				);
 2245   		}
 2246   
 2247   		return result;
 2248   	}
 2249   
 2250   	/**
 2251   	 * Check whether the current loader can support returning ScrollableResults.
 2252   	 *
 2253   	 * @throws HibernateException
 2254   	 */
 2255   	protected void checkScrollability() throws HibernateException {
 2256   		// Allows various loaders (ok mainly the QueryLoader :) to check
 2257   		// whether scrolling of their result set should be allowed.
 2258   		//
 2259   		// By default it is allowed.
 2260   		return;
 2261   	}
 2262   
 2263   	/**
 2264   	 * Does the result set to be scrolled contain collection fetches?
 2265   	 *
 2266   	 * @return True if it does, and thus needs the special fetching scroll
 2267   	 * functionality; false otherwise.
 2268   	 */
 2269   	protected boolean needsFetchingScroll() {
 2270   		return false;
 2271   	}
 2272   
 2273   	/**
 2274   	 * Return the query results, as an instance of <tt>ScrollableResults</tt>
 2275   	 *
 2276   	 * @param queryParameters The parameters with which the query should be executed.
 2277   	 * @param returnTypes The expected return types of the query
 2278   	 * @param holderInstantiator If the return values are expected to be wrapped
 2279   	 * in a holder, this is the thing that knows how to wrap them.
 2280   	 * @param session The session from which the scroll request originated.
 2281   	 * @return The ScrollableResults instance.
 2282   	 * @throws HibernateException Indicates an error executing the query, or constructing
 2283   	 * the ScrollableResults.
 2284   	 */
 2285   	protected ScrollableResults scroll(
 2286   	        final QueryParameters queryParameters,
 2287   	        final Type[] returnTypes,
 2288   	        final HolderInstantiator holderInstantiator,
 2289   	        final SessionImplementor session) throws HibernateException {
 2290   
 2291   		checkScrollability();
 2292   
 2293   		final boolean stats = getQueryIdentifier() != null &&
 2294   				getFactory().getStatistics().isStatisticsEnabled();
 2295   		long startTime = 0;
 2296   		if ( stats ) startTime = System.currentTimeMillis();
 2297   
 2298   		try {
 2299   
 2300   			PreparedStatement st = prepareQueryStatement( queryParameters, true, session );
 2301   			ResultSet rs = getResultSet(st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), queryParameters.getRowSelection(), session);
 2302   
 2303   			if ( stats ) {
 2304   				getFactory().getStatisticsImplementor().queryExecuted(
 2305   						getQueryIdentifier(),
 2306   						0,
 2307   						System.currentTimeMillis() - startTime
 2308   					);
 2309   			}
 2310   
 2311   			if ( needsFetchingScroll() ) {
 2312   				return new FetchingScrollableResultsImpl(
 2313   						rs,
 2314   						st,
 2315   						session,
 2316   						this,
 2317   						queryParameters,
 2318   						returnTypes,
 2319   						holderInstantiator
 2320   					);
 2321   			}
 2322   			else {
 2323   				return new ScrollableResultsImpl(
 2324   						rs,
 2325   						st,
 2326   						session,
 2327   						this,
 2328   						queryParameters,
 2329   						returnTypes,
 2330   						holderInstantiator
 2331   					);
 2332   			}
 2333   
 2334   		}
 2335   		catch ( SQLException sqle ) {
 2336   			throw JDBCExceptionHelper.convert(
 2337   			        factory.getSQLExceptionConverter(),
 2338   			        sqle,
 2339   			        "could not execute query using scroll",
 2340   			        getSQLString()
 2341   				);
 2342   		}
 2343   
 2344   	}
 2345   
 2346   	/**
 2347   	 * Calculate and cache select-clause suffixes. Must be
 2348   	 * called by subclasses after instantiation.
 2349   	 */
 2350   	protected void postInstantiate() {}
 2351   
 2352   	/**
 2353   	 * Get the result set descriptor
 2354   	 */
 2355   	protected abstract EntityAliases[] getEntityAliases();
 2356   
 2357   	protected abstract CollectionAliases[] getCollectionAliases();
 2358   
 2359   	/**
 2360   	 * Identifies the query for statistics reporting, if null,
 2361   	 * no statistics will be reported
 2362   	 */
 2363   	protected String getQueryIdentifier() {
 2364   		return null;
 2365   	}
 2366   
 2367   	public final SessionFactoryImplementor getFactory() {
 2368   		return factory;
 2369   	}
 2370   
 2371   	public String toString() {
 2372   		return getClass().getName() + '(' + getSQLString() + ')';
 2373   	}
 2374   
 2375   }

Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » loader » [javadoc | source]