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