KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > hibernate > loader > Loader


1 //$Id: Loader.java,v 1.117 2005/07/12 20:12:56 oneovthafew Exp $
2
package org.hibernate.loader;
3
4 import java.io.Serializable JavaDoc;
5 import java.sql.CallableStatement JavaDoc;
6 import java.sql.PreparedStatement JavaDoc;
7 import java.sql.ResultSet JavaDoc;
8 import java.sql.SQLException JavaDoc;
9 import java.util.ArrayList JavaDoc;
10 import java.util.Arrays JavaDoc;
11 import java.util.HashSet JavaDoc;
12 import java.util.Iterator JavaDoc;
13 import java.util.List JavaDoc;
14 import java.util.Map JavaDoc;
15 import java.util.Set JavaDoc;
16
17 import org.apache.commons.logging.Log;
18 import org.apache.commons.logging.LogFactory;
19 import org.hibernate.AssertionFailure;
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.EventSource;
42 import org.hibernate.event.PostLoadEvent;
43 import org.hibernate.event.PreLoadEvent;
44 import org.hibernate.exception.JDBCExceptionHelper;
45 import org.hibernate.hql.HolderInstantiator;
46 import org.hibernate.impl.ScrollableResultsImpl;
47 import org.hibernate.impl.FetchingScrollableResultsImpl;
48 import org.hibernate.jdbc.ColumnNameCache;
49 import org.hibernate.jdbc.ResultSetWrapper;
50 import org.hibernate.persister.collection.CollectionPersister;
51 import org.hibernate.persister.entity.EntityPersister;
52 import org.hibernate.persister.entity.Loadable;
53 import org.hibernate.persister.entity.UniqueKeyLoadable;
54 import org.hibernate.pretty.MessageHelper;
55 import org.hibernate.proxy.HibernateProxy;
56 import org.hibernate.type.AssociationType;
57 import org.hibernate.type.EntityType;
58 import org.hibernate.type.Type;
59 import org.hibernate.type.VersionType;
60 import org.hibernate.util.StringHelper;
61
62 /**
63  * Abstract superclass of object loading (and querying) strategies. This class implements
64  * useful common functionality that concrete loaders delegate to. It is not intended that this
65  * functionality would be directly accessed by client code. (Hence, all methods of this class
66  * are declared <tt>protected</tt> or <tt>private</tt>.) This class relies heavily upon the
67  * <tt>Loadable</tt> interface, which is the contract between this class and
68  * <tt>EntityPersister</tt>s that may be loaded by it.<br>
69  * <br>
70  * The present implementation is able to load any number of columns of entities and at most
71  * one collection role per query.
72  *
73  * @author Gavin King
74  * @see org.hibernate.persister.entity.Loadable
75  */

