Save This Page
Home » Hibernate-3.3.2.GA » 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   	private int interpretFirstRow(int zeroBasedFirstResult) {
 1525   		return getFactory().getDialect().convertToFirstRowValue( zeroBasedFirstResult );
 1526   	}
 1527   
 1528   	/**
 1529   	 * Should we pre-process the SQL string, adding a dialect-specific
 1530   	 * LIMIT clause.
 1531   	 */
 1532   	private static boolean useLimit(final RowSelection selection, final Dialect dialect) {
 1533   		return dialect.supportsLimit() && hasMaxRows( selection );
 1534   	}
 1535   
 1536   	/**
 1537   	 * Obtain a <tt>PreparedStatement</tt> with all parameters pre-bound.
 1538   	 * Bind JDBC-style <tt>?</tt> parameters, named parameters, and
 1539   	 * limit parameters.
 1540   	 */
 1541   	protected final PreparedStatement prepareQueryStatement(
 1542   	        final QueryParameters queryParameters,
 1543   	        final boolean scroll,
 1544   	        final SessionImplementor session) throws SQLException, HibernateException {
 1545   
 1546   		queryParameters.processFilters( getSQLString(), session );
 1547   		String sql = queryParameters.getFilteredSQL();
 1548   		final Dialect dialect = getFactory().getDialect();
 1549   		final RowSelection selection = queryParameters.getRowSelection();
 1550   		boolean useLimit = useLimit( selection, dialect );
 1551   		boolean hasFirstRow = getFirstRow( selection ) > 0;
 1552   		boolean useOffset = hasFirstRow && useLimit && dialect.supportsLimitOffset();
 1553   		boolean callable = queryParameters.isCallable();
 1554   		
 1555   		boolean useScrollableResultSetToSkip = hasFirstRow &&
 1556   				!useOffset &&
 1557   				getFactory().getSettings().isScrollableResultSetsEnabled();
 1558   		ScrollMode scrollMode = scroll ? queryParameters.getScrollMode() : ScrollMode.SCROLL_INSENSITIVE;
 1559   
 1560   		if ( useLimit ) {
 1561   			sql = dialect.getLimitString( 
 1562   					sql.trim(), //use of trim() here is ugly?
 1563   					useOffset ? getFirstRow(selection) : 0, 
 1564   					getMaxOrLimit(selection, dialect) 
 1565   				);
 1566   		}
 1567   
 1568   		sql = preprocessSQL( sql, queryParameters, dialect );
 1569   		
 1570   		PreparedStatement st = null;
 1571   		
 1572   		if (callable) {
 1573   			st = session.getBatcher()
 1574   				.prepareCallableQueryStatement( sql, scroll || useScrollableResultSetToSkip, scrollMode );
 1575   		} 
 1576   		else {
 1577   			st = session.getBatcher()
 1578   				.prepareQueryStatement( sql, scroll || useScrollableResultSetToSkip, scrollMode );
 1579   		}
 1580   				
 1581   
 1582   		try {
 1583   
 1584   			int col = 1;
 1585   			//TODO: can we limit stored procedures ?!
 1586   			if ( useLimit && dialect.bindLimitParametersFirst() ) {
 1587   				col += bindLimitParameters( st, col, selection );
 1588   			}
 1589   			if (callable) {
 1590   				col = dialect.registerResultSetOutParameter( (CallableStatement)st, col );
 1591   			}
 1592   
 1593   			col += bindParameterValues( st, queryParameters, col, session );
 1594   
 1595   			if ( useLimit && !dialect.bindLimitParametersFirst() ) {
 1596   				col += bindLimitParameters( st, col, selection );
 1597   			}
 1598   
 1599   			if ( !useLimit ) {
 1600   				setMaxRows( st, selection );
 1601   			}
 1602   
 1603   			if ( selection != null ) {
 1604   				if ( selection.getTimeout() != null ) {
 1605   					st.setQueryTimeout( selection.getTimeout().intValue() );
 1606   				}
 1607   				if ( selection.getFetchSize() != null ) {
 1608   					st.setFetchSize( selection.getFetchSize().intValue() );
 1609   				}
 1610   			}
 1611   		}
 1612   		catch ( SQLException sqle ) {
 1613   			session.getBatcher().closeQueryStatement( st, null );
 1614   			throw sqle;
 1615   		}
 1616   		catch ( HibernateException he ) {
 1617   			session.getBatcher().closeQueryStatement( st, null );
 1618   			throw he;
 1619   		}
 1620   
 1621   		return st;
 1622   	}
 1623   
 1624   	/**
 1625   	 * Some dialect-specific LIMIT clauses require the maximium last row number
 1626   	 * (aka, first_row_number + total_row_count), while others require the maximum
 1627   	 * returned row count (the total maximum number of rows to return).
 1628   	 *
 1629   	 * @param selection The selection criteria
 1630   	 * @param dialect The dialect
 1631   	 * @return The appropriate value to bind into the limit clause.
 1632   	 */
 1633   	private static int getMaxOrLimit(final RowSelection selection, final Dialect dialect) {
 1634   		final int firstRow = dialect.convertToFirstRowValue( getFirstRow( selection ) );
 1635   		final int lastRow = selection.getMaxRows().intValue();
 1636   		if ( dialect.useMaxForLimit() ) {
 1637   			return lastRow + firstRow;
 1638   		}
 1639   		else {
 1640   			return lastRow;
 1641   		}
 1642   	}
 1643   
 1644   	/**
 1645   	 * Bind parameter values needed by the dialect-specific LIMIT clause.
 1646   	 *
 1647   	 * @param statement The statement to which to bind limit param values.
 1648   	 * @param index The bind position from which to start binding
 1649   	 * @param selection The selection object containing the limit information.
 1650   	 * @return The number of parameter values bound.
 1651   	 * @throws java.sql.SQLException Indicates problems binding parameter values.
 1652   	 */
 1653   	private int bindLimitParameters(
 1654   			final PreparedStatement statement,
 1655   			final int index,
 1656   			final RowSelection selection) throws SQLException {
 1657   		Dialect dialect = getFactory().getDialect();
 1658   		if ( !dialect.supportsVariableLimit() ) {
 1659   			return 0;
 1660   		}
 1661   		if ( !hasMaxRows( selection ) ) {
 1662   			throw new AssertionFailure( "no max results set" );
 1663   		}
 1664   		int firstRow = interpretFirstRow( getFirstRow( selection ) );
 1665   		int lastRow = getMaxOrLimit( selection, dialect );
 1666   		boolean hasFirstRow = dialect.supportsLimitOffset() && ( firstRow > 0 || dialect.forceLimitUsage() );
 1667   		boolean reverse = dialect.bindLimitParametersInReverseOrder();
 1668   		if ( hasFirstRow ) {
 1669   			statement.setInt( index + ( reverse ? 1 : 0 ), firstRow );
 1670   		}
 1671   		statement.setInt( index + ( reverse || !hasFirstRow ? 0 : 1 ), lastRow );
 1672   		return hasFirstRow ? 2 : 1;
 1673   	}
 1674   
 1675   	/**
 1676   	 * Use JDBC API to limit the number of rows returned by the SQL query if necessary
 1677   	 */
 1678   	private void setMaxRows(
 1679   			final PreparedStatement st,
 1680   			final RowSelection selection) throws SQLException {
 1681   		if ( hasMaxRows( selection ) ) {
 1682   			st.setMaxRows( selection.getMaxRows().intValue() + interpretFirstRow( getFirstRow( selection ) ) );
 1683   		}
 1684   	}
 1685   
 1686   	/**
 1687   	 * Bind all parameter values into the prepared statement in preparation
 1688   	 * for execution.
 1689   	 *
 1690   	 * @param statement The JDBC prepared statement
 1691   	 * @param queryParameters The encapsulation of the parameter values to be bound.
 1692   	 * @param startIndex The position from which to start binding parameter values.
 1693   	 * @param session The originating session.
 1694   	 * @return The number of JDBC bind positions actually bound during this method execution.
 1695   	 * @throws SQLException Indicates problems performing the binding.
 1696   	 */
 1697   	protected int bindParameterValues(
 1698   			PreparedStatement statement,
 1699   			QueryParameters queryParameters,
 1700   			int startIndex,
 1701   			SessionImplementor session) throws SQLException {
 1702   		int span = 0;
 1703   		span += bindPositionalParameters( statement, queryParameters, startIndex, session );
 1704   		span += bindNamedParameters( statement, queryParameters.getNamedParameters(), startIndex + span, session );
 1705   		return span;
 1706   	}
 1707   
 1708   	/**
 1709   	 * Bind positional parameter values to the JDBC prepared statement.
 1710   	 * <p/>
 1711   	 * Postional parameters are those specified by JDBC-style ? parameters
 1712   	 * in the source query.  It is (currently) expected that these come
 1713   	 * before any named parameters in the source query.
 1714   	 *
 1715   	 * @param statement The JDBC prepared statement
 1716   	 * @param queryParameters The encapsulation of the parameter values to be bound.
 1717   	 * @param startIndex The position from which to start binding parameter values.
 1718   	 * @param session The originating session.
 1719   	 * @return The number of JDBC bind positions actually bound during this method execution.
 1720   	 * @throws SQLException Indicates problems performing the binding.
 1721   	 * @throws org.hibernate.HibernateException Indicates problems delegating binding to the types.
 1722   	 */
 1723   	protected int bindPositionalParameters(
 1724   	        final PreparedStatement statement,
 1725   	        final QueryParameters queryParameters,
 1726   	        final int startIndex,
 1727   	        final SessionImplementor session) throws SQLException, HibernateException {
 1728   		final Object[] values = queryParameters.getFilteredPositionalParameterValues();
 1729   		final Type[] types = queryParameters.getFilteredPositionalParameterTypes();
 1730   		int span = 0;
 1731   		for ( int i = 0; i < values.length; i++ ) {
 1732   			types[i].nullSafeSet( statement, values[i], startIndex + span, session );
 1733   			span += types[i].getColumnSpan( getFactory() );
 1734   		}
 1735   		return span;
 1736   	}
 1737   
 1738   	/**
 1739   	 * Bind named parameters to the JDBC prepared statement.
 1740   	 * <p/>
 1741   	 * This is a generic implementation, the problem being that in the
 1742   	 * general case we do not know enough information about the named
 1743   	 * parameters to perform this in a complete manner here.  Thus this
 1744   	 * is generally overridden on subclasses allowing named parameters to
 1745   	 * apply the specific behavior.  The most usual limitation here is that
 1746   	 * we need to assume the type span is always one...
 1747   	 *
 1748   	 * @param statement The JDBC prepared statement
 1749   	 * @param namedParams A map of parameter names to values
 1750   	 * @param startIndex The position from which to start binding parameter values.
 1751   	 * @param session The originating session.
 1752   	 * @return The number of JDBC bind positions actually bound during this method execution.
 1753   	 * @throws SQLException Indicates problems performing the binding.
 1754   	 * @throws org.hibernate.HibernateException Indicates problems delegating binding to the types.
 1755   	 */
 1756   	protected int bindNamedParameters(
 1757   			final PreparedStatement statement,
 1758   			final Map namedParams,
 1759   			final int startIndex,
 1760   			final SessionImplementor session) throws SQLException, HibernateException {
 1761   		if ( namedParams != null ) {
 1762   			// assumes that types are all of span 1
 1763   			Iterator iter = namedParams.entrySet().iterator();
 1764   			int result = 0;
 1765   			while ( iter.hasNext() ) {
 1766   				Map.Entry e = ( Map.Entry ) iter.next();
 1767   				String name = ( String ) e.getKey();
 1768   				TypedValue typedval = ( TypedValue ) e.getValue();
 1769   				int[] locs = getNamedParameterLocs( name );
 1770   				for ( int i = 0; i < locs.length; i++ ) {
 1771   					if ( log.isDebugEnabled() ) {
 1772   						log.debug(
 1773   								"bindNamedParameters() " +
 1774   								typedval.getValue() + " -> " + name +
 1775   								" [" + ( locs[i] + startIndex ) + "]"
 1776   							);
 1777   					}
 1778   					typedval.getType().nullSafeSet( statement, typedval.getValue(), locs[i] + startIndex, session );
 1779   				}
 1780   				result += locs.length;
 1781   			}
 1782   			return result;
 1783   		}
 1784   		else {
 1785   			return 0;
 1786   		}
 1787   	}
 1788   
 1789   	public int[] getNamedParameterLocs(String name) {
 1790   		throw new AssertionFailure("no named parameters");
 1791   	}
 1792   
 1793   	/**
 1794   	 * Fetch a <tt>PreparedStatement</tt>, call <tt>setMaxRows</tt> and then execute it,
 1795   	 * advance to the first result and return an SQL <tt>ResultSet</tt>
 1796   	 */
 1797   	protected final ResultSet getResultSet(
 1798   	        final PreparedStatement st,
 1799   	        final boolean autodiscovertypes,
 1800   	        final boolean callable,
 1801   	        final RowSelection selection,
 1802   	        final SessionImplementor session) 
 1803   	throws SQLException, HibernateException {
 1804   	
 1805   		ResultSet rs = null;
 1806   		try {
 1807   			Dialect dialect = getFactory().getDialect();
 1808   			if (callable) {
 1809   				rs = session.getBatcher().getResultSet( (CallableStatement) st, dialect );
 1810   			} 
 1811   			else {
 1812   				rs = session.getBatcher().getResultSet( st );
 1813   			}
 1814   			rs = wrapResultSetIfEnabled( rs , session );
 1815   			
 1816   			if ( !dialect.supportsLimitOffset() || !useLimit( selection, dialect ) ) {
 1817   				advance( rs, selection );
 1818   			}
 1819   			
 1820   			if ( autodiscovertypes ) {
 1821   				autoDiscoverTypes( rs );
 1822   			}
 1823   			return rs;
 1824   		}
 1825   		catch ( SQLException sqle ) {
 1826   			session.getBatcher().closeQueryStatement( st, rs );
 1827   			throw sqle;
 1828   		}
 1829   	}
 1830   
 1831   	protected void autoDiscoverTypes(ResultSet rs) {
 1832   		throw new AssertionFailure("Auto discover types not supported in this loader");
 1833   		
 1834   	}
 1835   
 1836   	private synchronized ResultSet wrapResultSetIfEnabled(final ResultSet rs, final SessionImplementor session) {
 1837   		// synchronized to avoid multi-thread access issues; defined as method synch to avoid
 1838   		// potential deadlock issues due to nature of code.
 1839   		if ( session.getFactory().getSettings().isWrapResultSetsEnabled() ) {
 1840   			try {
 1841   				log.debug("Wrapping result set [" + rs + "]");
 1842   				return new ResultSetWrapper( rs, retreiveColumnNameToIndexCache( rs ) );
 1843   			}
 1844   			catch(SQLException e) {
 1845   				log.info("Error wrapping result set", e);
 1846   				return rs;
 1847   			}
 1848   		}
 1849   		else {
 1850   			return rs;
 1851   		}
 1852   	}
 1853   
 1854   	private ColumnNameCache retreiveColumnNameToIndexCache(ResultSet rs) throws SQLException {
 1855   		if ( columnNameCache == null ) {
 1856   			log.trace("Building columnName->columnIndex cache");
 1857   			columnNameCache = new ColumnNameCache( rs.getMetaData().getColumnCount() );
 1858   		}
 1859   
 1860   		return columnNameCache;
 1861   	}
 1862   
 1863   	/**
 1864   	 * Called by subclasses that load entities
 1865   	 * @param persister only needed for logging
 1866   	 */
 1867   	protected final List loadEntity(
 1868   	        final SessionImplementor session,
 1869   	        final Object id,
 1870   	        final Type identifierType,
 1871   	        final Object optionalObject,
 1872   	        final String optionalEntityName,
 1873   	        final Serializable optionalIdentifier,
 1874   	        final EntityPersister persister) throws HibernateException {
 1875   		
 1876   		if ( log.isDebugEnabled() ) {
 1877   			log.debug( 
 1878   					"loading entity: " + 
 1879   					MessageHelper.infoString( persister, id, identifierType, getFactory() ) 
 1880   				);
 1881   		}
 1882   
 1883   		List result;
 1884   		try {
 1885   			result = doQueryAndInitializeNonLazyCollections( 
 1886   					session,
 1887   					new QueryParameters( 
 1888   							new Type[] { identifierType },
 1889   							new Object[] { id },
 1890   							optionalObject,
 1891   							optionalEntityName,
 1892   							optionalIdentifier 
 1893   						),
 1894   					false 
 1895   				);
 1896   		}
 1897   		catch ( SQLException sqle ) {
 1898   			final Loadable[] persisters = getEntityPersisters();
 1899   			throw JDBCExceptionHelper.convert(
 1900   			        factory.getSQLExceptionConverter(),
 1901   			        sqle,
 1902   			        "could not load an entity: " + 
 1903   			        MessageHelper.infoString( persisters[persisters.length-1], id, identifierType, getFactory() ),
 1904   			        getSQLString()
 1905   				);
 1906   		}
 1907   
 1908   		log.debug("done entity load");
 1909   		
 1910   		return result;
 1911   		
 1912   	}
 1913   
 1914   	/**
 1915   	 * Called by subclasses that load entities
 1916   	 * @param persister only needed for logging
 1917   	 */
 1918   	protected final List loadEntity(
 1919   	        final SessionImplementor session,
 1920   	        final Object key,
 1921   	        final Object index,
 1922   	        final Type keyType,
 1923   	        final Type indexType,
 1924   	        final EntityPersister persister) throws HibernateException {
 1925   		
 1926   		if ( log.isDebugEnabled() ) {
 1927   			log.debug( "loading collection element by index" );
 1928   		}
 1929   
 1930   		List result;
 1931   		try {
 1932   			result = doQueryAndInitializeNonLazyCollections( 
 1933   					session,
 1934   					new QueryParameters( 
 1935   							new Type[] { keyType, indexType },
 1936   							new Object[] { key, index }
 1937   						),
 1938   					false 
 1939   				);
 1940   		}
 1941   		catch ( SQLException sqle ) {
 1942   			throw JDBCExceptionHelper.convert(
 1943   			        factory.getSQLExceptionConverter(),
 1944   			        sqle,
 1945   			        "could not collection element by index",
 1946   			        getSQLString()
 1947   				);
 1948   		}
 1949   
 1950   		log.debug("done entity load");
 1951   		
 1952   		return result;
 1953   		
 1954   	}
 1955   
 1956   	/**
 1957   	 * Called by wrappers that batch load entities
 1958   	 * @param persister only needed for logging
 1959   	 */
 1960   	public final List loadEntityBatch(
 1961   	        final SessionImplementor session,
 1962   	        final Serializable[] ids,
 1963   	        final Type idType,
 1964   	        final Object optionalObject,
 1965   	        final String optionalEntityName,
 1966   	        final Serializable optionalId,
 1967   	        final EntityPersister persister) throws HibernateException {
 1968   
 1969   		if ( log.isDebugEnabled() ) {
 1970   			log.debug( 
 1971   					"batch loading entity: " + 
 1972   					MessageHelper.infoString(persister, ids, getFactory() ) 
 1973   				);
 1974   		}
 1975   
 1976   		Type[] types = new Type[ids.length];
 1977   		Arrays.fill( types, idType );
 1978   		List result;
 1979   		try {
 1980   			result = doQueryAndInitializeNonLazyCollections( 
 1981   					session,
 1982   					new QueryParameters( types, ids, optionalObject, optionalEntityName, optionalId ),
 1983   					false 
 1984   				);
 1985   		}
 1986   		catch ( SQLException sqle ) {
 1987   			throw JDBCExceptionHelper.convert(
 1988   			        factory.getSQLExceptionConverter(),
 1989   			        sqle,
 1990   			        "could not load an entity batch: " + 
 1991   			        MessageHelper.infoString( getEntityPersisters()[0], ids, getFactory() ),
 1992   			        getSQLString()
 1993   				);
 1994   		}
 1995   
 1996   		log.debug("done entity batch load");
 1997   		
 1998   		return result;
 1999   
 2000   	}
 2001   
 2002   	/**
 2003   	 * Called by subclasses that initialize collections
 2004   	 */
 2005   	public final void loadCollection(
 2006   	        final SessionImplementor session,
 2007   	        final Serializable id,
 2008   	        final Type type) throws HibernateException {
 2009   
 2010   		if ( log.isDebugEnabled() ) {
 2011   			log.debug( 
 2012   					"loading collection: "+ 
 2013   					MessageHelper.collectionInfoString( getCollectionPersisters()[0], id, getFactory() )
 2014   				);
 2015   		}
 2016   
 2017   		Serializable[] ids = new Serializable[]{id};
 2018   		try {
 2019   			doQueryAndInitializeNonLazyCollections( 
 2020   					session,
 2021   					new QueryParameters( new Type[]{type}, ids, ids ),
 2022   					true 
 2023   				);
 2024   		}
 2025   		catch ( SQLException sqle ) {
 2026   			throw JDBCExceptionHelper.convert(
 2027   					factory.getSQLExceptionConverter(),
 2028   					sqle,
 2029   					"could not initialize a collection: " + 
 2030   					MessageHelper.collectionInfoString( getCollectionPersisters()[0], id, getFactory() ),
 2031   					getSQLString()
 2032   				);
 2033   		}
 2034   	
 2035   		log.debug("done loading collection");
 2036   
 2037   	}
 2038   
 2039   	/**
 2040   	 * Called by wrappers that batch initialize collections
 2041   	 */
 2042   	public final void loadCollectionBatch(
 2043   	        final SessionImplementor session,
 2044   	        final Serializable[] ids,
 2045   	        final Type type) throws HibernateException {
 2046   
 2047   		if ( log.isDebugEnabled() ) {
 2048   			log.debug( 
 2049   					"batch loading collection: "+ 
 2050   					MessageHelper.collectionInfoString( getCollectionPersisters()[0], ids, getFactory() )
 2051   				);
 2052   		}
 2053   
 2054   		Type[] idTypes = new Type[ids.length];
 2055   		Arrays.fill( idTypes, type );
 2056   		try {
 2057   			doQueryAndInitializeNonLazyCollections( 
 2058   					session,
 2059   					new QueryParameters( idTypes, ids, ids ),
 2060   					true 
 2061   				);
 2062   		}
 2063   		catch ( SQLException sqle ) {
 2064   			throw JDBCExceptionHelper.convert(
 2065   			        factory.getSQLExceptionConverter(),
 2066   			        sqle,
 2067   			        "could not initialize a collection batch: " + 
 2068   			        MessageHelper.collectionInfoString( getCollectionPersisters()[0], ids, getFactory() ),
 2069   			        getSQLString()
 2070   				);
 2071   		}
 2072   		
 2073   		log.debug("done batch load");
 2074   
 2075   	}
 2076   
 2077   	/**
 2078   	 * Called by subclasses that batch initialize collections
 2079   	 */
 2080   	protected final void loadCollectionSubselect(
 2081   	        final SessionImplementor session,
 2082   	        final Serializable[] ids,
 2083   	        final Object[] parameterValues,
 2084   	        final Type[] parameterTypes,
 2085   	        final Map namedParameters,
 2086   	        final Type type) throws HibernateException {
 2087   
 2088   		Type[] idTypes = new Type[ids.length];
 2089   		Arrays.fill( idTypes, type );
 2090   		try {
 2091   			doQueryAndInitializeNonLazyCollections( session,
 2092   					new QueryParameters( parameterTypes, parameterValues, namedParameters, ids ),
 2093   					true 
 2094   				);
 2095   		}
 2096   		catch ( SQLException sqle ) {
 2097   			throw JDBCExceptionHelper.convert(
 2098   			        factory.getSQLExceptionConverter(),
 2099   			        sqle,
 2100   			        "could not load collection by subselect: " + 
 2101   			        MessageHelper.collectionInfoString( getCollectionPersisters()[0], ids, getFactory() ),
 2102   			        getSQLString()
 2103   				);
 2104   		}
 2105   	}
 2106   
 2107   	/**
 2108   	 * Return the query results, using the query cache, called
 2109   	 * by subclasses that implement cacheable queries
 2110   	 */
 2111   	protected List list(
 2112   	        final SessionImplementor session,
 2113   	        final QueryParameters queryParameters,
 2114   	        final Set querySpaces,
 2115   	        final Type[] resultTypes) throws HibernateException {
 2116   
 2117   		final boolean cacheable = factory.getSettings().isQueryCacheEnabled() && 
 2118   			queryParameters.isCacheable();
 2119   
 2120   		if ( cacheable ) {
 2121   			return listUsingQueryCache( session, queryParameters, querySpaces, resultTypes );
 2122   		}
 2123   		else {
 2124   			return listIgnoreQueryCache( session, queryParameters );
 2125   		}
 2126   	}
 2127   
 2128   	private List listIgnoreQueryCache(SessionImplementor session, QueryParameters queryParameters) {
 2129   		return getResultList( doList( session, queryParameters ), queryParameters.getResultTransformer() );
 2130   	}
 2131   
 2132   	private List listUsingQueryCache(
 2133   			final SessionImplementor session, 
 2134   			final QueryParameters queryParameters, 
 2135   			final Set querySpaces, 
 2136   			final Type[] resultTypes) {
 2137   	
 2138   		QueryCache queryCache = factory.getQueryCache( queryParameters.getCacheRegion() );
 2139   		
 2140   		Set filterKeys = FilterKey.createFilterKeys( 
 2141   				session.getEnabledFilters(),
 2142   				session.getEntityMode() 
 2143   		);
 2144   		QueryKey key = QueryKey.generateQueryKey(
 2145   				getSQLString(), 
 2146   				queryParameters, 
 2147   				filterKeys, 
 2148   				session
 2149   		);
 2150   		
 2151   		List result = getResultFromQueryCache( 
 2152   				session, 
 2153   				queryParameters, 
 2154   				querySpaces, 
 2155   				resultTypes, 
 2156   				queryCache, 
 2157   				key 
 2158   		);
 2159   
 2160   		if ( result == null ) {
 2161   			result = doList( session, queryParameters );
 2162   
 2163   			putResultInQueryCache( 
 2164   					session, 
 2165   					queryParameters, 
 2166   					resultTypes,
 2167   					queryCache, 
 2168   					key, 
 2169   					result 
 2170   			);
 2171   		}
 2172   
 2173   		return getResultList( result, queryParameters.getResultTransformer() );
 2174   	}
 2175   
 2176   	private List getResultFromQueryCache(
 2177   			final SessionImplementor session,
 2178   			final QueryParameters queryParameters,
 2179   			final Set querySpaces,
 2180   			final Type[] resultTypes,
 2181   			final QueryCache queryCache,
 2182   			final QueryKey key) {
 2183   		List result = null;
 2184   
 2185   		if ( session.getCacheMode().isGetEnabled() ) {
 2186   			boolean isImmutableNaturalKeyLookup = queryParameters.isNaturalKeyLookup()
 2187   					&& getEntityPersisters()[0].getEntityMetamodel().hasImmutableNaturalId();
 2188   			result = queryCache.get( key, resultTypes, isImmutableNaturalKeyLookup, querySpaces, session );
 2189   			if ( factory.getStatistics().isStatisticsEnabled() ) {
 2190   				if ( result == null ) {
 2191   					factory.getStatisticsImplementor()
 2192   							.queryCacheMiss( getQueryIdentifier(), queryCache.getRegion().getName() );
 2193   				}
 2194   				else {
 2195   					factory.getStatisticsImplementor()
 2196   							.queryCacheHit( getQueryIdentifier(), queryCache.getRegion().getName() );
 2197   				}
 2198   			}
 2199   		}
 2200   
 2201   		return result;
 2202   	}
 2203   
 2204   	private void putResultInQueryCache(
 2205   			final SessionImplementor session,
 2206   			final QueryParameters queryParameters,
 2207   			final Type[] resultTypes,
 2208   			final QueryCache queryCache,
 2209   			final QueryKey key,
 2210   			final List result) {
 2211   		if ( session.getCacheMode().isPutEnabled() ) {
 2212   			boolean put = queryCache.put( key, resultTypes, result, queryParameters.isNaturalKeyLookup(), session );
 2213   			if ( put && factory.getStatistics().isStatisticsEnabled() ) {
 2214   				factory.getStatisticsImplementor()
 2215   						.queryCachePut( getQueryIdentifier(), queryCache.getRegion().getName() );
 2216   			}
 2217   		}
 2218   	}
 2219   
 2220   	/**
 2221   	 * Actually execute a query, ignoring the query cache
 2222   	 */
 2223   	protected List doList(final SessionImplementor session, final QueryParameters queryParameters)
 2224   			throws HibernateException {
 2225   
 2226   		final boolean stats = getFactory().getStatistics().isStatisticsEnabled();
 2227   		long startTime = 0;
 2228   		if ( stats ) startTime = System.currentTimeMillis();
 2229   
 2230   		List result;
 2231   		try {
 2232   			result = doQueryAndInitializeNonLazyCollections( session, queryParameters, true );
 2233   		}
 2234   		catch ( SQLException sqle ) {
 2235   			throw JDBCExceptionHelper.convert(
 2236   			        factory.getSQLExceptionConverter(),
 2237   			        sqle,
 2238   			        "could not execute query",
 2239   			        getSQLString()
 2240   				);
 2241   		}
 2242   
 2243   		if ( stats ) {
 2244   			getFactory().getStatisticsImplementor().queryExecuted(
 2245   					getQueryIdentifier(),
 2246   					result.size(),
 2247   					System.currentTimeMillis() - startTime
 2248   				);
 2249   		}
 2250   
 2251   		return result;
 2252   	}
 2253   
 2254   	/**
 2255   	 * Check whether the current loader can support returning ScrollableResults.
 2256   	 *
 2257   	 * @throws HibernateException
 2258   	 */
 2259   	protected void checkScrollability() throws HibernateException {
 2260   		// Allows various loaders (ok mainly the QueryLoader :) to check
 2261   		// whether scrolling of their result set should be allowed.
 2262   		//
 2263   		// By default it is allowed.
 2264   		return;
 2265   	}
 2266   
 2267   	/**
 2268   	 * Does the result set to be scrolled contain collection fetches?
 2269   	 *
 2270   	 * @return True if it does, and thus needs the special fetching scroll
 2271   	 * functionality; false otherwise.
 2272   	 */
 2273   	protected boolean needsFetchingScroll() {
 2274   		return false;
 2275   	}
 2276   
 2277   	/**
 2278   	 * Return the query results, as an instance of <tt>ScrollableResults</tt>
 2279   	 *
 2280   	 * @param queryParameters The parameters with which the query should be executed.
 2281   	 * @param returnTypes The expected return types of the query
 2282   	 * @param holderInstantiator If the return values are expected to be wrapped
 2283   	 * in a holder, this is the thing that knows how to wrap them.
 2284   	 * @param session The session from which the scroll request originated.
 2285   	 * @return The ScrollableResults instance.
 2286   	 * @throws HibernateException Indicates an error executing the query, or constructing
 2287   	 * the ScrollableResults.
 2288   	 */
 2289   	protected ScrollableResults scroll(
 2290   	        final QueryParameters queryParameters,
 2291   	        final Type[] returnTypes,
 2292   	        final HolderInstantiator holderInstantiator,
 2293   	        final SessionImplementor session) throws HibernateException {
 2294   
 2295   		checkScrollability();
 2296   
 2297   		final boolean stats = getQueryIdentifier() != null &&
 2298   				getFactory().getStatistics().isStatisticsEnabled();
 2299   		long startTime = 0;
 2300   		if ( stats ) startTime = System.currentTimeMillis();
 2301   
 2302   		try {
 2303   
 2304   			PreparedStatement st = prepareQueryStatement( queryParameters, true, session );
 2305   			ResultSet rs = getResultSet(st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), queryParameters.getRowSelection(), session);
 2306   
 2307   			if ( stats ) {
 2308   				getFactory().getStatisticsImplementor().queryExecuted(
 2309   						getQueryIdentifier(),
 2310   						0,
 2311   						System.currentTimeMillis() - startTime
 2312   					);
 2313   			}
 2314   
 2315   			if ( needsFetchingScroll() ) {
 2316   				return new FetchingScrollableResultsImpl(
 2317   						rs,
 2318   						st,
 2319   						session,
 2320   						this,
 2321   						queryParameters,
 2322   						returnTypes,
 2323   						holderInstantiator
 2324   					);
 2325   			}
 2326   			else {
 2327   				return new ScrollableResultsImpl(
 2328   						rs,
 2329   						st,
 2330   						session,
 2331   						this,
 2332   						queryParameters,
 2333   						returnTypes,
 2334   						holderInstantiator
 2335   					);
 2336   			}
 2337   
 2338   		}
 2339   		catch ( SQLException sqle ) {
 2340   			throw JDBCExceptionHelper.convert(
 2341   			        factory.getSQLExceptionConverter(),
 2342   			        sqle,
 2343   			        "could not execute query using scroll",
 2344   			        getSQLString()
 2345   				);
 2346   		}
 2347   
 2348   	}
 2349   
 2350   	/**
 2351   	 * Calculate and cache select-clause suffixes. Must be
 2352   	 * called by subclasses after instantiation.
 2353   	 */
 2354   	protected void postInstantiate() {}
 2355   
 2356   	/**
 2357   	 * Get the result set descriptor
 2358   	 */
 2359   	protected abstract EntityAliases[] getEntityAliases();
 2360   
 2361   	protected abstract CollectionAliases[] getCollectionAliases();
 2362   
 2363   	/**
 2364   	 * Identifies the query for statistics reporting, if null,
 2365   	 * no statistics will be reported
 2366   	 */
 2367   	protected String getQueryIdentifier() {
 2368   		return null;
 2369   	}
 2370   
 2371   	public final SessionFactoryImplementor getFactory() {
 2372   		return factory;
 2373   	}
 2374   
 2375   	public String toString() {
 2376   		return getClass().getName() + '(' + getSQLString() + ')';
 2377   	}
 2378   
 2379   }

Save This Page
Home » Hibernate-3.3.2.GA » org.hibernate » loader » [javadoc | source]