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