76 public abstract class Loader {
77
78     private static final Log log = LogFactory.getLog( Loader.class );
79
80     private final SessionFactoryImplementor factory;
81     private ColumnNameCache columnNameCache;
82
83     public Loader(SessionFactoryImplementor factory) {
84         this.factory = factory;
85     }
86
87     /**
88      * The SQL query string to be called; implemented by all subclasses
89      */

90     protected abstract String JavaDoc getSQLString();
91
92     /**
93      * An array of persisters of entity classes contained in each row of results;
94      * implemented by all subclasses
95      */

96     protected abstract Loadable[] getEntityPersisters();
97     
98     /**
99      * An array indicating whether the entities have eager property fetching
100      * enabled
101      */

102     protected boolean[] getEntityEagerPropertyFetches() {
103         return null;
104     }
105
106     /**
107      * An array of indexes of the entity that owns a one-to-one association
108      * to the entity at the given index (-1 if there is no "owner")
109      */

110     protected int[] getOwners() {
111         return null;
112     }
113
114     /**
115      * An array of unique key property names by which the corresponding
116      * entities are referenced by other entities in the result set
117      */

118     protected EntityType[] getOwnerAssociationTypes() {
119         return null;
120     }
121
122     /**
123      * An (optional) persister for a collection to be initialized; only
124      * collection loaders return a non-null value
125      */

126     protected CollectionPersister[] getCollectionPersisters() {
127         return null;
128     }
129
130     /**
131      * Get the index of the entity that owns the collection, or -1
132      * if there is no owner in the query results (ie. in the case of a
133      * collection initializer) or no collection.
134      */

135     protected int[] getCollectionOwners() {
136         return null;
137     }
138
139     /**
140      * What lock mode does this load entities with?
141      *
142      * @param lockModes a collection of lock modes specified dynamically via the Query interface
143      */

144     protected abstract LockMode[] getLockModes(Map JavaDoc lockModes);
145
146     /**
147      * Append <tt>FOR UPDATE OF</tt> clause, if necessary. This
148      * empty superclass implementation merely returns its first
149      * argument.
150      */

151     protected String JavaDoc applyLocks(String JavaDoc sql, Map JavaDoc lockModes, Dialect dialect) throws HibernateException {
152         return sql;
153     }
154
155     /**
156      * Does this query return objects that might be already cached
157      * by the session, whose lock mode may need upgrading
158      */

159     protected boolean upgradeLocks() {
160         return false;
161     }
162
163     /**
164      * Return false is this loader is a batch entity loader
165      */

166     protected boolean isSingleRowLoader() {
167         return false;
168     }
169
170     /**
171      * Get the SQL table aliases of entities whose
172      * associations are subselect-loadable, returning
173      * null if this loader does not support subselect
174      * loading
175      */

176     protected String JavaDoc[] getAliases() {
177         return null;
178     }
179
180     /**
181      * Modify the SQL, adding lock hints and comments, if necessary
182      */

183     protected String JavaDoc preprocessSQL(String JavaDoc sql, QueryParameters parameters, Dialect dialect)
184             throws HibernateException {
185         
186         sql = applyLocks( sql, parameters.getLockModes(), dialect );
187         
188         return getFactory().getSettings().isCommentsEnabled() ?
189                 prependComment( sql, parameters ) : sql;
190     }
191
192     private String JavaDoc prependComment(String JavaDoc sql, QueryParameters parameters) {
193         String JavaDoc comment = parameters.getComment();
194         if ( comment == null ) {
195             return sql;
196         }
197         else {
198             return new StringBuffer JavaDoc( comment.length() + sql.length() + 5 )
199                     .append( "/* " )
200                     .append( comment )
201                     .append( " */ " )
202                     .append( sql )
203                     .toString();
204         }
205     }
206
207     /**
208      * Execute an SQL query and attempt to instantiate instances of the class mapped by the given
209      * persister from each row of the <tt>ResultSet</tt>. If an object is supplied, will attempt to
210      * initialize that object. If a collection is supplied, attempt to initialize that collection.
211      */

212     private List JavaDoc doQueryAndInitializeNonLazyCollections(final SessionImplementor session,
213                                                         final QueryParameters queryParameters,
214                                                         final boolean returnProxies)
215         throws HibernateException, SQLException JavaDoc {
216
217         final PersistenceContext persistenceContext = session.getPersistenceContext();
218         persistenceContext.beforeLoad();
219         List JavaDoc result;
220         try {
221             result = doQuery( session, queryParameters, returnProxies );
222         }
223         finally {
224             persistenceContext.afterLoad();
225         }
226         persistenceContext.initializeNonLazyCollections();
227         return result;
228     }
229
230     /**
231      * Loads a single row from the result set. This is the processing used from the
232      * ScrollableResults where no collection fetches were encountered.
233      *
234      * @param resultSet The result set from which to do the load.
235      * @param session The session from which the request originated.
236      * @param queryParameters The query parameters specified by the user.
237      * @param returnProxies Should proxies be generated
238      * @return The loaded "row".
239      * @throws HibernateException
240      */

241     public Object JavaDoc loadSingleRow(
242             final ResultSet JavaDoc resultSet,
243             final SessionImplementor session,
244             final QueryParameters queryParameters,
245             final boolean returnProxies) throws HibernateException {
246
247         final int entitySpan = getEntityPersisters().length;
248         final List JavaDoc hydratedObjects = entitySpan == 0 ? null : new ArrayList JavaDoc( entitySpan );
249
250         final Object JavaDoc result;
251         try {
252             result = getRowFromResultSet(
253                     resultSet,
254                     session,
255                     queryParameters,
256                     getLockModes( queryParameters.getLockModes() ),
257                     null,
258                     hydratedObjects,
259                     new EntityKey[entitySpan],
260                     returnProxies
261             );
262         }
263         catch ( SQLException JavaDoc sqle ) {
264             throw JDBCExceptionHelper.convert(
265                     factory.getSQLExceptionConverter(),
266                     sqle,
267                     "could not read next row of results",
268                     getSQLString()
269                 );
270         }
271
272         initializeEntitiesAndCollections( hydratedObjects, resultSet, session, queryParameters.isReadOnly() );
273         session.getPersistenceContext().initializeNonLazyCollections();
274         return result;
275     }
276
277     private Object JavaDoc sequentialLoad(
278             final ResultSet JavaDoc resultSet,
279             final SessionImplementor session,
280             final QueryParameters queryParameters,
281             final boolean returnProxies,
282             final EntityKey keyToRead) throws HibernateException {
283
284         final int entitySpan = getEntityPersisters().length;
285         final List JavaDoc hydratedObjects = entitySpan == 0 ? null : new ArrayList JavaDoc( entitySpan );
286
287         Object JavaDoc result = null;
288         final EntityKey[] loadedKeys = new EntityKey[entitySpan];
289
290         try {
291             do {
292                 Object JavaDoc loaded = getRowFromResultSet(
293                         resultSet,
294                         session,
295                         queryParameters,
296                         getLockModes( queryParameters.getLockModes() ),
297                         null,
298                         hydratedObjects,
299                         loadedKeys,
300                         returnProxies
301                 );
302                 if ( result == null ) {
303                     result = loaded;
304                 }
305             } while ( keyToRead.equals( loadedKeys[0] ) && resultSet.next() );
306         }
307         catch ( SQLException JavaDoc sqle ) {
308             throw JDBCExceptionHelper.convert(
309                     factory.getSQLExceptionConverter(),
310                     sqle,
311                     "could not perform sequential read of results (forward)",
312                     getSQLString()
313                 );
314         }
315
316         initializeEntitiesAndCollections( hydratedObjects, resultSet, session, queryParameters.isReadOnly() );
317         session.getPersistenceContext().initializeNonLazyCollections();
318         return result;
319     }
320
321     /**
322      * Loads a single logical row from the result set moving forward. This is the
323      * processing used from the ScrollableResults where there were collection fetches
324      * encountered; thus a single logical row may have multiple rows in the underlying
325      * result set.
326      *
327      * @param resultSet The result set from which to do the load.
328      * @param session The session from which the request originated.
329      * @param queryParameters The query parameters specified by the user.
330      * @param returnProxies Should proxies be generated
331      * @return The loaded "row".
332      * @throws HibernateException
333      */

334     public Object JavaDoc loadSequentialRowsForward(
335             final ResultSet JavaDoc resultSet,
336             final SessionImplementor session,
337             final QueryParameters queryParameters,
338             final boolean returnProxies) throws HibernateException {
339
340         // note that for sequential scrolling, we make the assumption that
341
// the first persister element is the "root entity"
342

343         try {
344             if ( resultSet.isAfterLast() ) {
345                 // don't even bother trying to read further
346
return null;
347             }
348
349             if ( resultSet.isBeforeFirst() ) {
350                 resultSet.first();
351             }
352
353             // We call getKeyFromResultSet() here so that we can know the
354
// key value upon which to perform the breaking logic. However,
355
// it is also then called from getRowFromResultSet() which is certainly
356
// not the most efficient. But the call here is needed, and there
357
// currently is no other way without refactoring of the doQuery()/getRowFromResultSet()
358
// methods
359
final EntityKey currentKey = getKeyFromResultSet(
360                     0,
361                     getEntityPersisters()[0],
362                     null,
363                     resultSet,
364                     session
365             );
366
367             return sequentialLoad( resultSet, session, queryParameters, returnProxies, currentKey );
368         }
369         catch ( SQLException JavaDoc sqle ) {
370             throw JDBCExceptionHelper.convert(
371                     factory.getSQLExceptionConverter(),
372                     sqle,
373                     "could not perform sequential read of results (forward)",
374                     getSQLString()
375                 );
376         }
377     }
378
379     /**
380      * Loads a single logical row from the result set moving forward. This is the
381      * processing used from the ScrollableResults where there were collection fetches
382      * encountered; thus a single logical row may have multiple rows in the underlying
383      * result set.
384      *
385      * @param resultSet The result set from which to do the load.
386      * @param session The session from which the request originated.
387      * @param queryParameters The query parameters specified by the user.
388      * @param returnProxies Should proxies be generated
389      * @return The loaded "row".
390      * @throws HibernateException
391      */

392     public Object JavaDoc loadSequentialRowsReverse(
393             final ResultSet JavaDoc resultSet,
394             final SessionImplementor session,
395             final QueryParameters queryParameters,
396             final boolean returnProxies,
397             final boolean isLogicallyAfterLast) throws HibernateException {
398
399         // note that for sequential scrolling, we make the assumption that
400
// the first persister element is the "root entity"
401

402         try {
403             if ( resultSet.isFirst() ) {
404                 // don't even bother trying to read any further
405
return null;
406             }
407
408             EntityKey keyToRead = null;
409             // This check is needed since processing leaves the cursor
410
// after the last physical row for the current logical row;
411
// thus if we are after the last physical row, this might be
412
// caused by either:
413
// 1) scrolling to the last logical row
414
// 2) scrolling past the last logical row
415
// In the latter scenario, the previous logical row
416
// really is the last logical row.
417
//
418
// In all other cases, we should process back two
419
// logical records (the current logic row, plus the
420
// previous logical row).
421
if ( resultSet.isAfterLast() && isLogicallyAfterLast ) {
422                 // position cursor to the last row
423
resultSet.last();
424                 keyToRead = getKeyFromResultSet(
425                         0,
426                         getEntityPersisters()[0],
427                         null,
428                         resultSet,
429                         session
430                 );
431             }
432             else {
433                 // Since the result set cursor is always left at the first
434
// physical row after the "last processed", we need to jump
435
// back one position to get the key value we are interested
436
// in skipping
437
resultSet.previous();
438
439                 // sequentially read the result set in reverse until we recognize
440
// a change in the key value. At that point, we are pointed at
441
// the last physical sequential row for the logical row in which
442
// we are interested in processing
443
boolean firstPass = true;
444                 final EntityKey lastKey = getKeyFromResultSet(
445                         0,
446                         getEntityPersisters()[0],
447                         null,
448                         resultSet,
449                         session
450                 );
451                 while ( resultSet.previous() ) {
452                     EntityKey checkKey = getKeyFromResultSet(
453                             0,
454                             getEntityPersisters()[0],
455                             null,
456                             resultSet,
457                             session
458                     );
459
460                     if ( firstPass ) {
461                         firstPass = false;
462                         keyToRead = checkKey;
463                     }
464
465                     if ( !lastKey.equals( checkKey ) ) {
466                         break;
467                     }
468                 }
469
470             }
471
472             // Read backwards until we read past the first physical sequential
473
// row with the key we are interested in loading
474
while ( resultSet.previous() ) {
475                 EntityKey checkKey = getKeyFromResultSet(
476                         0,
477                         getEntityPersisters()[0],
478                         null,
479                         resultSet,
480                         session
481                 );
482
483                 if ( !keyToRead.equals( checkKey ) ) {
484                     break;
485                 }
486             }
487
488             // Finally, read ahead one row to position result set cursor
489
// at the first physical row we are interested in loading
490
resultSet.next();
491
492             // and perform the load
493
return sequentialLoad( resultSet, session, queryParameters, returnProxies, keyToRead );
494         }
495         catch ( SQLException JavaDoc sqle ) {
496             throw JDBCExceptionHelper.convert(
497                     factory.getSQLExceptionConverter(),
498                     sqle,
499                     "could not perform sequential read of results (forward)",
500                     getSQLString()
501                 );
502         }
503     }
504
505     private static EntityKey getOptionalObjectKey(QueryParameters queryParameters, SessionImplementor session) {
506         final Object JavaDoc optionalObject = queryParameters.getOptionalObject();
507         final Serializable JavaDoc optionalId = queryParameters.getOptionalId();
508         final String JavaDoc optionalEntityName = queryParameters.getOptionalEntityName();
509
510         if ( optionalObject != null && optionalEntityName != null ) {
511             return new EntityKey( optionalId,
512                     session.getEntityPersister( optionalEntityName, optionalObject ),
513                     session.getEntityMode()
514             );
515         }
516         else {
517             return null;
518         }
519
520     }
521
522     private Object JavaDoc getRowFromResultSet(
523             final ResultSet JavaDoc resultSet,
524             final SessionImplementor session,
525             final QueryParameters queryParameters,
526             final LockMode[] lockModeArray,
527             final EntityKey optionalObjectKey,
528             final List JavaDoc hydratedObjects,
529             final EntityKey[] keys,
530             boolean returnProxies)
531     throws SQLException JavaDoc, HibernateException {
532
533         final Loadable[] persisters = getEntityPersisters();
534         final int entitySpan = persisters.length;
535
536         for ( int i = 0; i < entitySpan; i++ ) {
537             keys[i] = getKeyFromResultSet(
538                     i,
539                     persisters[i],
540                     i == entitySpan - 1 ?
541                     queryParameters.getOptionalId() :
542                     null,
543                     resultSet,
544                     session
545             );
546             //TODO: the i==entitySpan-1 bit depends upon subclass implementation (very bad)
547
}
548
549         registerNonExists( keys, persisters, session );
550
551         // this call is side-effecty
552
Object JavaDoc[] row = getRow(
553                 resultSet,
554                 persisters,
555                 keys,
556                 queryParameters.getOptionalObject(),
557                 optionalObjectKey,
558                 lockModeArray,
559                 hydratedObjects,
560                 session
561         );
562
563         readCollectionElements( row, resultSet, session );
564
565         if ( returnProxies ) {
566             // now get an existing proxy for each row element (if there is one)
567
for ( int i = 0; i < entitySpan; i++ ) {
568                 Object JavaDoc entity = row[i];
569                 Object JavaDoc proxy = session.getPersistenceContext().proxyFor( persisters[i], keys[i], entity );
570                 if ( entity != proxy ) {
571                     // force the proxy to resolve itself
572
( (HibernateProxy) proxy ).getHibernateLazyInitializer().setImplementation(entity);
573                     row[i] = proxy;
574                 }
575             }
576         }
577
578         return getResultColumnOrRow( row, resultSet, session );
579
580     }
581
582     /**
583      * Read any collection elements contained in a single row of the result set
584      */

585     private void readCollectionElements(Object JavaDoc[] row, ResultSet JavaDoc resultSet, SessionImplementor session)
586             throws SQLException JavaDoc, HibernateException {
587
588         //TODO: make this handle multiple collection roles!
589

590         final CollectionPersister[] collectionPersisters = getCollectionPersisters();
591         if ( collectionPersisters != null ) {
592
593             final CollectionAliases[] descriptors = getCollectionAliases();
594             final int[] collectionOwners = getCollectionOwners();
595
596             for ( int i=0; i<collectionPersisters.length; i++ ) {
597
598                 final boolean hasCollectionOwners = collectionOwners !=null &&
599                         collectionOwners[i] > -1;
600                 //true if this is a query and we are loading multiple instances of the same collection role
601
//otherwise this is a CollectionInitializer and we are loading up a single collection or batch
602

603                 final Object JavaDoc owner = hasCollectionOwners ?
604                         row[ collectionOwners[i] ] :
605                         null; //if null, owner will be retrieved from session
606

607                 final CollectionPersister collectionPersister = collectionPersisters[i];
608                 final Serializable JavaDoc key;
609                 if ( owner == null ) {
610                     key = null;
611                 }
612                 else {
613                     key = collectionPersister.getCollectionType()
614                             .getKeyOfOwner( owner, session );
615                     //TODO: old version did not require hashmap lookup:
616
//keys[collectionOwner].getIdentifier()
617
}
618     
619                 readCollectionElement( owner, key, collectionPersister, descriptors[i], resultSet, session );
620                 
621             }
622
623         }
624     }
625
626     private List JavaDoc doQuery(final SessionImplementor session,
627                          final QueryParameters queryParameters,
628                          final boolean returnProxies) throws SQLException JavaDoc, HibernateException {
629
630         final RowSelection selection = queryParameters.getRowSelection();
631         final int maxRows = hasMaxRows( selection ) ?
632                 selection.getMaxRows().intValue() :
633                 Integer.MAX_VALUE;
634
635         final int entitySpan = getEntityPersisters().length;
636
637         final ArrayList JavaDoc hydratedObjects = entitySpan == 0 ? null : new ArrayList JavaDoc( entitySpan * 10 );
638         final PreparedStatement JavaDoc st = prepareQueryStatement( queryParameters, false, session );
639         final ResultSet JavaDoc rs = getResultSet( st, queryParameters.isCallable(), selection, session );
640
641 // would be great to move all this below here into another method that could also be used
642
// from the new scrolling stuff.
643
//
644
// Would need to change the way the max-row stuff is handled (i.e. behind an interface) so
645
// that I could do the control breaking at the means to know when to stop
646
final LockMode[] lockModeArray = getLockModes( queryParameters.getLockModes() );
647         final EntityKey optionalObjectKey = getOptionalObjectKey( queryParameters, session );
648
649         final boolean createSubselects = isSubselectLoadingEnabled();
650         final List JavaDoc subselectResultKeys = createSubselects ? new ArrayList JavaDoc() : null;
651         final List JavaDoc results = new ArrayList JavaDoc();
652
653         try {
654
655             handleEmptyCollections( queryParameters.getCollectionKeys(), rs, session );
656
657             EntityKey[] keys = new EntityKey[entitySpan]; //we can reuse it for each row
658

659             if ( log.isTraceEnabled() ) log.trace( "processing result set" );
660
661             int count;
662             for ( count = 0; count < maxRows && rs.next(); count++ ) {
663                 
664                 if ( log.isTraceEnabled() ) log.debug("result set row: " + count);
665
666                 Object JavaDoc result = getRowFromResultSet( rs,
667                         session,
668                         queryParameters,
669                         lockModeArray,
670                         optionalObjectKey,
671                         hydratedObjects,
672                         keys,
673                         returnProxies );
674                 results.add( result );
675
676                 if ( createSubselects ) {
677                     subselectResultKeys.add(keys);
678                     keys = new EntityKey[entitySpan]; //can't reuse in this case
679
}
680                 
681             }
682
683             if ( log.isTraceEnabled() ) log.trace( "done processing result set (" + count + " rows)" );
684
685         }
686         finally {
687             session.getBatcher().closeQueryStatement( st, rs );
688         }
689
690         initializeEntitiesAndCollections( hydratedObjects, rs, session, queryParameters.isReadOnly() );
691
692         if ( createSubselects ) createSubselects( subselectResultKeys, queryParameters, session );
693
694         return results; //getResultList(results);
695

696     }
697
698     protected boolean isSubselectLoadingEnabled() {
699         return false;
700     }
701     
702     protected boolean hasSubselectLoadableCollections() {
703         final Loadable[] loadables = getEntityPersisters();
704         for (int i=0; i<loadables.length; i++ ) {
705             if ( loadables[i].hasSubselectLoadableCollections() ) return true;
706         }
707         return false;
708     }
709     
710     private static Set JavaDoc[] transpose( List JavaDoc keys ) {
711         Set JavaDoc[] result = new Set JavaDoc[ ( ( EntityKey[] ) keys.get(0) ).length ];
712         for ( int j=0; j<result.length; j++ ) {
713             result[j] = new HashSet JavaDoc( keys.size() );
714             for ( int i=0; i<keys.size(); i++ ) {
715                 result[j].add( ( ( EntityKey[] ) keys.get(i) ) [j] );
716             }
717         }
718         return result;
719     }
720
721     private void createSubselects(List JavaDoc keys, QueryParameters queryParameters, SessionImplementor session) {
722         if ( keys.size() > 1 ) { //if we only returned one entity, query by key is more efficient
723

724             Set JavaDoc[] keySets = transpose(keys);
725             
726             final Loadable[] loadables = getEntityPersisters();
727             final String JavaDoc[] aliases = getAliases();
728             final Iterator JavaDoc iter = keys.iterator();
729             while ( iter.hasNext() ) {
730                 
731                 final EntityKey[] rowKeys = (EntityKey[]) iter.next();
732                 for ( int i=0; i<rowKeys.length; i++ ) {
733                     
734                     if ( rowKeys[i]!=null && loadables[i].hasSubselectLoadableCollections() ) {
735                         
736                         SubselectFetch subselectFetch = new SubselectFetch(
737                                 //getSQLString(),
738
aliases[i],
739                                 loadables[i],
740                                 queryParameters,
741                                 keySets[i]
742                             );
743                         
744                         session.getPersistenceContext()
745                             .getBatchFetchQueue()
746                             .addSubselect( rowKeys[i], subselectFetch );
747                     }
748                     
749                 }
750                 
751             }
752         }
753     }
754
755     private void initializeEntitiesAndCollections(
756             final List JavaDoc hydratedObjects,
757             final Object JavaDoc resultSetId,
758             final SessionImplementor session,
759             final boolean readOnly)
760     throws HibernateException {
761         
762         final CollectionPersister[] collectionPersisters = getCollectionPersisters();
763         if ( collectionPersisters != null ) {
764             for ( int i=0; i<collectionPersisters.length; i++ ) {
765                 if ( collectionPersisters[i].isArray() ) {
766                     //for arrays, we should end the collection load before resolving
767
//the entities, since the actual array instances are not instantiated
768
//during loading
769
//TODO: or we could do this polymorphically, and have two
770
// different operations implemented differently for arrays
771
endCollectionLoad( resultSetId, session, collectionPersisters[i] );
772                 }
773             }
774         }
775
776         //important: reuse the same event instances for performance!
777
final PreLoadEvent pre;
778         final PostLoadEvent post;
779         if ( session.isEventSource() ) {
780             pre = new PreLoadEvent( (EventSource) session );
781             post = new PostLoadEvent( (EventSource) session );
782         }
783         else {
784             pre = null;
785             post = null;
786         }
787         
788         if ( hydratedObjects!=null ) {
789             int hydratedObjectsSize = hydratedObjects.size();
790             if ( log.isTraceEnabled() ) log.trace( "total objects hydrated: " + hydratedObjectsSize );
791             for ( int i = 0; i < hydratedObjectsSize; i++ ) {
792                 TwoPhaseLoad.initializeEntity( hydratedObjects.get(i), readOnly, session, pre, post );
793             }
794         }
795         
796         if ( collectionPersisters != null ) {
797             for ( int i=0; i<collectionPersisters.length; i++ ) {
798                 if ( !collectionPersisters[i].isArray() ) {
799                     //for sets, we should end the collection load after resolving
800
//the entities, since we might call hashCode() on the elements
801
//TODO: or we could do this polymorphically, and have two
802
// different operations implemented differently for arrays
803
endCollectionLoad( resultSetId, session, collectionPersisters[i] );
804                 }
805             }
806         }
807         
808     }
809
810     private void endCollectionLoad(
811             final Object JavaDoc resultSetId,
812             final SessionImplementor session,
813             final CollectionPersister collectionPersister
814     ) {
815         //this is a query and we are loading multiple instances of the same collection role
816
session.getPersistenceContext().getCollectionLoadContext()
817             .endLoadingCollections( collectionPersister, resultSetId, session );
818     }
819
820     protected List JavaDoc getResultList(List JavaDoc results) throws QueryException {
821         return results;
822     }
823
824     /**
825      * Get the actual object that is returned in the user-visible result list.
826      * This empty implementation merely returns its first argument. This is
827      * overridden by some subclasses.
828      */

829     protected Object JavaDoc getResultColumnOrRow(Object JavaDoc[] row, ResultSet JavaDoc rs, SessionImplementor session)
830             throws SQLException JavaDoc, HibernateException {
831         return row;
832     }
833
834     /**
835      * For missing objects associated by one-to-one with another object in the
836      * result set, register the fact that the the object is missing with the
837      * session.
838      */

839     private void registerNonExists(
840             final EntityKey[] keys,
841             final Loadable[] persisters,
842             final SessionImplementor session) {
843         
844         final int[] owners = getOwners();
845         if ( owners != null ) {
846             
847             EntityType[] ownerAssociationTypes = getOwnerAssociationTypes();
848             for ( int i = 0; i < keys.length; i++ ) {
849                 
850                 int owner = owners[i];
851                 if ( owner > -1 ) {
852                     EntityKey ownerKey = keys[owner];
853                     if ( keys[i] == null && ownerKey != null ) {
854                         
855                         final PersistenceContext persistenceContext = session.getPersistenceContext();
856                         
857                         /*final boolean isPrimaryKey;
858                         final boolean isSpecialOneToOne;
859                         if ( ownerAssociationTypes == null || ownerAssociationTypes[i] == null ) {
860                             isPrimaryKey = true;
861                             isSpecialOneToOne = false;
862                         }
863                         else {
864                             isPrimaryKey = ownerAssociationTypes[i].getRHSUniqueKeyPropertyName()==null;
865                             isSpecialOneToOne = ownerAssociationTypes[i].getLHSPropertyName()!=null;
866                         }*/

867                         
868                         //TODO: can we *always* use the "null property" approach for everything?
869
/*if ( isPrimaryKey && !isSpecialOneToOne ) {
870                             persistenceContext.addNonExistantEntityKey(
871                                     new EntityKey( ownerKey.getIdentifier(), persisters[i], session.getEntityMode() )
872                             );
873                         }
874                         else if ( isSpecialOneToOne ) {*/

875                         boolean isOneToOneAssociation = ownerAssociationTypes!=null &&
876                                 ownerAssociationTypes[i]!=null &&
877                                 ownerAssociationTypes[i].isOneToOne();
878                         if ( isOneToOneAssociation ) {
879                             persistenceContext.addNullProperty( ownerKey,
880                                     ownerAssociationTypes[i].getPropertyName() );
881                         }
882                         /*}
883                         else {
884                             persistenceContext.addNonExistantEntityUniqueKey( new EntityUniqueKey(
885                                     persisters[i].getEntityName(),
886                                     ownerAssociationTypes[i].getRHSUniqueKeyPropertyName(),
887                                     ownerKey.getIdentifier(),
888                                     persisters[owner].getIdentifierType(),
889                                     session.getEntityMode()
890                             ) );
891                         }*/

892                     }
893                 }
894             }
895         }
896     }
897
898     /**
899      * Read one collection element from the current row of the JDBC result set
900      */

901     private void readCollectionElement(
902             final Object JavaDoc optionalOwner,
903             final Serializable JavaDoc optionalKey,
904             final CollectionPersister persister,
905             final CollectionAliases descriptor,
906             final ResultSet JavaDoc rs,
907             final SessionImplementor session)
908     throws HibernateException, SQLException JavaDoc {
909
910         final PersistenceContext persistenceContext = session.getPersistenceContext();
911
912         final Serializable JavaDoc collectionRowKey = (Serializable JavaDoc) persister.readKey(
913                 rs,
914                 descriptor.getSuffixedKeyAliases(),
915                 session
916             );
917         
918         if ( collectionRowKey != null ) {
919             // we found a collection element in the result set
920

921             if ( log.isDebugEnabled() ) {
922                 log.debug( "found row of collection: " +
923                         MessageHelper.collectionInfoString( persister, collectionRowKey, getFactory() ) );
924             }
925
926             Object JavaDoc owner = optionalOwner;
927             if ( owner == null ) {
928                 owner = persistenceContext.getCollectionOwner( collectionRowKey, persister );
929                 if ( owner == null ) {
930                     //TODO: This is assertion is disabled because there is a bug that means the
931
// original owner of a transient, uninitialized collection is not known
932
// if the collection is re-referenced by a different object associated
933
// with the current Session
934
//throw new AssertionFailure("bug loading unowned collection");
935
}
936             }
937
938             PersistentCollection rowCollection = persistenceContext.getCollectionLoadContext()
939                     .getLoadingCollection( persister, collectionRowKey, rs, session.getEntityMode() );
940
941             if ( rowCollection != null ) {
942                 rowCollection.readFrom( rs, persister, descriptor, owner );
943             }
944
945         }
946         else if ( optionalKey != null ) {
947             // we did not find a collection element in the result set, so we
948
// ensure that a collection is created with the owner's identifier,
949
// since what we have is an empty collection
950

951             if ( log.isDebugEnabled() ) {
952                 log.debug( "result set contains (possibly empty) collection: " +
953                         MessageHelper.collectionInfoString( persister, optionalKey, getFactory() ) );
954             }
955
956             persistenceContext.getCollectionLoadContext()
957                 .getLoadingCollection( persister, optionalKey, rs, session.getEntityMode() ); //handle empty collection
958

959         }
960
961         // else no collection element, but also no owner
962

963     }
964
965     /**
966      * If this is a collection initializer, we need to tell the session that a collection
967      * is being initilized, to account for the possibility of the collection having
968      * no elements (hence no rows in the result set).
969      */

970     private void handleEmptyCollections(
971             final Serializable JavaDoc[] keys,
972             final Object JavaDoc resultSetId,
973             final SessionImplementor session) throws HibernateException {
974
975         if ( keys != null ) {
976             // this is a collection initializer, so we must create a collection
977
// for each of the passed-in keys, to account for the possibility
978
// that the collection is empty and has no rows in the result set
979

980             CollectionPersister[] collectionPersisters = getCollectionPersisters();
981             for ( int j=0; j<collectionPersisters.length; j++ ) {
982                 for ( int i = 0; i < keys.length; i++ ) {
983                     //handle empty collections
984

985                     if ( log.isDebugEnabled() ) {
986                         log.debug(
987                                 "result set contains (possibly empty) collection: " +
988                                 MessageHelper.collectionInfoString( collectionPersisters[j], keys[i], getFactory() )
989                             );
990                     }
991     
992                     session.getPersistenceContext()
993                         .getCollectionLoadContext()
994                         .getLoadingCollection(
995                                 collectionPersisters[j],
996                                 keys[i],
997                                 resultSetId,
998                                 session.getEntityMode()
999                             );
1000                }
1001            }
1002
1003        }
1004
1005        // else this is not a collection initializer (and empty collections will
1006
// be detected by looking for the owner's identifier in the result set)
1007
}
1008
1009    /**
1010     * Read a row of <tt>Key</tt>s from the <tt>ResultSet</tt> into the given array.
1011     * Warning: this method is side-effecty.
1012     * <p/>
1013     * If an <tt>id</tt> is given, don't bother going to the <tt>ResultSet</tt>.
1014     */

1015    private EntityKey getKeyFromResultSet(
1016            final int i,
1017            final Loadable persister,
1018            final Serializable JavaDoc id,
1019            final ResultSet JavaDoc rs,
1020            final SessionImplementor session) throws HibernateException, SQLException JavaDoc {
1021
1022        Serializable JavaDoc resultId;
1023
1024        // if we know there is exactly 1 row, we can skip.
1025
// it would be great if we could _always_ skip this;
1026
// it is a problem for <key-many-to-one>
1027

1028        if ( isSingleRowLoader() && id != null ) {
1029            resultId = id;
1030        }
1031        else {
1032            
1033            Type idType = persister.getIdentifierType();
1034            resultId = (Serializable JavaDoc) idType.nullSafeGet(
1035                    rs,
1036                    getEntityAliases()[i].getSuffixedKeyAliases(),
1037                    session,
1038                    null //problematic for <key-many-to-one>!
1039
);
1040            
1041            final boolean idIsResultId = id != null &&
1042                    resultId != null &&
1043                    idType.isEqual( id, resultId, session.getEntityMode(), factory );
1044            
1045            if ( idIsResultId ) resultId = id; //use the id passed in
1046
}
1047
1048        return resultId == null ?
1049                null :
1050                new EntityKey( resultId, persister, session.getEntityMode() );
1051    }
1052
1053    /**
1054     * Check the version of the object in the <tt>ResultSet</tt> against
1055     * the object version in the session cache, throwing an exception
1056     * if the version numbers are different
1057     */

1058    private void checkVersion(
1059            final int i,
1060            final Loadable persister,
1061            final Serializable JavaDoc id,
1062            final Object JavaDoc entity,
1063            final ResultSet JavaDoc rs,
1064            final SessionImplementor session)
1065    throws HibernateException, SQLException JavaDoc {
1066
1067        Object JavaDoc version = session.getPersistenceContext().getEntry( entity ).getVersion();
1068
1069        if ( version != null ) { //null version means the object is in the process of being loaded somewhere else in the ResultSet
1070
VersionType versionType = persister.getVersionType();
1071            Object JavaDoc currentVersion = versionType.nullSafeGet(
1072                    rs,
1073                    getEntityAliases()[i].getSuffixedVersionAliases(),
1074                    session,
1075                    null
1076            );
1077            if ( !versionType.isEqual(version, currentVersion) ) {
1078                throw new StaleObjectStateException( persister.getEntityName(), id );
1079            }
1080        }
1081
1082    }
1083
1084    /**
1085     * Resolve any ids for currently loaded objects, duplications within the
1086     * <tt>ResultSet</tt>, etc. Instantiate empty objects to be initialized from the
1087     * <tt>ResultSet</tt>. Return an array of objects (a row of results) and an
1088     * array of booleans (by side-effect) that determine whether the corresponding
1089     * object should be initialized.
1090     */

1091    private Object JavaDoc[] getRow(
1092            final ResultSet JavaDoc rs,
1093            final Loadable[] persisters,
1094            final EntityKey[] keys,
1095            final Object JavaDoc optionalObject,
1096            final EntityKey optionalObjectKey,
1097            final LockMode[] lockModes,
1098            final List JavaDoc hydratedObjects,
1099            final SessionImplementor session)
1100    throws HibernateException, SQLException JavaDoc {
1101
1102        final int cols = persisters.length;
1103        final EntityAliases[] descriptors = getEntityAliases();
1104
1105        if ( log.isDebugEnabled() ) {
1106            log.debug(
1107                    "result row: " +
1108                    StringHelper.toString( keys )
1109                );
1110        }
1111
1112        final Object JavaDoc[] rowResults = new Object JavaDoc[cols];
1113
1114        for ( int i = 0; i < cols; i++ ) {
1115
1116            Object JavaDoc object = null;
1117            EntityKey key = keys[i];
1118
1119            if ( keys[i] == null ) {
1120                //do nothing
1121
}
1122            else {
1123
1124                //If the object is already loaded, return the loaded one
1125
object = session.getEntityUsingInterceptor( key );
1126                if ( object != null ) {
1127                    //its already loaded so don't need to hydrate it
1128
instanceAlreadyLoaded(
1129                            rs,
1130                            i,
1131                            persisters[i],
1132                            key,
1133                            object,
1134                            lockModes[i],
1135                            session
1136                        );
1137                }
1138                else {
1139                    object = instanceNotYetLoaded(
1140                            rs,
1141                            i,
1142                            persisters[i],
1143                            descriptors[i].getRowIdAlias(),
1144                            key,
1145                            lockModes[i],
1146                            optionalObjectKey,
1147                            optionalObject,
1148                            hydratedObjects,
1149                            session
1150                        );
1151                }
1152
1153            }
1154
1155            rowResults[i] = object;
1156
1157        }
1158
1159        return rowResults;
1160
1161    }
1162
1163    /**
1164     * The entity instance is already in the session cache
1165     */

1166    private void instanceAlreadyLoaded(
1167            final ResultSet JavaDoc rs,
1168            final int i,
1169            final Loadable persister,
1170            final EntityKey key,
1171            final Object JavaDoc object,
1172            final LockMode lockMode,
1173            final SessionImplementor session)
1174    throws HibernateException, SQLException JavaDoc {
1175
1176        if ( !persister.isInstance( object, session.getEntityMode() ) ) {
1177            throw new WrongClassException(
1178                    "loaded object was of wrong class",
1179                    key.getIdentifier(),
1180                    persister.getEntityName()
1181                );
1182        }
1183
1184        if ( LockMode.NONE != lockMode && upgradeLocks() ) { //no point doing this if NONE was requested
1185

1186            final boolean isVersionCheckNeeded = persister.isVersioned() &&
1187                    session.getPersistenceContext().getEntry(object)
1188                            .getLockMode().lessThan( lockMode );
1189            // we don't need to worry about existing version being uninitialized
1190
// because this block isn't called by a re-entrant load (re-entrant
1191
// loads _always_ have lock mode NONE)
1192
if (isVersionCheckNeeded) {
1193                //we only check the version when _upgrading_ lock modes
1194
checkVersion( i, persister, key.getIdentifier(), object, rs, session );
1195                //we need to upgrade the lock mode to the mode requested
1196
session.getPersistenceContext().getEntry(object)
1197                        .setLockMode(lockMode);
1198            }
1199        }
1200    }
1201
1202    /**
1203     * The entity instance is not in the session cache
1204     */

1205    private Object JavaDoc instanceNotYetLoaded(
1206            final ResultSet JavaDoc rs,
1207            final int i,
1208            final Loadable persister,
1209            final String JavaDoc rowIdAlias,
1210            final EntityKey key,
1211            final LockMode lockMode,
1212            final EntityKey optionalObjectKey,
1213            final Object JavaDoc optionalObject,
1214            final List JavaDoc hydratedObjects,
1215            final SessionImplementor session)
1216    throws HibernateException, SQLException JavaDoc {
1217
1218        final String JavaDoc instanceClass = getInstanceClass(
1219                rs,
1220                i,
1221                persister,
1222                key.getIdentifier(),
1223                session
1224            );
1225
1226        final Object JavaDoc object;
1227        if ( optionalObjectKey != null && key.equals( optionalObjectKey ) ) {
1228            //its the given optional object
1229
object = optionalObject;
1230        }
1231        else {
1232            // instantiate a new instance
1233
object = session.instantiate( instanceClass, key.getIdentifier() );
1234        }
1235
1236        //need to hydrate it.
1237

1238        // grab its state from the ResultSet and keep it in the Session
1239
// (but don't yet initialize the object itself)
1240
// note that we acquire LockMode.READ even if it was not requested
1241
LockMode acquiredLockMode = lockMode == LockMode.NONE ? LockMode.READ : lockMode;
1242        loadFromResultSet(
1243                rs,
1244                i,
1245                object,
1246                instanceClass,
1247                key,
1248                rowIdAlias,
1249                acquiredLockMode,
1250                persister,
1251                session
1252            );
1253
1254        //materialize associations (and initialize the object) later
1255
hydratedObjects.add( object );
1256
1257        return object;
1258    }
1259    
1260    private boolean isEagerPropertyFetchEnabled(int i) {
1261        boolean[] array = getEntityEagerPropertyFetches();
1262        return array!=null && array[i];
1263    }
1264
1265
1266    /**
1267     * Hydrate the state an object from the SQL <tt>ResultSet</tt>, into
1268     * an array or "hydrated" values (do not resolve associations yet),
1269     * and pass the hydrates state to the session.
1270     */

1271    private void loadFromResultSet(
1272            final ResultSet JavaDoc rs,
1273            final int i,
1274            final Object JavaDoc object,
1275            final String JavaDoc instanceEntityName,
1276            final EntityKey key,
1277            final String JavaDoc rowIdAlias,
1278            final LockMode lockMode,
1279            final Loadable rootPersister,
1280            final SessionImplementor session)
1281    throws SQLException JavaDoc, HibernateException {
1282
1283        final Serializable JavaDoc id = key.getIdentifier();
1284
1285        // Get the persister for the _subclass_
1286
final Loadable persister = (Loadable) getFactory().getEntityPersister( instanceEntityName );
1287
1288        if ( log.isTraceEnabled() ) {
1289            log.trace(
1290                    "Initializing object from ResultSet: " +
1291                    MessageHelper.infoString( persister, id, getFactory() )
1292                );
1293        }
1294        
1295        boolean eagerPropertyFetch = isEagerPropertyFetchEnabled(i);
1296
1297        // add temp entry so that the next step is circular-reference
1298
// safe - only needed because some types don't take proper
1299
// advantage of two-phase-load (esp. components)
1300
TwoPhaseLoad.addUninitializedEntity(
1301                key,
1302                object,
1303                persister,
1304                lockMode,
1305                !eagerPropertyFetch,
1306                session
1307            );
1308
1309        //This is not very nice (and quite slow):
1310
final String JavaDoc[][] cols = persister == rootPersister ?
1311                getEntityAliases()[i].getSuffixedPropertyAliases() :
1312                getEntityAliases()[i].getSuffixedPropertyAliases(persister);
1313
1314        final Object JavaDoc[] values = persister.hydrate(
1315                rs,
1316                id,
1317                object,
1318                rootPersister,
1319                cols,
1320                eagerPropertyFetch,
1321                session
1322            );
1323
1324        final Object JavaDoc rowId = persister.hasRowId() ? rs.getObject(rowIdAlias) : null;
1325
1326        final AssociationType[] ownerAssociationTypes = getOwnerAssociationTypes();
1327        if ( ownerAssociationTypes != null && ownerAssociationTypes[i] != null ) {
1328            String JavaDoc ukName = ownerAssociationTypes[i].getRHSUniqueKeyPropertyName();
1329            if (ukName!=null) {
1330                final int index = ( (UniqueKeyLoadable) persister ).getPropertyIndex(ukName);
1331                final Type type = persister.getPropertyTypes()[index];
1332    
1333                // polymorphism not really handled completely correctly,
1334
// perhaps...well, actually its ok, assuming that the
1335
// entity name used in the lookup is the same as the
1336
// the one used here, which it will be
1337

1338                EntityUniqueKey euk = new EntityUniqueKey(
1339                        rootPersister.getEntityName(), //polymorphism comment above
1340
ukName,
1341                        type.semiResolve( values[index], session, object ),
1342                        type,
1343                        session.getEntityMode(), session.getFactory()
1344                    );
1345                session.getPersistenceContext().addEntity( euk, object );
1346            }
1347        }
1348
1349        TwoPhaseLoad.postHydrate(
1350                persister,
1351                id,
1352                values,
1353                rowId,
1354                object,
1355                lockMode,
1356                !eagerPropertyFetch,
1357                session
1358            );
1359
1360    }
1361
1362    /**
1363     * Determine the concrete class of an instance in the <tt>ResultSet</tt>
1364     */

1365    private String JavaDoc getInstanceClass(
1366            final ResultSet JavaDoc rs,
1367            final int i,
1368            final Loadable persister,
1369            final Serializable JavaDoc id,
1370            final SessionImplementor session)
1371    throws HibernateException, SQLException JavaDoc {
1372
1373        if ( persister.hasSubclasses() ) {
1374
1375            // Code to handle subclasses of topClass
1376
Object JavaDoc discriminatorValue = persister.getDiscriminatorType().nullSafeGet(
1377                    rs,
1378                    getEntityAliases()[i].getSuffixedDiscriminatorAlias(),
1379                    session,
1380                    null
1381                );
1382
1383            final String JavaDoc result = persister.getSubclassForDiscriminatorValue( discriminatorValue );
1384
1385            if ( result == null ) {
1386                //woops we got an instance of another class hierarchy branch
1387
throw new WrongClassException(
1388                        "Discriminator: " + discriminatorValue,
1389                        id,
1390                        persister.getEntityName()
1391                    );
1392            }
1393
1394            return result;
1395
1396        }
1397        else {
1398            return persister.getEntityName();
1399        }
1400    }
1401
1402    /**
1403     * Advance the cursor to the first required row of the <tt>ResultSet</tt>
1404     */

1405    private void advance(final ResultSet JavaDoc rs, final RowSelection selection)
1406            throws SQLException JavaDoc {
1407
1408        final int firstRow = getFirstRow( selection );
1409        if ( firstRow != 0 ) {
1410            if ( getFactory().getSettings().isScrollableResultSetsEnabled() ) {
1411                // we can go straight to the first required row
1412
rs.absolute( firstRow );
1413            }
1414            else {
1415                // we need to step through the rows one row at a time (slow)
1416
for ( int m = 0; m < firstRow; m++ ) rs.next();
1417            }
1418        }
1419    }
1420
1421    private static boolean hasMaxRows(RowSelection selection) {
1422        return selection != null && selection.getMaxRows() != null;
1423    }
1424
1425    private static int getFirstRow(RowSelection selection) {
1426        if ( selection == null || selection.getFirstRow() == null ) {
1427            return 0;
1428        }
1429        else {
1430            return selection.getFirstRow().intValue();
1431        }
1432    }
1433
1434    /**
1435     * Should we pre-process the SQL string, adding a dialect-specific
1436     * LIMIT clause.
1437     */

1438    private static boolean useLimit(final RowSelection selection, final Dialect dialect) {
1439        return dialect.supportsLimit() && hasMaxRows( selection );
1440    }
1441
1442    /**
1443     * Bind positional parameter values to the <tt>PreparedStatement</tt>
1444     * (these are parameters specified by a JDBC-style ?).
1445     */

1446    protected int bindPositionalParameters(
1447            final PreparedStatement JavaDoc st,
1448            final QueryParameters queryParameters,
1449            final int start,
1450            final SessionImplementor session) throws SQLException JavaDoc, HibernateException {
1451
1452        final Object JavaDoc[] values = queryParameters.getFilteredPositionalParameterValues();
1453        final Type[] types = queryParameters.getFilteredPositionalParameterTypes();
1454        int span = 0;
1455        for ( int i = 0; i < values.length; i++ ) {
1456            types[i].nullSafeSet( st, values[i], start + span, session );
1457            span += types[i].getColumnSpan( getFactory() );
1458        }
1459        return span;
1460    }
1461
1462    /**
1463     * Obtain a <tt>PreparedStatement</tt> with all parameters pre-bound.
1464     * Bind JDBC-style <tt>?</tt> parameters, named parameters, and
1465     * limit parameters.
1466     */

1467    protected final PreparedStatement JavaDoc prepareQueryStatement(
1468            final QueryParameters queryParameters,
1469            final boolean scroll,
1470            final SessionImplementor session) throws SQLException JavaDoc, HibernateException {
1471
1472        queryParameters.processFilters( getSQLString(), session );
1473        String JavaDoc sql = queryParameters.getFilteredSQL();
1474        final Dialect dialect = getFactory().getDialect();
1475        final RowSelection selection = queryParameters.getRowSelection();
1476        boolean useLimit = useLimit( selection, dialect );
1477        boolean hasFirstRow = getFirstRow( selection ) > 0;
1478        boolean useOffset = hasFirstRow && useLimit && dialect.supportsLimitOffset();
1479        boolean callable = queryParameters.isCallable();
1480        
1481        boolean useScrollableResultSetToSkip = hasFirstRow &&
1482                !useOffset &&
1483                getFactory().getSettings().isScrollableResultSetsEnabled();
1484        ScrollMode scrollMode = scroll ? queryParameters.getScrollMode() : ScrollMode.SCROLL_INSENSITIVE;
1485
1486        if ( useLimit ) {
1487            sql = dialect.getLimitString(
1488                    sql.trim(), //use of trim() here is ugly?
1489
useOffset ? getFirstRow(selection) : 0,
1490                    getMaxOrLimit(selection, dialect)
1491                );
1492        }
1493
1494        sql = preprocessSQL( sql, queryParameters, dialect );
1495        
1496        PreparedStatement JavaDoc st = null;
1497        
1498        if (callable) {
1499            st = session.getBatcher()
1500                .prepareCallableQueryStatement( sql, scroll || useScrollableResultSetToSkip, scrollMode );
1501        }
1502        else {
1503            st = session.getBatcher()
1504                .prepareQueryStatement( sql, scroll || useScrollableResultSetToSkip, scrollMode );
1505        }
1506                
1507
1508        try {
1509
1510            int col = 1;
1511            //TODO: can we limit stored procedures ?!
1512
if ( useLimit && dialect.bindLimitParametersFirst() ) {
1513                col += bindLimitParameters( st, col, selection );
1514            }
1515            if (callable) {
1516                col = dialect.registerResultSetOutParameter( (CallableStatement JavaDoc)st, col );
1517            }
1518            col += bindPositionalParameters( st, queryParameters, col, session );
1519            col += bindNamedParameters( st, queryParameters.getNamedParameters(), col, session );
1520
1521            if ( useLimit && !dialect.bindLimitParametersFirst() ) {
1522                col += bindLimitParameters( st, col, selection );
1523            }
1524
1525            if ( !useLimit ) setMaxRows( st, selection );
1526            if ( selection != null ) {
1527                if ( selection.getTimeout() != null ) {
1528                    st.setQueryTimeout( selection.getTimeout().intValue() );
1529                }
1530                if ( selection.getFetchSize() != null ) {
1531                    st.setFetchSize( selection.getFetchSize().intValue() );
1532                }
1533            }
1534        }
1535        catch ( SQLException JavaDoc sqle ) {
1536            session.getBatcher().closeQueryStatement( st, null );
1537            throw sqle;
1538        }
1539        catch ( HibernateException he ) {
1540            session.getBatcher().closeQueryStatement( st, null );
1541            throw he;
1542        }
1543
1544        return st;
1545    }
1546
1547    /**
1548     * Some dialect-specific LIMIT clauses require the maximium last row number,
1549     * others require the maximum returned row count.
1550     */

1551    private static int getMaxOrLimit(final RowSelection selection, final Dialect dialect) {
1552        final int firstRow = getFirstRow( selection );
1553        final int lastRow = selection.getMaxRows().intValue();
1554        if ( dialect.useMaxForLimit() ) {
1555            return lastRow + firstRow;
1556        }
1557        else {
1558            return lastRow;
1559        }
1560    }
1561
1562    /**
1563     * Bind parameters needed by the dialect-specific LIMIT clause
1564     */

1565    private int bindLimitParameters(final PreparedStatement JavaDoc st, final int index, final RowSelection selection)
1566            throws SQLException JavaDoc {
1567
1568        Dialect dialect = getFactory().getDialect();
1569        if ( !dialect.supportsVariableLimit() ) return 0;
1570        if ( !hasMaxRows( selection ) ) {
1571            throw new AssertionFailure( "no max results set" );
1572        }
1573        int firstRow = getFirstRow( selection );
1574        int lastRow = getMaxOrLimit( selection, dialect );
1575        boolean hasFirstRow = firstRow > 0 && dialect.supportsLimitOffset();
1576        boolean reverse = dialect.bindLimitParametersInReverseOrder();
1577        if ( hasFirstRow ) st.setInt( index + ( reverse ? 1 : 0 ), firstRow );
1578        st.setInt( index + ( reverse || !hasFirstRow ? 0 : 1 ), lastRow );
1579        return hasFirstRow ? 2 : 1;
1580    }
1581
1582    /**
1583     * Use JDBC API to limit the number of rows returned by the SQL query if necessary
1584     */

1585    private void setMaxRows(final PreparedStatement JavaDoc st, final RowSelection selection)
1586            throws SQLException JavaDoc {
1587        if ( hasMaxRows( selection ) ) {
1588            st.setMaxRows( selection.getMaxRows().intValue() + getFirstRow( selection ) );
1589        }
1590    }
1591
1592    protected final ResultSet JavaDoc getResultSet(
1593            final PreparedStatement JavaDoc st,
1594            final RowSelection selection,
1595            final SessionImplementor session)
1596    throws HibernateException, SQLException JavaDoc {
1597        return getResultSet(st, false, selection, session);
1598    }
1599
1600    /**
1601     * Fetch a <tt>PreparedStatement</tt>, call <tt>setMaxRows</tt> and then execute it,
1602     * advance to the first result and return an SQL <tt>ResultSet</tt>
1603     */

1604    protected final ResultSet JavaDoc getResultSet(
1605            final PreparedStatement JavaDoc st,
1606            final boolean callable,
1607            final RowSelection selection,
1608            final SessionImplementor session)
1609    throws SQLException JavaDoc, HibernateException {
1610    
1611        ResultSet JavaDoc rs = null;
1612        try {
1613            Dialect dialect = getFactory().getDialect();
1614            if (callable) {
1615                rs = session.getBatcher().getResultSet( (CallableStatement JavaDoc) st, dialect );
1616            }
1617            else {
1618                rs = session.getBatcher().getResultSet( st );
1619            }
1620            rs = wrapResultSetIfEnabled( rs , session );
1621            if ( !dialect.supportsLimitOffset() || !useLimit( selection, dialect ) ) {
1622                advance( rs, selection );
1623            }
1624            return rs;
1625        }
1626        catch ( SQLException JavaDoc sqle ) {
1627            session.getBatcher().closeQueryStatement( st, rs );
1628            throw sqle;
1629        }
1630    }
1631
1632    private synchronized ResultSet JavaDoc wrapResultSetIfEnabled(final ResultSet JavaDoc rs, final SessionImplementor session) {
1633        // synchronized to avoid multi-thread access issues; defined as method synch to avoid
1634
// potential deadlock issues due to nature of code.
1635
if ( session.getFactory().getSettings().isWrapResultSetsEnabled() ) {
1636            try {
1637                log.debug("Wrapping result set [" + rs + "]");
1638                return new ResultSetWrapper( rs, retreiveColumnNameToIndexCache( rs ) );
1639            }
1640            catch(SQLException JavaDoc e) {
1641                log.info("Error wrapping result set", e);
1642                return rs;
1643            }
1644        }
1645        else {
1646            return rs;
1647        }
1648    }
1649
1650    private ColumnNameCache retreiveColumnNameToIndexCache(ResultSet JavaDoc rs) throws SQLException JavaDoc {
1651        if ( columnNameCache == null ) {
1652            log.trace("Building columnName->columnIndex cache");
1653            columnNameCache = new ColumnNameCache( rs.getMetaData().getColumnCount() );
1654        }
1655
1656        return columnNameCache;
1657    }
1658
1659    /**
1660     * Bind named parameters to the <tt>PreparedStatement</tt>. This has an empty
1661     * implementation on this superclass and should be implemented by subclasses
1662     * (queries) which allow named parameters.
1663     */

1664    protected int bindNamedParameters(
1665            PreparedStatement JavaDoc st,
1666            Map JavaDoc namedParams,
1667            int start,
1668            SessionImplementor session)
1669    throws SQLException JavaDoc, HibernateException {
1670        return 0;
1671    }
1672
1673    /**
1674     * Called by subclasses that load entities
1675     * @param persister only needed for logging
1676     */

1677    protected final List JavaDoc loadEntity(
1678            final SessionImplementor session,
1679            final Object JavaDoc id,
1680            final Type identifierType,
1681            final Object JavaDoc optionalObject,
1682            final String JavaDoc optionalEntityName,
1683            final Serializable JavaDoc optionalIdentifier,
1684            final EntityPersister persister) throws HibernateException {
1685        
1686        if ( log.isDebugEnabled() ) {
1687            log.debug(
1688                    "loading entity: " +
1689                    MessageHelper.infoString( persister, id, identifierType, getFactory() )
1690                );
1691        }
1692
1693        List JavaDoc result;
1694        try {
1695            result = doQueryAndInitializeNonLazyCollections(
1696                    session,
1697                    new QueryParameters(
1698                            new Type[]{identifierType},
1699                            new Object JavaDoc[]{id},
1700                            optionalObject,
1701                            optionalEntityName,
1702                            optionalIdentifier
1703                        ),
1704                    false
1705                );
1706        }
1707        catch ( SQLException JavaDoc sqle ) {
1708            final Loadable[] persisters = getEntityPersisters();
1709            throw JDBCExceptionHelper.convert(
1710                    factory.getSQLExceptionConverter(),
1711                    sqle,
1712                    "could not load an entity: " +
1713                    MessageHelper.infoString( persisters[persisters.length-1], id, identifierType, getFactory() ),
1714                    getSQLString()
1715                );
1716        }
1717
1718        log.debug("done entity load");
1719        
1720        return result;
1721        
1722    }
1723
1724    /**
1725     * Called by wrappers that batch load entities
1726     * @param persister only needed for logging
1727     */

1728    public final List JavaDoc loadEntityBatch(
1729            final SessionImplementor session,
1730            final Serializable JavaDoc[] ids,
1731            final Type idType,
1732            final Object JavaDoc optionalObject,
1733            final String JavaDoc optionalEntityName,
1734            final Serializable JavaDoc optionalId,
1735            final EntityPersister persister) throws HibernateException {
1736
1737        if ( log.isDebugEnabled() ) {
1738            log.debug(
1739                    "batch loading entity: " +
1740                    MessageHelper.infoString(persister, ids, getFactory() )
1741                );
1742        }
1743
1744        Type[] types = new Type[ids.length];
1745        Arrays.fill( types, idType );
1746        List JavaDoc result;
1747        try {
1748            result = doQueryAndInitializeNonLazyCollections(
1749                    session,
1750                    new QueryParameters( types, ids, optionalObject, optionalEntityName, optionalId ),
1751                    false
1752                );
1753        }
1754        catch ( SQLException JavaDoc sqle ) {
1755            throw JDBCExceptionHelper.convert(
1756                    factory.getSQLExceptionConverter(),
1757                    sqle,
1758                    "could not load an entity batch: " +
1759                    MessageHelper.infoString( getEntityPersisters()[0], ids, getFactory() ),
1760                    getSQLString()
1761                );
1762        }
1763
1764        log.debug("done entity batch load");
1765        
1766        return result;
1767
1768    }
1769
1770    /**
1771     * Called by subclasses that initialize collections
1772     */

1773    public final void loadCollection(
1774            final SessionImplementor session,
1775            final Serializable JavaDoc id,
1776            final Type type) throws HibernateException {
1777
1778        if ( log.isDebugEnabled() ) {
1779            log.debug(
1780                    "loading collection: "+
1781                    MessageHelper.collectionInfoString( getCollectionPersisters()[0], id, getFactory() )
1782                );
1783        }
1784
1785        Serializable JavaDoc[] ids = new Serializable JavaDoc[]{id};
1786        try {
1787            doQueryAndInitializeNonLazyCollections(
1788                    session,
1789                    new QueryParameters( new Type[]{type}, ids, ids ),
1790                    true
1791                );
1792        }
1793        catch ( SQLException JavaDoc sqle ) {
1794            throw JDBCExceptionHelper.convert(
1795                    factory.getSQLExceptionConverter(),
1796                    sqle,
1797                    "could not initialize a collection: " +
1798                    MessageHelper.collectionInfoString( getCollectionPersisters()[0], id, getFactory() ),
1799                    getSQLString()
1800                );
1801        }
1802    
1803        log.debug("done loading collection");
1804
1805    }
1806
1807    /**
1808     * Called by wrappers that batch initialize collections
1809     */

1810    public final void loadCollectionBatch(
1811            final SessionImplementor session,
1812            final Serializable JavaDoc[] ids,
1813            final Type type) throws HibernateException {
1814
1815        if ( log.isDebugEnabled() ) {
1816            log.debug(
1817                    "batch loading collection: "+
1818                    MessageHelper.collectionInfoString( getCollectionPersisters()[0], ids, getFactory() )
1819                );
1820        }
1821
1822        Type[] idTypes = new Type[ids.length];
1823        Arrays.fill( idTypes, type );
1824        try {
1825            doQueryAndInitializeNonLazyCollections(
1826                    session,
1827                    new QueryParameters( idTypes, ids, ids ),
1828                    true
1829                );
1830        }
1831        catch ( SQLException JavaDoc sqle ) {
1832            throw JDBCExceptionHelper.convert(
1833                    factory.getSQLExceptionConverter(),
1834                    sqle,
1835                    "could not initialize a collection batch: " +
1836                    MessageHelper.collectionInfoString( getCollectionPersisters()[0], ids, getFactory() ),
1837                    getSQLString()
1838                );
1839        }
1840        
1841        log.debug("done batch load");
1842
1843    }
1844
1845    /**
1846     * Called by subclasses that batch initialize collections
1847     */

1848    protected final void loadCollectionSubselect(
1849            final SessionImplementor session,
1850            final Serializable JavaDoc[] ids,
1851            final Object JavaDoc[] parameterValues,
1852            final Type[] parameterTypes,
1853            final Map JavaDoc namedParameters,
1854            final Type type) throws HibernateException {
1855
1856        Type[] idTypes = new Type[ids.length];
1857        Arrays.fill( idTypes, type );
1858        try {
1859            doQueryAndInitializeNonLazyCollections( session,
1860                    new QueryParameters( parameterTypes, parameterValues, namedParameters, ids ),
1861                    true
1862                );
1863        }
1864        catch ( SQLException JavaDoc sqle ) {
1865            throw JDBCExceptionHelper.convert(
1866                    factory.getSQLExceptionConverter(),
1867                    sqle,
1868                    "could not load collection by subselect: " +
1869                    MessageHelper.collectionInfoString( getCollectionPersisters()[0], ids, getFactory() ),
1870                    getSQLString()
1871                );
1872        }
1873    }
1874
1875    /**
1876     * Return the query results, using the query cache, called
1877     * by subclasses that implement cacheable queries
1878     */

1879    protected List JavaDoc list(
1880            final SessionImplementor session,
1881            final QueryParameters queryParameters,
1882            final Set JavaDoc querySpaces,
1883            final Type[] resultTypes) throws HibernateException {
1884
1885        final boolean cacheable = factory.getSettings().isQueryCacheEnabled() &&
1886            queryParameters.isCacheable();
1887
1888        if ( cacheable ) {
1889
1890            final boolean queryStatisticsEnabled = factory.getStatistics().isStatisticsEnabled();
1891
1892            QueryCache queryCache = factory.getQueryCache( queryParameters.getCacheRegion() );
1893            Set JavaDoc filterKeys = FilterKey.createFilterKeys(
1894                    session.getEnabledFilters(),
1895                    session.getEntityMode()
1896                );
1897            QueryKey key = new QueryKey(
1898                    getSQLString(),
1899                    queryParameters,
1900                    filterKeys,
1901                    session.getEntityMode()
1902                );
1903            List JavaDoc result = null;
1904
1905            if ( /*!queryParameters.isForceCacheRefresh() &&*/ session.getCacheMode().isGetEnabled() ) {
1906                result = queryCache.get(
1907                        key,
1908                        resultTypes,
1909                        queryParameters.isNaturalKeyLookup(),
1910                        querySpaces,
1911                        session
1912                    );
1913
1914                if (queryStatisticsEnabled) {
1915                    if (result==null) {
1916                        factory.getStatisticsImplementor()
1917                                .queryCacheMiss( getQueryIdentifier(), queryCache.getRegionName() );
1918                    }
1919                    else {
1920                        factory.getStatisticsImplementor()
1921                                .queryCacheHit( getQueryIdentifier(), queryCache.getRegionName() );
1922                    }
1923                }
1924            }
1925
1926
1927            if ( result == null ) {
1928                result = doList( session, queryParameters );
1929
1930                if ( cacheable && session.getCacheMode().isPutEnabled() ) {
1931                    queryCache.put( key, resultTypes, result, session );
1932
1933                    if ( queryStatisticsEnabled ) {
1934                        factory.getStatisticsImplementor()
1935                                .queryCachePut( getQueryIdentifier(), queryCache.getRegionName() );
1936                    }
1937                }
1938            }
1939
1940            return getResultList( result );
1941        }
1942        else {
1943            return getResultList( doList( session, queryParameters ) );
1944        }
1945    }
1946
1947    /**
1948     * Actually execute a query, ignoring the query cache
1949     */

1950    protected List JavaDoc doList(final SessionImplementor session, final QueryParameters queryParameters)
1951            throws HibernateException {
1952
1953        final boolean stats = getFactory().getStatistics().isStatisticsEnabled();
1954        long startTime = 0;
1955        if ( stats ) startTime = System.currentTimeMillis();
1956
1957        List JavaDoc result;
1958        try {
1959            result = doQueryAndInitializeNonLazyCollections( session, queryParameters, true );
1960        }
1961        catch ( SQLException JavaDoc sqle ) {
1962            throw JDBCExceptionHelper.convert(
1963                    factory.getSQLExceptionConverter(),
1964                    sqle,
1965                    "could not execute query",
1966                    getSQLString()
1967                );
1968        }
1969
1970        if ( stats ) {
1971            getFactory().getStatisticsImplementor().queryExecuted(
1972                    getQueryIdentifier(),
1973                    result.size(),
1974                    System.currentTimeMillis() - startTime );
1975        }
1976
1977        return result;
1978    }
1979
1980    /**
1981     * Check whether the current loader can support returning ScrollableResults.
1982     *
1983     * @throws HibernateException
1984     */

1985    protected void checkScrollability() throws HibernateException {
1986        // Allows various loaders (ok mainly the QueryLoader :) to check
1987
// whether scrolling of their result set should be allowed.
1988
//
1989
// By default it is allowed.
1990
return;
1991    }
1992
1993    /**
1994     * Does the result set to be scrolled contain collection fetches?
1995     *
1996     * @return True if it does, and thus needs the special fetching scroll
1997     * functionality; false otherwise.
1998     */

1999    protected boolean needsFecthingScroll() {
2000        return false;
2001    }
2002
2003    /**
2004     * Return the query results, as an instance of <tt>ScrollableResults</tt>
2005     *
2006     * @param queryParameters The parameters with which the query should be executed.
2007     * @param returnTypes The expected return types of the query
2008     * @param holderInstantiator If the return values are expected to be wrapped
2009     * in a holder, this is the thing that knows how to wrap them.
2010     * @param session The session from which the scroll request originated.
2011     * @return The ScrollableResults instance.
2012     * @throws HibernateException Indicates an error executing the query, or constructing
2013     * the ScrollableResults.
2014     */

2015    protected ScrollableResults scroll(
2016            final QueryParameters queryParameters,
2017            final Type[] returnTypes,
2018            final HolderInstantiator holderInstantiator,
2019            final SessionImplementor session) throws HibernateException {
2020
2021        checkScrollability();
2022
2023        final boolean stats = getQueryIdentifier() != null &&
2024                getFactory().getStatistics().isStatisticsEnabled();
2025        long startTime = 0;
2026        if ( stats ) startTime = System.currentTimeMillis();
2027
2028        try {
2029
2030            PreparedStatement JavaDoc st = prepareQueryStatement( queryParameters, true, session );
2031            ResultSet JavaDoc rs = getResultSet( st, queryParameters.getRowSelection(), session );
2032
2033            if ( stats ) {
2034                getFactory().getStatisticsImplementor().queryExecuted(
2035                        getQueryIdentifier(),
2036                        0,
2037                        System.currentTimeMillis() - startTime
2038                    );
2039            }
2040
2041            if ( needsFecthingScroll() ) {
2042                return new FetchingScrollableResultsImpl(
2043                        rs,
2044                        st,
2045                        session,
2046                        this,
2047                        queryParameters,
2048                        returnTypes,
2049                        holderInstantiator
2050                    );
2051            }
2052            else {
2053                return new ScrollableResultsImpl(
2054                        rs,
2055                        st,
2056                        session,
2057                        this,
2058                        queryParameters,
2059                        returnTypes,
2060                        holderInstantiator
2061                    );
2062            }
2063
2064        }
2065        catch ( SQLException JavaDoc sqle ) {
2066            throw JDBCExceptionHelper.convert(
2067                    factory.getSQLExceptionConverter(),
2068                    sqle,
2069                    "could not execute query using scroll",
2070                    getSQLString()
2071                );
2072        }
2073
2074    }
2075
2076    /**
2077     * Calculate and cache select-clause suffixes. Must be
2078     * called by subclasses after instantiation.
2079     */

2080    protected void postInstantiate() {}
2081
2082    /**
2083     * Get the result set descriptor
2084     */

2085    protected abstract EntityAliases[] getEntityAliases();
2086
2087    protected abstract CollectionAliases[] getCollectionAliases();
2088
2089    /**
2090     * Identifies the query for statistics reporting, if null,
2091     * no statistics will be reported
2092     */

2093    protected String JavaDoc getQueryIdentifier() {
2094        return null;
2095    }
2096
2097    public final SessionFactoryImplementor getFactory() {
2098        return factory;
2099    }
2100
2101    public String JavaDoc toString() {
2102        return getClass().getName() + '(' + getSQLString() + ')';
2103    }
2104
2105}
2106
Popular Tags