Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/hibernate/loader/Loader.java


1   //$Id: Loader.java,v 1.101 2005/04/26 16:55:57 oneovthafew Exp $
2   package org.hibernate.loader;
3   
4   import java.io.Serializable;
5   import java.sql.CallableStatement;
6   import java.sql.PreparedStatement;
7   import java.sql.ResultSet;
8   import java.sql.SQLException;
9   import java.util.ArrayList;
10  import java.util.Arrays;
11  import java.util.Iterator;
12  import java.util.List;
13  import java.util.Map;
14  import java.util.Set;
15  
16  import org.apache.commons.logging.Log;
17  import org.apache.commons.logging.LogFactory;
18  import org.hibernate.AssertionFailure;
19  import org.hibernate.Hibernate;
20  import org.hibernate.HibernateException;
21  import org.hibernate.LockMode;
22  import org.hibernate.QueryException;
23  import org.hibernate.ScrollMode;
24  import org.hibernate.ScrollableResults;
25  import org.hibernate.StaleObjectStateException;
26  import org.hibernate.WrongClassException;
27  import org.hibernate.cache.FilterKey;
28  import org.hibernate.cache.QueryCache;
29  import org.hibernate.cache.QueryKey;
30  import org.hibernate.collection.PersistentCollection;
31  import org.hibernate.dialect.Dialect;
32  import org.hibernate.engine.EntityKey;
33  import org.hibernate.engine.EntityUniqueKey;
34  import org.hibernate.engine.PersistenceContext;
35  import org.hibernate.engine.QueryParameters;
36  import org.hibernate.engine.RowSelection;
37  import org.hibernate.engine.SessionFactoryImplementor;
38  import org.hibernate.engine.SessionImplementor;
39  import org.hibernate.engine.SubselectFetch;
40  import org.hibernate.engine.TwoPhaseLoad;
41  import org.hibernate.event.PostLoadEvent;
42  import org.hibernate.event.PreLoadEvent;
43  import org.hibernate.exception.JDBCExceptionHelper;
44  import org.hibernate.impl.ScrollableResultsImpl;
45  import org.hibernate.jdbc.ColumnNameCache;
46  import org.hibernate.jdbc.ResultSetWrapper;
47  import org.hibernate.persister.collection.CollectionPersister;
48  import org.hibernate.persister.entity.EntityPersister;
49  import org.hibernate.persister.entity.Loadable;
50  import org.hibernate.persister.entity.UniqueKeyLoadable;
51  import org.hibernate.pretty.MessageHelper;
52  import org.hibernate.type.AssociationType;
53  import org.hibernate.type.EntityType;
54  import org.hibernate.type.Type;
55  import org.hibernate.type.VersionType;
56  import org.hibernate.util.StringHelper;
57  
58  /**
59   * Abstract superclass of object loading (and querying) strategies. This class implements
60   * useful common functionality that concrete loaders delegate to. It is not intended that this
61   * functionality would be directly accessed by client code. (Hence, all methods of this class
62   * are declared <tt>protected</tt> or <tt>private</tt>.) This class relies heavily upon the
63   * <tt>Loadable</tt> interface, which is the contract between this class and
64   * <tt>EntityPersister</tt>s that may be loaded by it.<br>
65   * <br>
66   * The present implementation is able to load any number of columns of entities and at most
67   * one collection role per query.
68   *
69   * @author Gavin King
70   * @see org.hibernate.persister.entity.Loadable
71   */
72  public abstract class Loader {
73  
74    private static final Log log = LogFactory.getLog( Loader.class );
75  
76    private final SessionFactoryImplementor factory;
77    private ColumnNameCache columnNameCache;
78  
79    public Loader(SessionFactoryImplementor factory) {
80      this.factory = factory;
81    }
82  
83    /**
84     * The SQL query string to be called; implemented by all subclasses
85     */
86    protected abstract String getSQLString();
87  
88    /**
89     * An array of persisters of entity classes contained in each row of results;
90     * implemented by all subclasses
91     */
92    protected abstract Loadable[] getEntityPersisters();
93    
94    /**
95     * An array indicating whether the entities have eager property fetching
96     * enabled
97     */
98    protected boolean[] getEntityEagerPropertyFetches() {
99      return null;
100   }
101 
102   /**
103    * An array of indexes of the entity that owns a one-to-one association
104    * to the entity at the given index (-1 if there is no "owner")
105    */
106   protected int[] getOwners() {
107     return null;
108   }
109 
110   /**
111    * An array of unique key property names by which the corresponding
112    * entities are referenced by other entities in the result set
113    */
114   protected EntityType[] getOwnerAssociationTypes() {
115     return null;
116   }
117 
118   /**
119    * An (optional) persister for a collection to be initialized; only collection loaders
120    * return a non-null value
121    */
122   protected CollectionPersister getCollectionPersister() {
123     return null;
124   }
125 
126   /**
127    * Get the index of the entity that owns the collection, or -1
128    * if there is no owner in the query results (ie. in the case of a
129    * collection initializer) or no collection.
130    */
131   protected int getCollectionOwner() {
132     return -1;
133   }
134 
135   /**
136    * What lock mode does this load entities with?
137    *
138    * @param lockModes a collection of lock modes specified dynamically via the Query interface
139    */
140   protected abstract LockMode[] getLockModes(Map lockModes);
141 
142   /**
143    * Append <tt>FOR UPDATE OF</tt> clause, if necessary. This
144    * empty superclass implementation merely returns its first
145    * argument.
146    */
147   protected String applyLocks(String sql, Map lockModes, Dialect dialect) throws HibernateException {
148     return sql;
149   }
150 
151   /**
152    * Does this query return objects that might be already cached
153    * by the session, whose lock mode may need upgrading
154    */
155   protected boolean upgradeLocks() {
156     return false;
157   }
158 
159   /**
160    * Return false is this loader is a batch entity loader
161    */
162   protected boolean isSingleRowLoader() {
163     return false;
164   }
165 
166   /**
167    * Get the SQL table aliases of entities whose
168    * associations are subselect-loadable, returning
169    * null if this loader does not support subselect
170    * loading
171    */
172   protected String[] getAliases() {
173     return null;
174   }
175 
176   /**
177    * Modify the SQL, adding lock hints and comments, if necessary
178    */
179   protected String preprocessSQL(String sql, QueryParameters parameters, Dialect dialect)
180       throws HibernateException {
181     sql = applyLocks( sql, parameters.getLockModes(), dialect );
182     String comment = parameters.getComment();
183     if ( comment == null ) {
184       return sql;
185     }
186     else {
187       return new StringBuffer( comment.length() + sql.length() + 5 )
188           .append( "/*" )
189           .append( comment )
190           .append( "*/ " )
191           .append( sql )
192           .toString();
193     }
194   }
195 
196   /**
197    * Execute an SQL query and attempt to instantiate instances of the class mapped by the given
198    * persister from each row of the <tt>ResultSet</tt>. If an object is supplied, will attempt to
199    * initialize that object. If a collection is supplied, attempt to initialize that collection.
200    */
201   private List doQueryAndInitializeNonLazyCollections(final SessionImplementor session,
202                             final QueryParameters queryParameters,
203                             final boolean returnProxies) 
204     throws HibernateException, SQLException {
205 
206     final PersistenceContext persistenceContext = session.getPersistenceContext();
207     persistenceContext.beforeLoad();
208     List result;
209     try {
210       result = doQuery( session, queryParameters, returnProxies );
211     }
212     finally {
213       persistenceContext.afterLoad();
214     }
215     persistenceContext.initializeNonLazyCollections();
216     return result;
217   }
218 
219   public Object loadSingleRow(final ResultSet resultSet,
220                 final SessionImplementor session,
221                 final QueryParameters queryParameters,
222                 final boolean returnProxies)
223       throws HibernateException {
224 
225     final int entitySpan = getEntityPersisters().length;
226     final List hydratedObjects = entitySpan == 0 ? null : new ArrayList( entitySpan );
227 
228     final Object result;
229     try {
230       result = getRowFromResultSet( resultSet,
231           session,
232           queryParameters,
233           getLockModes( queryParameters.getLockModes() ),
234           null,
235           hydratedObjects,
236           new EntityKey[entitySpan],
237           returnProxies );
238     }
239     catch ( SQLException sqle ) {
240       throw JDBCExceptionHelper.convert(
241               factory.getSQLExceptionConverter(),
242               sqle,
243               "could not read next row of results",
244               getSQLString()
245       );
246     }
247 
248     initializeEntitiesAndCollections( hydratedObjects, resultSet, session, queryParameters.isReadOnly() );
249     session.getPersistenceContext().initializeNonLazyCollections();
250     return result;
251   }
252 
253   private static EntityKey getOptionalObjectKey(QueryParameters queryParameters, SessionImplementor session) {
254     final Object optionalObject = queryParameters.getOptionalObject();
255     final Serializable optionalId = queryParameters.getOptionalId();
256     final String optionalEntityName = queryParameters.getOptionalEntityName();
257 
258     if ( optionalObject != null && optionalEntityName != null ) {
259       return new EntityKey( optionalId,
260           session.getEntityPersister( optionalEntityName, optionalObject ), 
261           session.getEntityMode()
262       );
263     }
264     else {
265       return null;
266     }
267 
268   }
269 
270   private Object getRowFromResultSet(final ResultSet resultSet,
271                      final SessionImplementor session,
272                      final QueryParameters queryParameters,
273                      final LockMode[] lockModeArray,
274                      final EntityKey optionalObjectKey,
275                      final List hydratedObjects,
276                      final EntityKey[] keys,
277                      boolean returnProxies)
278       throws SQLException, HibernateException {
279 
280     final Loadable[] persisters = getEntityPersisters();
281     final int entitySpan = persisters.length;
282 
283     for ( int i = 0; i < entitySpan; i++ ) {
284       keys[i] = getKeyFromResultSet( i,
285           persisters[i],
286           i == entitySpan - 1 ?
287           queryParameters.getOptionalId() :
288           null,
289           resultSet,
290           session );
291       //TODO: the i==entitySpan-1 bit depends upon subclass implementation (very bad)
292     }
293 
294     registerNonExists( keys, persisters, session );
295 
296     // this call is side-effecty
297     Object[] row = getRow( resultSet,
298         persisters,
299         keys,
300         queryParameters.getOptionalObject(),
301         optionalObjectKey,
302         lockModeArray,
303         hydratedObjects,
304         session );
305 
306     readCollectionElements( row, resultSet, session );
307 
308     if ( returnProxies ) {
309       // now get an existing proxy for each row element (if there is one)
310       for ( int i = 0; i < entitySpan; i++ ) {
311         row[i] = session.getPersistenceContext().proxyFor( persisters[i], keys[i], row[i] );
312         // force the proxy to resolve itself
313         Hibernate.initialize( row[i] ); //TODO: if there is an uninitialized proxy, this involves a hashmap lookup that could be avoided
314       }
315     }
316 
317     return getResultColumnOrRow( row, resultSet, session );
318 
319   }
320 
321   /**
322    * Read any collection elements contained in a single row of the result set
323    */
324   private void readCollectionElements(Object[] row, ResultSet resultSet, SessionImplementor session)
325       throws SQLException, HibernateException {
326 
327     //TODO: make this handle multiple collection roles!
328 
329     final CollectionPersister collectionPersister = getCollectionPersister();
330     if ( collectionPersister != null ) {
331 
332       final int collectionOwner = getCollectionOwner();
333       final boolean hasCollectionOwners = collectionOwner >= 0;
334       //true if this is a query and we are loading multiple instances of the same collection role
335       //otherwise this is a CollectionInitializer and we are loading up a single collection or batch
336 
337       final Object owner = hasCollectionOwners ?
338           row[collectionOwner] :
339           null; //if null, owner will be retrieved from session
340 
341       final Serializable key;
342       if ( owner == null ) {
343         key = null;
344       }
345       else {
346         key = collectionPersister.getCollectionType().getKeyOfOwner( owner, session );
347         //TODO: old version did not require hashmap lookup:
348         //keys[collectionOwner].getIdentifier()
349       }
350 
351       readCollectionElement( owner, key, resultSet, session );
352 
353     }
354   }
355 
356   private List doQuery(final SessionImplementor session,
357              final QueryParameters queryParameters,
358              final boolean returnProxies) throws SQLException, HibernateException {
359 
360     final RowSelection selection = queryParameters.getRowSelection();
361     final int maxRows = hasMaxRows( selection ) ?
362         selection.getMaxRows().intValue() :
363         Integer.MAX_VALUE;
364 
365     final int entitySpan = getEntityPersisters().length;
366 
367     final ArrayList hydratedObjects = entitySpan == 0 ? null : new ArrayList( entitySpan * 10 );
368     final List results = new ArrayList();
369     final PreparedStatement st = prepareQueryStatement( queryParameters, false, session );
370     final ResultSet rs = getResultSet( st, queryParameters.isCallable(), selection, session );
371 
372     final LockMode[] lockModeArray = getLockModes( queryParameters.getLockModes() );
373     final EntityKey optionalObjectKey = getOptionalObjectKey( queryParameters, session );
374 
375     final boolean createSubselects = isSubselectLoadingEnabled();
376     final List subselectResultKeys = createSubselects ? new ArrayList() : null;
377 
378     try {
379 
380       handleEmptyCollections( queryParameters.getCollectionKeys(), rs, session );
381 
382       EntityKey[] keys = new EntityKey[entitySpan]; //we can reuse it for each row
383 
384       if ( log.isTraceEnabled() ) log.trace( "processing result set" );
385 
386       int count;
387       for ( count = 0; count < maxRows && rs.next(); count++ ) {
388         
389         if ( log.isTraceEnabled() ) log.debug("result set row: " + count);
390 
391         Object result = getRowFromResultSet( rs,
392             session,
393             queryParameters,
394             lockModeArray,
395             optionalObjectKey,
396             hydratedObjects,
397             keys,
398             returnProxies );
399         results.add( result );
400 
401         if ( createSubselects ) {
402           subselectResultKeys.add(keys);
403           keys = new EntityKey[entitySpan]; //can't reuse in this case
404         }
405         
406       }
407 
408       if ( log.isTraceEnabled() ) log.trace( "done processing result set (" + count + " rows)" );
409 
410     }
411     finally {
412       session.getBatcher().closeQueryStatement( st, rs );
413     }
414 
415     initializeEntitiesAndCollections( hydratedObjects, rs, session, queryParameters.isReadOnly() );
416 
417     if ( createSubselects ) createSubselects( subselectResultKeys, queryParameters, session );
418 
419     return results; //getResultList(results);
420 
421   }
422 
423   protected boolean isSubselectLoadingEnabled() {
424     return false;
425   }
426   
427   protected boolean hasSubselectLoadableCollections() {
428     final Loadable[] loadables = getEntityPersisters();
429     for (int i=0; i<loadables.length; i++ ) {
430       if ( loadables[i].hasSubselectLoadableCollections() ) return true;
431     }
432     return false;
433   }
434   
435   private static EntityKey[][] transpose( EntityKey[][] keys ) {
436     EntityKey[][] result = new EntityKey[ keys[0].length ][];
437     for ( int j=0; j<result.length; j++ ) {
438       result[j] = new EntityKey[keys.length];
439       for ( int i=0; i<keys.length; i++ ) {
440         result[j][i] = keys[i][j];
441       }
442     }
443     return result;
444   }
445 
446   private void createSubselects(List keys, QueryParameters queryParameters, SessionImplementor session) {
447     if ( keys.size() > 1 ) { //if we only returned one entity, query by key is more efficient
448       
449       //TODO: remove unnecessary creation of outer array
450       EntityKey[][] keyArray = transpose( ( EntityKey[][] ) keys.toArray( new EntityKey[ keys.size() ][] ) );
451       
452       final Loadable[] loadables = getEntityPersisters();
453       final String[] aliases = getAliases();
454       final Iterator iter = keys.iterator();
455       while ( iter.hasNext() ) {
456         
457         final EntityKey[] rowKeys = (EntityKey[]) iter.next();
458         for ( int i=0; i<rowKeys.length; i++ ) {
459           
460           if ( rowKeys[i]!=null && loadables[i].hasSubselectLoadableCollections() ) {
461             
462             SubselectFetch subselectFetch = new SubselectFetch( 
463                 //getSQLString(), 
464                 aliases[i], 
465                 loadables[i], 
466                 queryParameters, 
467                 keyArray[i] 
468               );
469             
470             session.getPersistenceContext()
471               .getBatchFetchQueue()
472               .addSubselect( rowKeys[i], subselectFetch );
473           }
474           
475         }
476         
477       }
478     }
479   }
480 
481   private void initializeEntitiesAndCollections(
482       final List hydratedObjects,
483       final Object resultSetId,
484       final SessionImplementor session,
485       final boolean readOnly) 
486   throws HibernateException {
487     
488     //important: reuse the same event instances for performance!
489     final PreLoadEvent pre = new PreLoadEvent(session);
490     final PostLoadEvent post = new PostLoadEvent(session);
491     
492     final CollectionPersister collectionPersister = getCollectionPersister();
493     if ( collectionPersister != null && collectionPersister.isArray() ) {
494       //for arrays, we should end the collection load before resolving
495       //the entities, since the actual array instances are not instantiated
496       //during loading
497       //TODO: or we could do this polymorphically, and have two
498       //      different operations implemented differently for arrays
499       endCollectionLoad( resultSetId, session, collectionPersister );
500     }
501 
502     if ( getEntityPersisters().length > 0 ) { //if no persisters, hydratedObjects is null
503       int hydratedObjectsSize = hydratedObjects.size();
504       if ( log.isTraceEnabled() ) log.trace( "total objects hydrated: " + hydratedObjectsSize );
505       for ( int i = 0; i < hydratedObjectsSize; i++ ) {
506         TwoPhaseLoad.initializeEntity( hydratedObjects.get(i), readOnly, session, pre, post );
507       }
508     }
509     
510     if ( collectionPersister != null && !collectionPersister.isArray() ) {
511       //for sets, we should end the collection load after resolving
512       //the entities, since we might call hashCode() on the elements
513       //TODO: or we could do this polymorphically, and have two
514       //      different operations implemented differently for arrays
515       endCollectionLoad( resultSetId, session, collectionPersister );
516     }
517 
518   }
519 
520   private void endCollectionLoad(
521       final Object resultSetId, 
522       final SessionImplementor session, 
523       final CollectionPersister collectionPersister
524   ) {
525     //this is a query and we are loading multiple instances of the same collection role
526     session.getPersistenceContext().getCollectionLoadContext()
527       .endLoadingCollections( collectionPersister, resultSetId, session );
528   }
529 
530   protected List getResultList(List results) throws QueryException {
531     return results;
532   }
533 
534   /**
535    * Get the actual object that is returned in the user-visible result list.
536    * This empty implementation merely returns its first argument. This is
537    * overridden by some subclasses.
538    */
539   protected Object getResultColumnOrRow(Object[] row, ResultSet rs, SessionImplementor session)
540       throws SQLException, HibernateException {
541     return row;
542   }
543 
544   /**
545    * For missing objects associated by one-to-one with another object in the
546    * result set, register the fact that the the object is missing with the
547    * session.
548    */
549   private void registerNonExists(final EntityKey[] keys,
550                    final Loadable[] persisters,
551                    final SessionImplementor session) {
552     
553     final int[] owners = getOwners();
554     if ( owners != null ) {
555       
556       EntityType[] ownerAssociationTypes = getOwnerAssociationTypes();
557       for ( int i = 0; i < keys.length; i++ ) {
558         
559         int owner = owners[i];
560         if ( owner > -1 ) {
561           EntityKey ownerKey = keys[owner];
562           if ( keys[i] == null && ownerKey != null ) {
563             
564             final PersistenceContext persistenceContext = session.getPersistenceContext();
565             
566             /*final boolean isPrimaryKey;
567             final boolean isSpecialOneToOne;
568             if ( ownerAssociationTypes == null || ownerAssociationTypes[i] == null ) {
569               isPrimaryKey = true;
570               isSpecialOneToOne = false;
571             }
572             else {
573               isPrimaryKey = ownerAssociationTypes[i].getRHSUniqueKeyPropertyName()==null;
574               isSpecialOneToOne = ownerAssociationTypes[i].getLHSPropertyName()!=null;
575             }*/
576             
577             //TODO: can we *always* use the "null property" approach for everything?
578             /*if ( isPrimaryKey && !isSpecialOneToOne ) {
579               persistenceContext.addNonExistantEntityKey( 
580                   new EntityKey( ownerKey.getIdentifier(), persisters[i], session.getEntityMode() ) 
581               );
582             }
583             else if ( isSpecialOneToOne ) {*/
584             boolean isOneToOneAssociation = ownerAssociationTypes!=null && 
585                 ownerAssociationTypes[i]!=null && 
586                 ownerAssociationTypes[i].isOneToOne();
587             if ( isOneToOneAssociation ) {
588               persistenceContext.addNullProperty( ownerKey, 
589                   ownerAssociationTypes[i].getPropertyName() );
590             }
591             /*}
592             else {
593               persistenceContext.addNonExistantEntityUniqueKey( new EntityUniqueKey( 
594                   persisters[i].getEntityName(),
595                   ownerAssociationTypes[i].getRHSUniqueKeyPropertyName(),
596                   ownerKey.getIdentifier(),
597                   persisters[owner].getIdentifierType(),
598                   session.getEntityMode()
599               ) );
600             }*/
601           }
602         }
603       }
604     }
605   }
606 
607   /**
608    * Read one collection element from the current row of the JDBC result set
609    */
610   private void readCollectionElement(final Object optionalOwner,
611                      final Serializable optionalKey,
612                      final ResultSet rs,
613                      final SessionImplementor session)
614       throws HibernateException, SQLException {
615 
616     final CollectionPersister collectionPersister = getCollectionPersister();
617     final Serializable collectionRowKey = ( Serializable ) collectionPersister.readKey( rs, session );
618     final PersistenceContext persistenceContext = session.getPersistenceContext();
619     if ( collectionRowKey != null ) {
620       // we found a collection element in the result set
621 
622       if ( log.isDebugEnabled() ) {
623         log.debug( "found row of collection: " +
624             MessageHelper.collectionInfoString( collectionPersister, collectionRowKey, getFactory() ) );
625       }
626 
627       Object owner = optionalOwner;
628       if ( owner == null ) {
629         owner = persistenceContext.getCollectionOwner( collectionRowKey, collectionPersister );
630         if ( owner == null ) {
631           //TODO: This is assertion is disabled because there is a bug that means the
632           //    original owner of a transient, uninitialized collection is not known
633           //    if the collection is re-referenced by a different object associated
634           //    with the current Session
635           //throw new AssertionFailure("bug loading unowned collection");
636         }
637       }
638 
639       PersistentCollection rowCollection = persistenceContext.getCollectionLoadContext()
640         .getLoadingCollection( collectionPersister, collectionRowKey, rs, session.getEntityMode() );
641       if ( rowCollection != null ) rowCollection.readFrom( rs, collectionPersister, owner );
642 
643     }
644     else if ( optionalKey != null ) {
645       // we did not find a collection element in the result set, so we
646       // ensure that a collection is created with the owner's identifier,
647       // since what we have is an empty collection
648 
649       if ( log.isDebugEnabled() ) {
650         log.debug( "result set contains (possibly empty) collection: " +
651             MessageHelper.collectionInfoString( collectionPersister, optionalKey, getFactory() ) );
652       }
653 
654       persistenceContext.getCollectionLoadContext()
655         .getLoadingCollection( collectionPersister, optionalKey, rs, session.getEntityMode() ); //handle empty collection
656 
657     }
658 
659     // else no collection element, but also no owner
660   }
661 
662   /**
663    * If this is a collection initializer, we need to tell the session that a collection
664    * is being initilized, to account for the possibility of the collection having
665    * no elements (hence no rows in the result set).
666    */
667   private void handleEmptyCollections(final Serializable[] keys,
668                     final Object resultSetId,
669                     final SessionImplementor session)
670       throws HibernateException {
671 
672     if ( keys != null ) {
673       // this is a collection initializer, so we must create a collection
674       // for each of the passed-in keys, to account for the possibility
675       // that the collection is empty and has no rows in the result set
676 
677       CollectionPersister collectionPersister = getCollectionPersister();
678       for ( int i = 0; i < keys.length; i++ ) {
679         //handle empty collections
680 
681         if ( log.isDebugEnabled() ) {
682           log.debug( "result set contains (possibly empty) collection: " +
683               MessageHelper.collectionInfoString( collectionPersister, keys[i], getFactory() ) );
684         }
685 
686         session.getPersistenceContext()
687           .getCollectionLoadContext()
688           .getLoadingCollection( collectionPersister, keys[i], resultSetId, session.getEntityMode() );
689       }
690 
691     }
692 
693     // else this is not a collection initializer (and empty collections will
694     // be detected by looking for the owner's identifier in the result set)
695   }
696 
697   /**
698    * Read a row of <tt>Key</tt>s from the <tt>ResultSet</tt> into the given array.
699    * Warning: this method is side-effecty.
700    * <p/>
701    * If an <tt>id</tt> is given, don't bother going to the <tt>ResultSet</tt>.
702    */
703   private EntityKey getKeyFromResultSet(final int i,
704                       final Loadable persister,
705                       final Serializable id,
706                       final ResultSet rs,
707                       final SessionImplementor session)
708       throws HibernateException, SQLException {
709 
710     Serializable resultId;
711 
712     // if we know there is exactly 1 row, we can skip.
713     // it would be great if we could _always_ skip this;
714     // it is a problem for <key-many-to-one>
715 
716     if ( isSingleRowLoader() && id != null ) {
717       resultId = id;
718     }
719     else {
720       
721       Type idType = persister.getIdentifierType();
722       resultId = (Serializable) idType.nullSafeGet(
723           rs,
724           getEntityAliases()[i].getSuffixedKeyAliases(),
725           session,
726           null //problematic for <key-many-to-one>!
727       );
728       
729       final boolean idIsResultId = id != null && 
730           resultId != null && 
731           idType.isEqual( id, resultId, session.getEntityMode(), factory );
732       
733       if ( idIsResultId ) resultId = id; //use the id passed in
734     }
735 
736     return resultId == null ?
737         null :
738         new EntityKey( resultId, persister, session.getEntityMode() );
739   }
740 
741   /**
742    * Check the version of the object in the <tt>ResultSet</tt> against
743    * the object version in the session cache, throwing an exception
744    * if the version numbers are different
745    */
746   private void checkVersion(final int i,
747                 final Loadable persister,
748                 final Serializable id,
749                 final Object entity,
750                 final ResultSet rs,
751                 final SessionImplementor session)
752       throws HibernateException, SQLException {
753 
754     Object version = session.getPersistenceContext().getEntry( entity ).getVersion();
755 
756     if ( version != null ) { //null version means the object is in the process of being loaded somewhere else in the ResultSet
757       VersionType versionType = persister.getVersionType();
758       Object currentVersion = versionType.nullSafeGet(
759           rs,
760           getEntityAliases()[i].getSuffixedVersionAliases(),
761           session,
762           null
763       );
764       if ( !versionType.isEqual(version, currentVersion) ) {
765         throw new StaleObjectStateException( persister.getEntityName(), id );
766       }
767     }
768 
769   }
770 
771   /**
772    * Resolve any ids for currently loaded objects, duplications within the
773    * <tt>ResultSet</tt>, etc. Instantiate empty objects to be initialized from the
774    * <tt>ResultSet</tt>. Return an array of objects (a row of results) and an
775    * array of booleans (by side-effect) that determine whether the corresponding
776    * object should be initialized.
777    */
778   private Object[] getRow(final ResultSet rs,
779               final Loadable[] persisters,
780               final EntityKey[] keys,
781               final Object optionalObject,
782               final EntityKey optionalObjectKey,
783               final LockMode[] lockModes,
784               final List hydratedObjects,
785               final SessionImplementor session)
786       throws HibernateException, SQLException {
787 
788     final int cols = persisters.length;
789     final EntityAliases[] descriptors = getEntityAliases();
790 
791     if ( log.isDebugEnabled() ) log.debug( "result row: " + StringHelper.toString( keys ) );
792 
793     final Object[] rowResults = new Object[cols];
794 
795     for ( int i = 0; i < cols; i++ ) {
796 
797       Object object = null;
798       EntityKey key = keys[i];
799 
800       if ( keys[i] == null ) {
801         //do nothing
802       }
803       else {
804 
805         //If the object is already loaded, return the loaded one
806         object = session.getEntityUsingInterceptor( key );
807         if ( object != null ) {
808           //its already loaded so don't need to hydrate it
809           instanceAlreadyLoaded( rs,
810               i,
811               persisters[i],
812               key,
813               object,
814               lockModes[i],
815               session );
816         }
817         else {
818           object = instanceNotYetLoaded( rs,
819               i,
820               persisters[i],
821               descriptors[i].getRowIdAlias(),
822               key,
823               lockModes[i],
824               optionalObjectKey,
825               optionalObject,
826               hydratedObjects,
827               session );
828         }
829 
830       }
831 
832       rowResults[i] = object;
833 
834     }
835 
836     return rowResults;
837 
838   }
839 
840   /**
841    * The entity instance is already in the session cache
842    */
843   private void instanceAlreadyLoaded(final ResultSet rs,
844                      final int i,
845                      final Loadable persister,
846                      final EntityKey key,
847                      final Object object,
848                      final LockMode lockMode,
849                      final SessionImplementor session)
850       throws HibernateException, SQLException {
851 
852     if ( !persister.isInstance( object, session.getEntityMode() ) ) {
853       throw new WrongClassException( "loaded object was of wrong class", key.getIdentifier(), persister.getEntityName() );
854     }
855 
856     if ( LockMode.NONE != lockMode && upgradeLocks() ) { //no point doing this if NONE was requested
857 
858       final boolean isVersionCheckNeeded = persister.isVersioned() &&
859           session.getPersistenceContext().getLockMode( object ).lessThan( lockMode );
860       // we don't need to worry about existing version being uninitialized
861       // because this block isn't called by a re-entrant load (re-entrant
862       // loads _always_ have lock mode NONE)
863       if (isVersionCheckNeeded) {
864         //we only check the version when _upgrading_ lock modes
865         checkVersion( i, persister, key.getIdentifier(), object, rs, session );
866         //we need to upgrade the lock mode to the mode requested
867         session.getPersistenceContext().setLockMode( object, lockMode );
868       }
869     }
870   }
871 
872   /**
873    * The entity instance is not in the session cache
874    */
875   private Object instanceNotYetLoaded(final ResultSet rs,
876                     final int i,
877                     final Loadable persister,
878                     final String rowIdAlias,
879                     final EntityKey key,
880                     final LockMode lockMode,
881                     final EntityKey optionalObjectKey,
882                     final Object optionalObject,
883                     final List hydratedObjects,
884                     final SessionImplementor session)
885       throws HibernateException, SQLException {
886 
887     final String instanceClass = getInstanceClass( rs, i, persister, key.getIdentifier(), session );
888 
889     final Object object;
890     if ( optionalObjectKey != null && key.equals( optionalObjectKey ) ) {
891       //its the given optional object
892       object = optionalObject;
893     }
894     else {
895       // instantiate a new instance
896       object = session.instantiate( instanceClass, key.getIdentifier() );
897     }
898 
899     //need to hydrate it.
900 
901     // grab its state from the ResultSet and keep it in the Session
902     // (but don't yet initialize the object itself)
903     // note that we acquire LockMode.READ even if it was not requested
904     LockMode acquiredLockMode = lockMode == LockMode.NONE ? LockMode.READ : lockMode;
905     loadFromResultSet( rs, i, object, instanceClass, key, rowIdAlias, acquiredLockMode, persister, session );
906 
907     //materialize associations (and initialize the object) later
908     hydratedObjects.add( object );
909 
910     return object;
911   }
912   
913   private boolean isEagerPropertyFetchEnabled(int i) {
914     boolean[] array = getEntityEagerPropertyFetches();
915     return array!=null && array[i];
916   }
917 
918 
919   /**
920    * Hydrate the state an object from the SQL <tt>ResultSet</tt>, into
921    * an array or "hydrated" values (do not resolve associations yet),
922    * and pass the hydrates state to the session.
923    */
924   private void loadFromResultSet(final ResultSet rs,
925                    final int i,
926                    final Object object,
927                    final String instanceEntityName,
928                    final EntityKey key,
929                    final String rowIdAlias,
930                    final LockMode lockMode,
931                    final Loadable rootPersister,
932                    final SessionImplementor session)
933       throws SQLException, HibernateException {
934 
935     final Serializable id = key.getIdentifier();
936 
937     // Get the persister for the _subclass_
938     final Loadable persister = (Loadable) getFactory().getEntityPersister( instanceEntityName );
939 
940     if ( log.isTraceEnabled() ) {
941       log.trace( "Initializing object from ResultSet: " + MessageHelper.infoString(persister, id, getFactory()) );
942     }
943 
944     // add temp entry so that the next step is circular-reference
945     // safe - only needed because some types don't take proper
946     // advantage of two-phase-load (esp. components)
947     TwoPhaseLoad.addUninitializedEntity( id, object, persister, lockMode, session );
948 
949     //This is not very nice (and quite slow):
950     final String[][] cols = persister == rootPersister ?
951         getEntityAliases()[i].getSuffixedPropertyAliases() :
952         getEntityAliases()[i].getSuffixedPropertyAliases(persister);
953 
954     final Object[] values = persister.hydrate( rs, id, object, rootPersister, session, cols, isEagerPropertyFetchEnabled(i) );
955 
956     final Object rowId = persister.hasRowId() ? rs.getObject(rowIdAlias) : null;
957 
958     final AssociationType[] ownerAssociationTypes = getOwnerAssociationTypes();
959     if ( ownerAssociationTypes != null && ownerAssociationTypes[i] != null ) {
960       String ukName = ownerAssociationTypes[i].getRHSUniqueKeyPropertyName();
961       if (ukName!=null) {
962         final int index = ( (UniqueKeyLoadable) persister ).getPropertyIndex(ukName);
963         final Type type = persister.getPropertyTypes()[index];
964   
965         // polymorphism not really handled completely correctly,
966         // perhaps...well, actually its ok, assuming that the
967         // entity name used in the lookup is the same as the
968         // the one used here, which it will be
969   
970         EntityUniqueKey euk = new EntityUniqueKey( 
971             rootPersister.getEntityName(), //polymorphism comment above
972             ukName,
973             type.semiResolve( values[index], session, object ),
974             type,
975             session.getEntityMode()
976         );
977         session.getPersistenceContext().addEntity( euk, object );
978       }
979     }
980 
981     TwoPhaseLoad.postHydrate( persister, id, values, rowId, object, lockMode, session );
982 
983   }
984 
985   /**
986    * Determine the concrete class of an instance in the <tt>ResultSet</tt>
987    */
988   private String getInstanceClass(final ResultSet rs,
989                   final int i,
990                   final Loadable persister,
991                   final Serializable id,
992                   final SessionImplementor session)
993       throws HibernateException, SQLException {
994 
995     if ( persister.hasSubclasses() ) {
996 
997       // Code to handle subclasses of topClass
998       Object discriminatorValue = persister.getDiscriminatorType().nullSafeGet(
999           rs,
1000          getEntityAliases()[i].getSuffixedDiscriminatorAlias(),
1001          session,
1002          null
1003      );
1004
1005      final String result = persister.getSubclassForDiscriminatorValue( discriminatorValue );
1006
1007      if ( result == null ) {
1008        //woops we got an instance of another class hierarchy branch
1009        throw new WrongClassException( 
1010            "Discriminator: " + discriminatorValue,
1011            id,
1012            persister.getEntityName() 
1013        );
1014      }
1015
1016      return result;
1017
1018    }
1019    else {
1020      return persister.getEntityName();
1021    }
1022  }
1023
1024  /**
1025   * Advance the cursor to the first required row of the <tt>ResultSet</tt>
1026   */
1027  private void advance(final ResultSet rs, final RowSelection selection)
1028      throws SQLException {
1029
1030    final int firstRow = getFirstRow( selection );
1031    if ( firstRow != 0 ) {
1032      if ( getFactory().getSettings().isScrollableResultSetsEnabled() ) {
1033        // we can go straight to the first required row
1034        rs.absolute( firstRow );
1035      }
1036      else {
1037        // we need to step through the rows one row at a time (slow)
1038        for ( int m = 0; m < firstRow; m++ ) rs.next();
1039      }
1040    }
1041  }
1042
1043  private static boolean hasMaxRows(RowSelection selection) {
1044    return selection != null && selection.getMaxRows() != null;
1045  }
1046
1047  private static int getFirstRow(RowSelection selection) {
1048    if ( selection == null || selection.getFirstRow() == null ) {
1049      return 0;
1050    }
1051    else {
1052      return selection.getFirstRow().intValue();
1053    }
1054  }
1055
1056  /**
1057   * Should we pre-process the SQL string, adding a dialect-specific
1058   * LIMIT clause.
1059   */
1060  private static boolean useLimit(final RowSelection selection, final Dialect dialect) {
1061    return dialect.supportsLimit() && hasMaxRows( selection );
1062  }
1063
1064  /**
1065   * Bind positional parameter values to the <tt>PreparedStatement</tt>
1066   * (these are parameters specified by a JDBC-style ?).
1067   */
1068  protected int bindPositionalParameters(final PreparedStatement st,
1069                       final QueryParameters queryParameters,
1070                       final int start,
1071                       final SessionImplementor session)
1072      throws SQLException, HibernateException {
1073
1074    final Object[] values = queryParameters.getFilteredPositionalParameterValues();
1075    final Type[] types = queryParameters.getFilteredPositionalParameterTypes();
1076    int span = 0;
1077    for ( int i = 0; i < values.length; i++ ) {
1078      types[i].nullSafeSet( st, values[i], start + span, session );
1079      span += types[i].getColumnSpan( getFactory() );
1080    }
1081    return span;
1082  }
1083
1084  /**
1085   * Obtain a <tt>PreparedStatement</tt> with all parameters pre-bound.
1086   * Bind JDBC-style <tt>?</tt> parameters, named parameters, and
1087   * limit parameters.
1088   */
1089  protected final PreparedStatement prepareQueryStatement(final QueryParameters queryParameters,
1090                              final boolean scroll,
1091                              final SessionImplementor session)
1092      throws SQLException, HibernateException {
1093
1094    queryParameters.processFilters( getSQLString(), session );
1095    String sql = queryParameters.getFilteredSQL();
1096    final Dialect dialect = getFactory().getDialect();
1097    final RowSelection selection = queryParameters.getRowSelection();
1098    boolean useLimit = useLimit( selection, dialect );
1099    boolean hasFirstRow = getFirstRow( selection ) > 0;
1100    boolean useOffset = hasFirstRow && useLimit && dialect.supportsLimitOffset();
1101    boolean callable = queryParameters.isCallable();
1102    
1103    boolean useScrollableResultSetToSkip = hasFirstRow &&
1104        !useOffset &&
1105        getFactory().getSettings().isScrollableResultSetsEnabled();
1106    ScrollMode scrollMode = scroll ? queryParameters.getScrollMode() : ScrollMode.SCROLL_INSENSITIVE;
1107
1108    if ( useLimit ) {
1109      sql = dialect.getLimitString( 
1110          sql.trim(), //use of trim() here is ugly?
1111          useOffset ? getFirstRow(selection) : 0, 
1112          getMaxOrLimit(selection, dialect) 
1113      );
1114    }
1115
1116    sql = preprocessSQL( sql, queryParameters, dialect );
1117    
1118    PreparedStatement st = null;
1119    
1120    if (callable) {
1121      st = session.getBatcher()
1122        .prepareCallableQueryStatement( sql, scroll || useScrollableResultSetToSkip, scrollMode );
1123    } 
1124    else {
1125      st = session.getBatcher()
1126        .prepareQueryStatement( sql, scroll || useScrollableResultSetToSkip, scrollMode );
1127    }
1128        
1129
1130    try {
1131
1132      int col = 1;
1133      //TODO: can we limit stored procedures ?!
1134      if ( useLimit && dialect.bindLimitParametersFirst() ) {
1135        col += bindLimitParameters( st, col, selection );
1136      }
1137      if (callable) {
1138        col = dialect.registerResultSetOutParameter( (CallableStatement)st, col );
1139      }
1140      col += bindPositionalParameters( st, queryParameters, col, session );
1141      col += bindNamedParameters( st, queryParameters.getNamedParameters(), col, session );
1142
1143      if ( useLimit && !dialect.bindLimitParametersFirst() ) {
1144        col += bindLimitParameters( st, col, selection );
1145      }
1146
1147      if ( !useLimit ) setMaxRows( st, selection );
1148      if ( selection != null ) {
1149        if ( selection.getTimeout() != null ) {
1150          st.setQueryTimeout( selection.getTimeout().intValue() );
1151        }
1152        if ( selection.getFetchSize() != null ) {
1153          st.setFetchSize( selection.getFetchSize().intValue() );
1154        }
1155      }
1156    }
1157    catch ( SQLException sqle ) {
1158      session.getBatcher().closeQueryStatement( st, null );
1159      throw sqle;
1160    }
1161    catch ( HibernateException he ) {
1162      session.getBatcher().closeQueryStatement( st, null );
1163      throw he;
1164    }
1165
1166    return st;
1167  }
1168
1169  /**
1170   * Some dialect-specific LIMIT clauses require the maximium last row number,
1171   * others require the maximum returned row count.
1172   */
1173  private static int getMaxOrLimit(final RowSelection selection, final Dialect dialect) {
1174    final int firstRow = getFirstRow( selection );
1175    final int lastRow = selection.getMaxRows().intValue();
1176    if ( dialect.useMaxForLimit() ) {
1177      return lastRow + firstRow;
1178    }
1179    else {
1180      return lastRow;
1181    }
1182  }
1183
1184  /**
1185   * Bind parameters needed by the dialect-specific LIMIT clause
1186   */
1187  private int bindLimitParameters(final PreparedStatement st, final int index, final RowSelection selection)
1188      throws SQLException {
1189
1190    Dialect dialect = getFactory().getDialect();
1191    if ( !dialect.supportsVariableLimit() ) return 0;
1192    if ( !hasMaxRows( selection ) ) throw new AssertionFailure( "no max results set" );
1193    int firstRow = getFirstRow( selection );
1194    int lastRow = getMaxOrLimit( selection, dialect );
1195    boolean hasFirstRow = firstRow > 0 && dialect.supportsLimitOffset();
1196    boolean reverse = dialect.bindLimitParametersInReverseOrder();
1197    if ( hasFirstRow ) st.setInt( index + ( reverse ? 1 : 0 ), firstRow );
1198    st.setInt( index + ( reverse || !hasFirstRow ? 0 : 1 ), lastRow );
1199    return hasFirstRow ? 2 : 1;
1200  }
1201
1202  /**
1203   * Use JDBC API to limit the number of rows returned by the SQL query if necessary
1204   */
1205  private void setMaxRows(final PreparedStatement st, final RowSelection selection)
1206      throws SQLException {
1207    if ( hasMaxRows( selection ) ) {
1208      st.setMaxRows( selection.getMaxRows().intValue() + getFirstRow( selection ) );
1209    }
1210  }
1211
1212  protected final ResultSet getResultSet(final PreparedStatement st,
1213         final RowSelection selection,
1214         final SessionImplementor session) throws HibernateException, SQLException {
1215    return getResultSet(st, false, selection, session);
1216  }
1217
1218  /**
1219   * Fetch a <tt>PreparedStatement</tt>, call <tt>setMaxRows</tt> and then execute it,
1220   * advance to the first result and return an SQL <tt>ResultSet</tt>
1221   */
1222  protected final ResultSet getResultSet(final PreparedStatement st,
1223                       final boolean callable,
1224                       final RowSelection selection,
1225                       final SessionImplementor session)
1226      throws SQLException, HibernateException {
1227  
1228    ResultSet rs = null;
1229    try {
1230      if(callable) {
1231        rs = session.getBatcher().getResultSet( (CallableStatement)st, getFactory().getDialect() );
1232      } else {
1233        rs = session.getBatcher().getResultSet( st );
1234      }
1235      rs = wrapResultSetIfEnabled( rs , session );
1236      Dialect dialect = getFactory().getDialect();
1237      if ( !dialect.supportsLimitOffset() || !useLimit( selection, dialect ) ) {
1238        advance( rs, selection );
1239      }
1240      return rs;
1241    }
1242    catch ( SQLException sqle ) {
1243      session.getBatcher().closeQueryStatement( st, rs );
1244      throw sqle;
1245    }
1246  }
1247
1248  private synchronized ResultSet wrapResultSetIfEnabled(final ResultSet rs, final SessionImplementor session) {
1249    // synchronized to avoid multi-thread access issues; defined as method synch to avoid
1250    // potential deadlock issues due to nature of code.
1251    if ( session.getFactory().getSettings().isWrapResultSetsEnabled() ) {
1252      try {
1253        log.debug("Wrapping result set [" + rs + "]");
1254        return new ResultSetWrapper( rs, retreiveColumnNameToIndexCache( rs ) );
1255      }
1256      catch(SQLException e) {
1257        log.info("Error wrapping result set", e);
1258        return rs;
1259      }
1260    }
1261    else {
1262      return rs;
1263    }
1264  }
1265
1266  private ColumnNameCache retreiveColumnNameToIndexCache(ResultSet rs) throws SQLException {
1267    if ( columnNameCache == null ) {
1268      log.trace("Building columnName->columnIndex cache");
1269      columnNameCache = new ColumnNameCache( rs.getMetaData().getColumnCount() );
1270    }
1271
1272    return columnNameCache;
1273  }
1274
1275  /**
1276   * Bind named parameters to the <tt>PreparedStatement</tt>. This has an empty
1277   * implementation on this superclass and should be implemented by subclasses
<