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