0001: //$Id: Loader.java 11301 2007-03-19 20:43:46Z steve.ebersole@jboss.com $
0002: package org.hibernate.loader;
0003:
0004: import java.io.Serializable;
0005: import java.sql.CallableStatement;
0006: import java.sql.PreparedStatement;
0007: import java.sql.ResultSet;
0008: import java.sql.SQLException;
0009: import java.util.ArrayList;
0010: import java.util.Arrays;
0011: import java.util.HashMap;
0012: import java.util.HashSet;
0013: import java.util.Iterator;
0014: import java.util.List;
0015: import java.util.Map;
0016: import java.util.Set;
0017:
0018: import org.apache.commons.logging.Log;
0019: import org.apache.commons.logging.LogFactory;
0020: import org.hibernate.AssertionFailure;
0021: import org.hibernate.HibernateException;
0022: import org.hibernate.LockMode;
0023: import org.hibernate.QueryException;
0024: import org.hibernate.ScrollMode;
0025: import org.hibernate.ScrollableResults;
0026: import org.hibernate.StaleObjectStateException;
0027: import org.hibernate.WrongClassException;
0028: import org.hibernate.cache.FilterKey;
0029: import org.hibernate.cache.QueryCache;
0030: import org.hibernate.cache.QueryKey;
0031: import org.hibernate.collection.PersistentCollection;
0032: import org.hibernate.dialect.Dialect;
0033: import org.hibernate.engine.EntityKey;
0034: import org.hibernate.engine.EntityUniqueKey;
0035: import org.hibernate.engine.PersistenceContext;
0036: import org.hibernate.engine.QueryParameters;
0037: import org.hibernate.engine.RowSelection;
0038: import org.hibernate.engine.SessionFactoryImplementor;
0039: import org.hibernate.engine.SessionImplementor;
0040: import org.hibernate.engine.SubselectFetch;
0041: import org.hibernate.engine.TwoPhaseLoad;
0042: import org.hibernate.engine.TypedValue;
0043: import org.hibernate.event.EventSource;
0044: import org.hibernate.event.PostLoadEvent;
0045: import org.hibernate.event.PreLoadEvent;
0046: import org.hibernate.exception.JDBCExceptionHelper;
0047: import org.hibernate.hql.HolderInstantiator;
0048: import org.hibernate.impl.FetchingScrollableResultsImpl;
0049: import org.hibernate.impl.ScrollableResultsImpl;
0050: import org.hibernate.jdbc.ColumnNameCache;
0051: import org.hibernate.jdbc.ResultSetWrapper;
0052: import org.hibernate.persister.collection.CollectionPersister;
0053: import org.hibernate.persister.entity.EntityPersister;
0054: import org.hibernate.persister.entity.Loadable;
0055: import org.hibernate.persister.entity.UniqueKeyLoadable;
0056: import org.hibernate.pretty.MessageHelper;
0057: import org.hibernate.proxy.HibernateProxy;
0058: import org.hibernate.transform.ResultTransformer;
0059: import org.hibernate.type.AssociationType;
0060: import org.hibernate.type.EntityType;
0061: import org.hibernate.type.Type;
0062: import org.hibernate.type.VersionType;
0063: import org.hibernate.util.StringHelper;
0064:
0065: /**
0066: * Abstract superclass of object loading (and querying) strategies. This class implements
0067: * useful common functionality that concrete loaders delegate to. It is not intended that this
0068: * functionality would be directly accessed by client code. (Hence, all methods of this class
0069: * are declared <tt>protected</tt> or <tt>private</tt>.) This class relies heavily upon the
0070: * <tt>Loadable</tt> interface, which is the contract between this class and
0071: * <tt>EntityPersister</tt>s that may be loaded by it.<br>
0072: * <br>
0073: * The present implementation is able to load any number of columns of entities and at most
0074: * one collection role per query.
0075: *
0076: * @author Gavin King
0077: * @see org.hibernate.persister.entity.Loadable
0078: */
0079: public abstract class Loader {
0080:
0081: private static final Log log = LogFactory.getLog(Loader.class);
0082:
0083: private final SessionFactoryImplementor factory;
0084: private ColumnNameCache columnNameCache;
0085:
0086: public Loader(SessionFactoryImplementor factory) {
0087: this .factory = factory;
0088: }
0089:
0090: /**
0091: * The SQL query string to be called; implemented by all subclasses
0092: *
0093: * @return The sql command this loader should use to get its {@link ResultSet}.
0094: */
0095: protected abstract String getSQLString();
0096:
0097: /**
0098: * An array of persisters of entity classes contained in each row of results;
0099: * implemented by all subclasses
0100: *
0101: * @return The entity persisters.
0102: */
0103: protected abstract Loadable[] getEntityPersisters();
0104:
0105: /**
0106: * An array indicating whether the entities have eager property fetching
0107: * enabled.
0108: *
0109: * @return Eager property fetching indicators.
0110: */
0111: protected boolean[] getEntityEagerPropertyFetches() {
0112: return null;
0113: }
0114:
0115: /**
0116: * An array of indexes of the entity that owns a one-to-one association
0117: * to the entity at the given index (-1 if there is no "owner"). The
0118: * indexes contained here are relative to the result of
0119: * {@link #getEntityPersisters}.
0120: *
0121: * @return The owner indicators (see discussion above).
0122: */
0123: protected int[] getOwners() {
0124: return null;
0125: }
0126:
0127: /**
0128: * An array of the owner types corresponding to the {@link #getOwners()}
0129: * returns. Indices indicating no owner would be null here.
0130: *
0131: * @return The types for the owners.
0132: */
0133: protected EntityType[] getOwnerAssociationTypes() {
0134: return null;
0135: }
0136:
0137: /**
0138: * An (optional) persister for a collection to be initialized; only
0139: * collection loaders return a non-null value
0140: */
0141: protected CollectionPersister[] getCollectionPersisters() {
0142: return null;
0143: }
0144:
0145: /**
0146: * Get the index of the entity that owns the collection, or -1
0147: * if there is no owner in the query results (ie. in the case of a
0148: * collection initializer) or no collection.
0149: */
0150: protected int[] getCollectionOwners() {
0151: return null;
0152: }
0153:
0154: /**
0155: * What lock mode does this load entities with?
0156: *
0157: * @param lockModes a collection of lock modes specified dynamically via the Query interface
0158: */
0159: protected abstract LockMode[] getLockModes(Map lockModes);
0160:
0161: /**
0162: * Append <tt>FOR UPDATE OF</tt> clause, if necessary. This
0163: * empty superclass implementation merely returns its first
0164: * argument.
0165: */
0166: protected String applyLocks(String sql, Map lockModes,
0167: Dialect dialect) throws HibernateException {
0168: return sql;
0169: }
0170:
0171: /**
0172: * Does this query return objects that might be already cached
0173: * by the session, whose lock mode may need upgrading
0174: */
0175: protected boolean upgradeLocks() {
0176: return false;
0177: }
0178:
0179: /**
0180: * Return false is this loader is a batch entity loader
0181: */
0182: protected boolean isSingleRowLoader() {
0183: return false;
0184: }
0185:
0186: /**
0187: * Get the SQL table aliases of entities whose
0188: * associations are subselect-loadable, returning
0189: * null if this loader does not support subselect
0190: * loading
0191: */
0192: protected String[] getAliases() {
0193: return null;
0194: }
0195:
0196: /**
0197: * Modify the SQL, adding lock hints and comments, if necessary
0198: */
0199: protected String preprocessSQL(String sql,
0200: QueryParameters parameters, Dialect dialect)
0201: throws HibernateException {
0202:
0203: sql = applyLocks(sql, parameters.getLockModes(), dialect);
0204:
0205: return getFactory().getSettings().isCommentsEnabled() ? prependComment(
0206: sql, parameters)
0207: : sql;
0208: }
0209:
0210: private String prependComment(String sql, QueryParameters parameters) {
0211: String comment = parameters.getComment();
0212: if (comment == null) {
0213: return sql;
0214: } else {
0215: return new StringBuffer(comment.length() + sql.length() + 5)
0216: .append("/* ").append(comment).append(" */ ")
0217: .append(sql).toString();
0218: }
0219: }
0220:
0221: /**
0222: * Execute an SQL query and attempt to instantiate instances of the class mapped by the given
0223: * persister from each row of the <tt>ResultSet</tt>. If an object is supplied, will attempt to
0224: * initialize that object. If a collection is supplied, attempt to initialize that collection.
0225: */
0226: private List doQueryAndInitializeNonLazyCollections(
0227: final SessionImplementor session,
0228: final QueryParameters queryParameters,
0229: final boolean returnProxies) throws HibernateException,
0230: SQLException {
0231:
0232: final PersistenceContext persistenceContext = session
0233: .getPersistenceContext();
0234: persistenceContext.beforeLoad();
0235: List result;
0236: try {
0237: result = doQuery(session, queryParameters, returnProxies);
0238: } finally {
0239: persistenceContext.afterLoad();
0240: }
0241: persistenceContext.initializeNonLazyCollections();
0242: return result;
0243: }
0244:
0245: /**
0246: * Loads a single row from the result set. This is the processing used from the
0247: * ScrollableResults where no collection fetches were encountered.
0248: *
0249: * @param resultSet The result set from which to do the load.
0250: * @param session The session from which the request originated.
0251: * @param queryParameters The query parameters specified by the user.
0252: * @param returnProxies Should proxies be generated
0253: * @return The loaded "row".
0254: * @throws HibernateException
0255: */
0256: public Object loadSingleRow(final ResultSet resultSet,
0257: final SessionImplementor session,
0258: final QueryParameters queryParameters,
0259: final boolean returnProxies) throws HibernateException {
0260:
0261: final int entitySpan = getEntityPersisters().length;
0262: final List hydratedObjects = entitySpan == 0 ? null
0263: : new ArrayList(entitySpan);
0264:
0265: final Object result;
0266: try {
0267: result = getRowFromResultSet(resultSet, session,
0268: queryParameters, getLockModes(queryParameters
0269: .getLockModes()), null, hydratedObjects,
0270: new EntityKey[entitySpan], returnProxies);
0271: } catch (SQLException sqle) {
0272: throw JDBCExceptionHelper.convert(factory
0273: .getSQLExceptionConverter(), sqle,
0274: "could not read next row of results",
0275: getSQLString());
0276: }
0277:
0278: initializeEntitiesAndCollections(hydratedObjects, resultSet,
0279: session, queryParameters.isReadOnly());
0280: session.getPersistenceContext().initializeNonLazyCollections();
0281: return result;
0282: }
0283:
0284: private Object sequentialLoad(final ResultSet resultSet,
0285: final SessionImplementor session,
0286: final QueryParameters queryParameters,
0287: final boolean returnProxies, final EntityKey keyToRead)
0288: throws HibernateException {
0289:
0290: final int entitySpan = getEntityPersisters().length;
0291: final List hydratedObjects = entitySpan == 0 ? null
0292: : new ArrayList(entitySpan);
0293:
0294: Object result = null;
0295: final EntityKey[] loadedKeys = new EntityKey[entitySpan];
0296:
0297: try {
0298: do {
0299: Object loaded = getRowFromResultSet(resultSet, session,
0300: queryParameters, getLockModes(queryParameters
0301: .getLockModes()), null,
0302: hydratedObjects, loadedKeys, returnProxies);
0303: if (result == null) {
0304: result = loaded;
0305: }
0306: } while (keyToRead.equals(loadedKeys[0])
0307: && resultSet.next());
0308: } catch (SQLException sqle) {
0309: throw JDBCExceptionHelper
0310: .convert(
0311: factory.getSQLExceptionConverter(),
0312: sqle,
0313: "could not perform sequential read of results (forward)",
0314: getSQLString());
0315: }
0316:
0317: initializeEntitiesAndCollections(hydratedObjects, resultSet,
0318: session, queryParameters.isReadOnly());
0319: session.getPersistenceContext().initializeNonLazyCollections();
0320: return result;
0321: }
0322:
0323: /**
0324: * Loads a single logical row from the result set moving forward. This is the
0325: * processing used from the ScrollableResults where there were collection fetches
0326: * encountered; thus a single logical row may have multiple rows in the underlying
0327: * result set.
0328: *
0329: * @param resultSet The result set from which to do the load.
0330: * @param session The session from which the request originated.
0331: * @param queryParameters The query parameters specified by the user.
0332: * @param returnProxies Should proxies be generated
0333: * @return The loaded "row".
0334: * @throws HibernateException
0335: */
0336: public Object loadSequentialRowsForward(final ResultSet resultSet,
0337: final SessionImplementor session,
0338: final QueryParameters queryParameters,
0339: final boolean returnProxies) throws HibernateException {
0340:
0341: // note that for sequential scrolling, we make the assumption that
0342: // the first persister element is the "root entity"
0343:
0344: try {
0345: if (resultSet.isAfterLast()) {
0346: // don't even bother trying to read further
0347: return null;
0348: }
0349:
0350: if (resultSet.isBeforeFirst()) {
0351: resultSet.next();
0352: }
0353:
0354: // We call getKeyFromResultSet() here so that we can know the
0355: // key value upon which to perform the breaking logic. However,
0356: // it is also then called from getRowFromResultSet() which is certainly
0357: // not the most efficient. But the call here is needed, and there
0358: // currently is no other way without refactoring of the doQuery()/getRowFromResultSet()
0359: // methods
0360: final EntityKey currentKey = getKeyFromResultSet(0,
0361: getEntityPersisters()[0], null, resultSet, session);
0362:
0363: return sequentialLoad(resultSet, session, queryParameters,
0364: returnProxies, currentKey);
0365: } catch (SQLException sqle) {
0366: throw JDBCExceptionHelper
0367: .convert(
0368: factory.getSQLExceptionConverter(),
0369: sqle,
0370: "could not perform sequential read of results (forward)",
0371: getSQLString());
0372: }
0373: }
0374:
0375: /**
0376: * Loads a single logical row from the result set moving forward. This is the
0377: * processing used from the ScrollableResults where there were collection fetches
0378: * encountered; thus a single logical row may have multiple rows in the underlying
0379: * result set.
0380: *
0381: * @param resultSet The result set from which to do the load.
0382: * @param session The session from which the request originated.
0383: * @param queryParameters The query parameters specified by the user.
0384: * @param returnProxies Should proxies be generated
0385: * @return The loaded "row".
0386: * @throws HibernateException
0387: */
0388: public Object loadSequentialRowsReverse(final ResultSet resultSet,
0389: final SessionImplementor session,
0390: final QueryParameters queryParameters,
0391: final boolean returnProxies,
0392: final boolean isLogicallyAfterLast)
0393: throws HibernateException {
0394:
0395: // note that for sequential scrolling, we make the assumption that
0396: // the first persister element is the "root entity"
0397:
0398: try {
0399: if (resultSet.isFirst()) {
0400: // don't even bother trying to read any further
0401: return null;
0402: }
0403:
0404: EntityKey keyToRead = null;
0405: // This check is needed since processing leaves the cursor
0406: // after the last physical row for the current logical row;
0407: // thus if we are after the last physical row, this might be
0408: // caused by either:
0409: // 1) scrolling to the last logical row
0410: // 2) scrolling past the last logical row
0411: // In the latter scenario, the previous logical row
0412: // really is the last logical row.
0413: //
0414: // In all other cases, we should process back two
0415: // logical records (the current logic row, plus the
0416: // previous logical row).
0417: if (resultSet.isAfterLast() && isLogicallyAfterLast) {
0418: // position cursor to the last row
0419: resultSet.last();
0420: keyToRead = getKeyFromResultSet(0,
0421: getEntityPersisters()[0], null, resultSet,
0422: session);
0423: } else {
0424: // Since the result set cursor is always left at the first
0425: // physical row after the "last processed", we need to jump
0426: // back one position to get the key value we are interested
0427: // in skipping
0428: resultSet.previous();
0429:
0430: // sequentially read the result set in reverse until we recognize
0431: // a change in the key value. At that point, we are pointed at
0432: // the last physical sequential row for the logical row in which
0433: // we are interested in processing
0434: boolean firstPass = true;
0435: final EntityKey lastKey = getKeyFromResultSet(0,
0436: getEntityPersisters()[0], null, resultSet,
0437: session);
0438: while (resultSet.previous()) {
0439: EntityKey checkKey = getKeyFromResultSet(0,
0440: getEntityPersisters()[0], null, resultSet,
0441: session);
0442:
0443: if (firstPass) {
0444: firstPass = false;
0445: keyToRead = checkKey;
0446: }
0447:
0448: if (!lastKey.equals(checkKey)) {
0449: break;
0450: }
0451: }
0452:
0453: }
0454:
0455: // Read backwards until we read past the first physical sequential
0456: // row with the key we are interested in loading
0457: while (resultSet.previous()) {
0458: EntityKey checkKey = getKeyFromResultSet(0,
0459: getEntityPersisters()[0], null, resultSet,
0460: session);
0461:
0462: if (!keyToRead.equals(checkKey)) {
0463: break;
0464: }
0465: }
0466:
0467: // Finally, read ahead one row to position result set cursor
0468: // at the first physical row we are interested in loading
0469: resultSet.next();
0470:
0471: // and perform the load
0472: return sequentialLoad(resultSet, session, queryParameters,
0473: returnProxies, keyToRead);
0474: } catch (SQLException sqle) {
0475: throw JDBCExceptionHelper
0476: .convert(
0477: factory.getSQLExceptionConverter(),
0478: sqle,
0479: "could not perform sequential read of results (forward)",
0480: getSQLString());
0481: }
0482: }
0483:
0484: private static EntityKey getOptionalObjectKey(
0485: QueryParameters queryParameters, SessionImplementor session) {
0486: final Object optionalObject = queryParameters
0487: .getOptionalObject();
0488: final Serializable optionalId = queryParameters.getOptionalId();
0489: final String optionalEntityName = queryParameters
0490: .getOptionalEntityName();
0491:
0492: if (optionalObject != null && optionalEntityName != null) {
0493: return new EntityKey(optionalId, session
0494: .getEntityPersister(optionalEntityName,
0495: optionalObject), session.getEntityMode());
0496: } else {
0497: return null;
0498: }
0499:
0500: }
0501:
0502: private Object getRowFromResultSet(final ResultSet resultSet,
0503: final SessionImplementor session,
0504: final QueryParameters queryParameters,
0505: final LockMode[] lockModeArray,
0506: final EntityKey optionalObjectKey,
0507: final List hydratedObjects, final EntityKey[] keys,
0508: boolean returnProxies) throws SQLException,
0509: HibernateException {
0510:
0511: final Loadable[] persisters = getEntityPersisters();
0512: final int entitySpan = persisters.length;
0513:
0514: for (int i = 0; i < entitySpan; i++) {
0515: keys[i] = getKeyFromResultSet(i, persisters[i],
0516: i == entitySpan - 1 ? queryParameters
0517: .getOptionalId() : null, resultSet, session);
0518: //TODO: the i==entitySpan-1 bit depends upon subclass implementation (very bad)
0519: }
0520:
0521: registerNonExists(keys, persisters, session);
0522:
0523: // this call is side-effecty
0524: Object[] row = getRow(resultSet, persisters, keys,
0525: queryParameters.getOptionalObject(), optionalObjectKey,
0526: lockModeArray, hydratedObjects, session);
0527:
0528: readCollectionElements(row, resultSet, session);
0529:
0530: if (returnProxies) {
0531: // now get an existing proxy for each row element (if there is one)
0532: for (int i = 0; i < entitySpan; i++) {
0533: Object entity = row[i];
0534: Object proxy = session.getPersistenceContext()
0535: .proxyFor(persisters[i], keys[i], entity);
0536: if (entity != proxy) {
0537: // force the proxy to resolve itself
0538: ((HibernateProxy) proxy)
0539: .getHibernateLazyInitializer()
0540: .setImplementation(entity);
0541: row[i] = proxy;
0542: }
0543: }
0544: }
0545:
0546: return getResultColumnOrRow(row, queryParameters
0547: .getResultTransformer(), resultSet, session);
0548:
0549: }
0550:
0551: /**
0552: * Read any collection elements contained in a single row of the result set
0553: */
0554: private void readCollectionElements(Object[] row,
0555: ResultSet resultSet, SessionImplementor session)
0556: throws SQLException, HibernateException {
0557:
0558: //TODO: make this handle multiple collection roles!
0559:
0560: final CollectionPersister[] collectionPersisters = getCollectionPersisters();
0561: if (collectionPersisters != null) {
0562:
0563: final CollectionAliases[] descriptors = getCollectionAliases();
0564: final int[] collectionOwners = getCollectionOwners();
0565:
0566: for (int i = 0; i < collectionPersisters.length; i++) {
0567:
0568: final boolean hasCollectionOwners = collectionOwners != null
0569: && collectionOwners[i] > -1;
0570: //true if this is a query and we are loading multiple instances of the same collection role
0571: //otherwise this is a CollectionInitializer and we are loading up a single collection or batch
0572:
0573: final Object owner = hasCollectionOwners ? row[collectionOwners[i]]
0574: : null; //if null, owner will be retrieved from session
0575:
0576: final CollectionPersister collectionPersister = collectionPersisters[i];
0577: final Serializable key;
0578: if (owner == null) {
0579: key = null;
0580: } else {
0581: key = collectionPersister.getCollectionType()
0582: .getKeyOfOwner(owner, session);
0583: //TODO: old version did not require hashmap lookup:
0584: //keys[collectionOwner].getIdentifier()
0585: }
0586:
0587: readCollectionElement(owner, key, collectionPersister,
0588: descriptors[i], resultSet, session);
0589:
0590: }
0591:
0592: }
0593: }
0594:
0595: private List doQuery(final SessionImplementor session,
0596: final QueryParameters queryParameters,
0597: final boolean returnProxies) throws SQLException,
0598: HibernateException {
0599:
0600: final RowSelection selection = queryParameters
0601: .getRowSelection();
0602: final int maxRows = hasMaxRows(selection) ? selection
0603: .getMaxRows().intValue() : Integer.MAX_VALUE;
0604:
0605: final int entitySpan = getEntityPersisters().length;
0606:
0607: final ArrayList hydratedObjects = entitySpan == 0 ? null
0608: : new ArrayList(entitySpan * 10);
0609: final PreparedStatement st = prepareQueryStatement(
0610: queryParameters, false, session);
0611: final ResultSet rs = getResultSet(st, queryParameters
0612: .hasAutoDiscoverScalarTypes(), queryParameters
0613: .isCallable(), selection, session);
0614:
0615: // would be great to move all this below here into another method that could also be used
0616: // from the new scrolling stuff.
0617: //
0618: // Would need to change the way the max-row stuff is handled (i.e. behind an interface) so
0619: // that I could do the control breaking at the means to know when to stop
0620: final LockMode[] lockModeArray = getLockModes(queryParameters
0621: .getLockModes());
0622: final EntityKey optionalObjectKey = getOptionalObjectKey(
0623: queryParameters, session);
0624:
0625: final boolean createSubselects = isSubselectLoadingEnabled();
0626: final List subselectResultKeys = createSubselects ? new ArrayList()
0627: : null;
0628: final List results = new ArrayList();
0629:
0630: try {
0631:
0632: handleEmptyCollections(queryParameters.getCollectionKeys(),
0633: rs, session);
0634:
0635: EntityKey[] keys = new EntityKey[entitySpan]; //we can reuse it for each row
0636:
0637: if (log.isTraceEnabled())
0638: log.trace("processing result set");
0639:
0640: int count;
0641: for (count = 0; count < maxRows && rs.next(); count++) {
0642:
0643: if (log.isTraceEnabled())
0644: log.debug("result set row: " + count);
0645:
0646: Object result = getRowFromResultSet(rs, session,
0647: queryParameters, lockModeArray,
0648: optionalObjectKey, hydratedObjects, keys,
0649: returnProxies);
0650: results.add(result);
0651:
0652: if (createSubselects) {
0653: subselectResultKeys.add(keys);
0654: keys = new EntityKey[entitySpan]; //can't reuse in this case
0655: }
0656:
0657: }
0658:
0659: if (log.isTraceEnabled()) {
0660: log.trace("done processing result set (" + count
0661: + " rows)");
0662: }
0663:
0664: } finally {
0665: session.getBatcher().closeQueryStatement(st, rs);
0666: }
0667:
0668: initializeEntitiesAndCollections(hydratedObjects, rs, session,
0669: queryParameters.isReadOnly());
0670:
0671: if (createSubselects)
0672: createSubselects(subselectResultKeys, queryParameters,
0673: session);
0674:
0675: return results; //getResultList(results);
0676:
0677: }
0678:
0679: protected boolean isSubselectLoadingEnabled() {
0680: return false;
0681: }
0682:
0683: protected boolean hasSubselectLoadableCollections() {
0684: final Loadable[] loadables = getEntityPersisters();
0685: for (int i = 0; i < loadables.length; i++) {
0686: if (loadables[i].hasSubselectLoadableCollections())
0687: return true;
0688: }
0689: return false;
0690: }
0691:
0692: private static Set[] transpose(List keys) {
0693: Set[] result = new Set[((EntityKey[]) keys.get(0)).length];
0694: for (int j = 0; j < result.length; j++) {
0695: result[j] = new HashSet(keys.size());
0696: for (int i = 0; i < keys.size(); i++) {
0697: result[j].add(((EntityKey[]) keys.get(i))[j]);
0698: }
0699: }
0700: return result;
0701: }
0702:
0703: private void createSubselects(List keys,
0704: QueryParameters queryParameters, SessionImplementor session) {
0705: if (keys.size() > 1) { //if we only returned one entity, query by key is more efficient
0706:
0707: Set[] keySets = transpose(keys);
0708:
0709: Map namedParameterLocMap = buildNamedParameterLocMap(queryParameters);
0710:
0711: final Loadable[] loadables = getEntityPersisters();
0712: final String[] aliases = getAliases();
0713: final Iterator iter = keys.iterator();
0714: while (iter.hasNext()) {
0715:
0716: final EntityKey[] rowKeys = (EntityKey[]) iter.next();
0717: for (int i = 0; i < rowKeys.length; i++) {
0718:
0719: if (rowKeys[i] != null
0720: && loadables[i]
0721: .hasSubselectLoadableCollections()) {
0722:
0723: SubselectFetch subselectFetch = new SubselectFetch(
0724: //getSQLString(),
0725: aliases[i], loadables[i],
0726: queryParameters, keySets[i],
0727: namedParameterLocMap);
0728:
0729: session.getPersistenceContext()
0730: .getBatchFetchQueue().addSubselect(
0731: rowKeys[i], subselectFetch);
0732: }
0733:
0734: }
0735:
0736: }
0737: }
0738: }
0739:
0740: private Map buildNamedParameterLocMap(
0741: QueryParameters queryParameters) {
0742: if (queryParameters.getNamedParameters() != null) {
0743: final Map namedParameterLocMap = new HashMap();
0744: Iterator piter = queryParameters.getNamedParameters()
0745: .keySet().iterator();
0746: while (piter.hasNext()) {
0747: String name = (String) piter.next();
0748: namedParameterLocMap.put(name,
0749: getNamedParameterLocs(name));
0750: }
0751: return namedParameterLocMap;
0752: } else {
0753: return null;
0754: }
0755: }
0756:
0757: private void initializeEntitiesAndCollections(
0758: final List hydratedObjects, final Object resultSetId,
0759: final SessionImplementor session, final boolean readOnly)
0760: throws HibernateException {
0761:
0762: final CollectionPersister[] collectionPersisters = getCollectionPersisters();
0763: if (collectionPersisters != null) {
0764: for (int i = 0; i < collectionPersisters.length; i++) {
0765: if (collectionPersisters[i].isArray()) {
0766: //for arrays, we should end the collection load before resolving
0767: //the entities, since the actual array instances are not instantiated
0768: //during loading
0769: //TODO: or we could do this polymorphically, and have two
0770: // different operations implemented differently for arrays
0771: endCollectionLoad(resultSetId, session,
0772: collectionPersisters[i]);
0773: }
0774: }
0775: }
0776:
0777: //important: reuse the same event instances for performance!
0778: final PreLoadEvent pre;
0779: final PostLoadEvent post;
0780: if (session.isEventSource()) {
0781: pre = new PreLoadEvent((EventSource) session);
0782: post = new PostLoadEvent((EventSource) session);
0783: } else {
0784: pre = null;
0785: post = null;
0786: }
0787:
0788: if (hydratedObjects != null) {
0789: int hydratedObjectsSize = hydratedObjects.size();
0790: if (log.isTraceEnabled()) {
0791: log.trace("total objects hydrated: "
0792: + hydratedObjectsSize);
0793: }
0794: for (int i = 0; i < hydratedObjectsSize; i++) {
0795: TwoPhaseLoad.initializeEntity(hydratedObjects.get(i),
0796: readOnly, session, pre, post);
0797: }
0798: }
0799:
0800: if (collectionPersisters != null) {
0801: for (int i = 0; i < collectionPersisters.length; i++) {
0802: if (!collectionPersisters[i].isArray()) {
0803: //for sets, we should end the collection load after resolving
0804: //the entities, since we might call hashCode() on the elements
0805: //TODO: or we could do this polymorphically, and have two
0806: // different operations implemented differently for arrays
0807: endCollectionLoad(resultSetId, session,
0808: collectionPersisters[i]);
0809: }
0810: }
0811: }
0812:
0813: }
0814:
0815: private void endCollectionLoad(final Object resultSetId,
0816: final SessionImplementor session,
0817: final CollectionPersister collectionPersister) {
0818: //this is a query and we are loading multiple instances of the same collection role
0819: session.getPersistenceContext().getLoadContexts()
0820: .getCollectionLoadContext((ResultSet) resultSetId)
0821: .endLoadingCollections(collectionPersister);
0822: }
0823:
0824: protected List getResultList(List results,
0825: ResultTransformer resultTransformer) throws QueryException {
0826: return results;
0827: }
0828:
0829: /**
0830: * Get the actual object that is returned in the user-visible result list.
0831: * This empty implementation merely returns its first argument. This is
0832: * overridden by some subclasses.
0833: */
0834: protected Object getResultColumnOrRow(Object[] row,
0835: ResultTransformer transformer, ResultSet rs,
0836: SessionImplementor session) throws SQLException,
0837: HibernateException {
0838: return row;
0839: }
0840:
0841: /**
0842: * For missing objects associated by one-to-one with another object in the
0843: * result set, register the fact that the the object is missing with the
0844: * session.
0845: */
0846: private void registerNonExists(final EntityKey[] keys,
0847: final Loadable[] persisters,
0848: final SessionImplementor session) {
0849:
0850: final int[] owners = getOwners();
0851: if (owners != null) {
0852:
0853: EntityType[] ownerAssociationTypes = getOwnerAssociationTypes();
0854: for (int i = 0; i < keys.length; i++) {
0855:
0856: int owner = owners[i];
0857: if (owner > -1) {
0858: EntityKey ownerKey = keys[owner];
0859: if (keys[i] == null && ownerKey != null) {
0860:
0861: final PersistenceContext persistenceContext = session
0862: .getPersistenceContext();
0863:
0864: /*final boolean isPrimaryKey;
0865: final boolean isSpecialOneToOne;
0866: if ( ownerAssociationTypes == null || ownerAssociationTypes[i] == null ) {
0867: isPrimaryKey = true;
0868: isSpecialOneToOne = false;
0869: }
0870: else {
0871: isPrimaryKey = ownerAssociationTypes[i].getRHSUniqueKeyPropertyName()==null;
0872: isSpecialOneToOne = ownerAssociationTypes[i].getLHSPropertyName()!=null;
0873: }*/
0874:
0875: //TODO: can we *always* use the "null property" approach for everything?
0876: /*if ( isPrimaryKey && !isSpecialOneToOne ) {
0877: persistenceContext.addNonExistantEntityKey(
0878: new EntityKey( ownerKey.getIdentifier(), persisters[i], session.getEntityMode() )
0879: );
0880: }
0881: else if ( isSpecialOneToOne ) {*/
0882: boolean isOneToOneAssociation = ownerAssociationTypes != null
0883: && ownerAssociationTypes[i] != null
0884: && ownerAssociationTypes[i]
0885: .isOneToOne();
0886: if (isOneToOneAssociation) {
0887: persistenceContext.addNullProperty(
0888: ownerKey, ownerAssociationTypes[i]
0889: .getPropertyName());
0890: }
0891: /*}
0892: else {
0893: persistenceContext.addNonExistantEntityUniqueKey( new EntityUniqueKey(
0894: persisters[i].getEntityName(),
0895: ownerAssociationTypes[i].getRHSUniqueKeyPropertyName(),
0896: ownerKey.getIdentifier(),
0897: persisters[owner].getIdentifierType(),
0898: session.getEntityMode()
0899: ) );
0900: }*/
0901: }
0902: }
0903: }
0904: }
0905: }
0906:
0907: /**
0908: * Read one collection element from the current row of the JDBC result set
0909: */
0910: private void readCollectionElement(final Object optionalOwner,
0911: final Serializable optionalKey,
0912: final CollectionPersister persister,
0913: final CollectionAliases descriptor, final ResultSet rs,
0914: final SessionImplementor session)
0915: throws HibernateException, SQLException {
0916:
0917: final PersistenceContext persistenceContext = session
0918: .getPersistenceContext();
0919:
0920: final Serializable collectionRowKey = (Serializable) persister
0921: .readKey(rs, descriptor.getSuffixedKeyAliases(),
0922: session);
0923:
0924: if (collectionRowKey != null) {
0925: // we found a collection element in the result set
0926:
0927: if (log.isDebugEnabled()) {
0928: log.debug("found row of collection: "
0929: + MessageHelper.collectionInfoString(persister,
0930: collectionRowKey, getFactory()));
0931: }
0932:
0933: Object owner = optionalOwner;
0934: if (owner == null) {
0935: owner = persistenceContext.getCollectionOwner(
0936: collectionRowKey, persister);
0937: if (owner == null) {
0938: //TODO: This is assertion is disabled because there is a bug that means the
0939: // original owner of a transient, uninitialized collection is not known
0940: // if the collection is re-referenced by a different object associated
0941: // with the current Session
0942: //throw new AssertionFailure("bug loading unowned collection");
0943: }
0944: }
0945:
0946: PersistentCollection rowCollection = persistenceContext
0947: .getLoadContexts().getCollectionLoadContext(rs)
0948: .getLoadingCollection(persister, collectionRowKey);
0949:
0950: if (rowCollection != null) {
0951: rowCollection
0952: .readFrom(rs, persister, descriptor, owner);
0953: }
0954:
0955: } else if (optionalKey != null) {
0956: // we did not find a collection element in the result set, so we
0957: // ensure that a collection is created with the owner's identifier,
0958: // since what we have is an empty collection
0959:
0960: if (log.isDebugEnabled()) {
0961: log
0962: .debug("result set contains (possibly empty) collection: "
0963: + MessageHelper.collectionInfoString(
0964: persister, optionalKey,
0965: getFactory()));
0966: }
0967:
0968: persistenceContext.getLoadContexts()
0969: .getCollectionLoadContext(rs).getLoadingCollection(
0970: persister, optionalKey); // handle empty collection
0971: }
0972:
0973: // else no collection element, but also no owner
0974:
0975: }
0976:
0977: /**
0978: * If this is a collection initializer, we need to tell the session that a collection
0979: * is being initilized, to account for the possibility of the collection having
0980: * no elements (hence no rows in the result set).
0981: */
0982: private void handleEmptyCollections(final Serializable[] keys,
0983: final Object resultSetId, final SessionImplementor session) {
0984:
0985: if (keys != null) {
0986: // this is a collection initializer, so we must create a collection
0987: // for each of the passed-in keys, to account for the possibility
0988: // that the collection is empty and has no rows in the result set
0989:
0990: CollectionPersister[] collectionPersisters = getCollectionPersisters();
0991: for (int j = 0; j < collectionPersisters.length; j++) {
0992: for (int i = 0; i < keys.length; i++) {
0993: //handle empty collections
0994:
0995: if (log.isDebugEnabled()) {
0996: log
0997: .debug("result set contains (possibly empty) collection: "
0998: + MessageHelper
0999: .collectionInfoString(
1000: collectionPersisters[j],
1001: keys[i],
1002: getFactory()));
1003: }
1004:
1005: session.getPersistenceContext().getLoadContexts()
1006: .getCollectionLoadContext(
1007: (ResultSet) resultSetId)
1008: .getLoadingCollection(
1009: collectionPersisters[j], keys[i]);
1010: }
1011: }
1012: }
1013:
1014: // else this is not a collection initializer (and empty collections will
1015: // be detected by looking for the owner's identifier in the result set)
1016: }
1017:
1018: /**
1019: * Read a row of <tt>Key</tt>s from the <tt>ResultSet</tt> into the given array.
1020: * Warning: this method is side-effecty.
1021: * <p/>
1022: * If an <tt>id</tt> is given, don't bother going to the <tt>ResultSet</tt>.
1023: */
1024: private EntityKey getKeyFromResultSet(final int i,
1025: final Loadable persister, final Serializable id,
1026: final ResultSet rs, final SessionImplementor session)
1027: throws HibernateException, SQLException {
1028:
1029: Serializable resultId;
1030:
1031: // if we know there is exactly 1 row, we can skip.
1032: // it would be great if we could _always_ skip this;
1033: // it is a problem for <key-many-to-one>
1034:
1035: if (isSingleRowLoader() && id != null) {
1036: resultId = id;
1037: } else {
1038:
1039: Type idType = persister.getIdentifierType();
1040: resultId = (Serializable) idType.nullSafeGet(rs,
1041: getEntityAliases()[i].getSuffixedKeyAliases(),
1042: session, null //problematic for <key-many-to-one>!
1043: );
1044:
1045: final boolean idIsResultId = id != null
1046: && resultId != null
1047: && idType.isEqual(id, resultId, session
1048: .getEntityMode(), factory);
1049:
1050: if (idIsResultId)
1051: resultId = id; //use the id passed in
1052: }
1053:
1054: return resultId == null ? null : new EntityKey(resultId,
1055: persister, session.getEntityMode());
1056: }
1057:
1058: /**
1059: * Check the version of the object in the <tt>ResultSet</tt> against
1060: * the object version in the session cache, throwing an exception
1061: * if the version numbers are different
1062: */
1063: private void checkVersion(final int i, final Loadable persister,
1064: final Serializable id, final Object entity,
1065: final ResultSet rs, final SessionImplementor session)
1066: throws HibernateException, SQLException {
1067:
1068: Object version = session.getPersistenceContext().getEntry(
1069: entity).getVersion();
1070:
1071: if (version != null) { //null version means the object is in the process of being loaded somewhere else in the ResultSet
1072: VersionType versionType = persister.getVersionType();
1073: Object currentVersion = versionType.nullSafeGet(rs,
1074: getEntityAliases()[i].getSuffixedVersionAliases(),
1075: session, null);
1076: if (!versionType.isEqual(version, currentVersion)) {
1077: if (session.getFactory().getStatistics()
1078: .isStatisticsEnabled()) {
1079: session.getFactory().getStatisticsImplementor()
1080: .optimisticFailure(
1081: persister.getEntityName());
1082: }
1083: throw new StaleObjectStateException(persister
1084: .getEntityName(), id);
1085: }
1086: }
1087:
1088: }
1089:
1090: /**
1091: * Resolve any ids for currently loaded objects, duplications within the
1092: * <tt>ResultSet</tt>, etc. Instantiate empty objects to be initialized from the
1093: * <tt>ResultSet</tt>. Return an array of objects (a row of results) and an
1094: * array of booleans (by side-effect) that determine whether the corresponding
1095: * object should be initialized.
1096: */
1097: private Object[] getRow(final ResultSet rs,
1098: final Loadable[] persisters, final EntityKey[] keys,
1099: final Object optionalObject,
1100: final EntityKey optionalObjectKey,
1101: final LockMode[] lockModes, final List hydratedObjects,
1102: final SessionImplementor session)
1103: throws HibernateException, SQLException {
1104:
1105: final int cols = persisters.length;
1106: final EntityAliases[] descriptors = getEntityAliases();
1107:
1108: if (log.isDebugEnabled()) {
1109: log.debug("result row: " + StringHelper.toString(keys));
1110: }
1111:
1112: final Object[] rowResults = new Object[cols];
1113:
1114: for (int i = 0; i < cols; i++) {
1115:
1116: Object object = null;
1117: EntityKey key = keys[i];
1118:
1119: if (keys[i] == null) {
1120: //do nothing
1121: } else {
1122:
1123: //If the object is already loaded, return the loaded one
1124: object = session.getEntityUsingInterceptor(key);
1125: if (object != null) {
1126: //its already loaded so don't need to hydrate it
1127: instanceAlreadyLoaded(rs, i, persisters[i], key,
1128: object, lockModes[i], session);
1129: } else {
1130: object = instanceNotYetLoaded(rs, i, persisters[i],
1131: descriptors[i].getRowIdAlias(), key,
1132: lockModes[i], optionalObjectKey,
1133: optionalObject, hydratedObjects, session);
1134: }
1135:
1136: }
1137:
1138: rowResults[i] = object;
1139:
1140: }
1141:
1142: return rowResults;
1143: }
1144:
1145: /**
1146: * The entity instance is already in the session cache
1147: */
1148: private void instanceAlreadyLoaded(final ResultSet rs, final int i,
1149: final Loadable persister, final EntityKey key,
1150: final Object object, final LockMode lockMode,
1151: final SessionImplementor session)
1152: throws HibernateException, SQLException {
1153:
1154: if (!persister.isInstance(object, session.getEntityMode())) {
1155: throw new WrongClassException(
1156: "loaded object was of wrong class "
1157: + object.getClass(), key.getIdentifier(),
1158: persister.getEntityName());
1159: }
1160:
1161: if (LockMode.NONE != lockMode && upgradeLocks()) { //no point doing this if NONE was requested
1162:
1163: final boolean isVersionCheckNeeded = persister
1164: .isVersioned()
1165: && session.getPersistenceContext().getEntry(object)
1166: .getLockMode().lessThan(lockMode);
1167: // we don't need to worry about existing version being uninitialized
1168: // because this block isn't called by a re-entrant load (re-entrant
1169: // loads _always_ have lock mode NONE)
1170: if (isVersionCheckNeeded) {
1171: //we only check the version when _upgrading_ lock modes
1172: checkVersion(i, persister, key.getIdentifier(), object,
1173: rs, session);
1174: //we need to upgrade the lock mode to the mode requested
1175: session.getPersistenceContext().getEntry(object)
1176: .setLockMode(lockMode);
1177: }
1178: }
1179: }
1180:
1181: /**
1182: * The entity instance is not in the session cache
1183: */
1184: private Object instanceNotYetLoaded(final ResultSet rs,
1185: final int i, final Loadable persister,
1186: final String rowIdAlias, final EntityKey key,
1187: final LockMode lockMode, final EntityKey optionalObjectKey,
1188: final Object optionalObject, final List hydratedObjects,
1189: final SessionImplementor session)
1190: throws HibernateException, SQLException {
1191:
1192: final String instanceClass = getInstanceClass(rs, i, persister,
1193: key.getIdentifier(), session);
1194:
1195: final Object object;
1196: if (optionalObjectKey != null && key.equals(optionalObjectKey)) {
1197: //its the given optional object
1198: object = optionalObject;
1199: } else {
1200: // instantiate a new instance
1201: object = session.instantiate(instanceClass, key
1202: .getIdentifier());
1203: }
1204:
1205: //need to hydrate it.
1206:
1207: // grab its state from the ResultSet and keep it in the Session
1208: // (but don't yet initialize the object itself)
1209: // note that we acquire LockMode.READ even if it was not requested
1210: LockMode acquiredLockMode = lockMode == LockMode.NONE ? LockMode.READ
1211: : lockMode;
1212: loadFromResultSet(rs, i, object, instanceClass, key,
1213: rowIdAlias, acquiredLockMode, persister, session);
1214:
1215: //materialize associations (and initialize the object) later
1216: hydratedObjects.add(object);
1217:
1218: return object;
1219: }
1220:
1221: private boolean isEagerPropertyFetchEnabled(int i) {
1222: boolean[] array = getEntityEagerPropertyFetches();
1223: return array != null && array[i];
1224: }
1225:
1226: /**
1227: * Hydrate the state an object from the SQL <tt>ResultSet</tt>, into
1228: * an array or "hydrated" values (do not resolve associations yet),
1229: * and pass the hydrates state to the session.
1230: */
1231: private void loadFromResultSet(final ResultSet rs, final int i,
1232: final Object object, final String instanceEntityName,
1233: final EntityKey key, final String rowIdAlias,
1234: final LockMode lockMode, final Loadable rootPersister,
1235: final SessionImplementor session) throws SQLException,
1236: HibernateException {
1237:
1238: final Serializable id = key.getIdentifier();
1239:
1240: // Get the persister for the _subclass_
1241: final Loadable persister = (Loadable) getFactory()
1242: .getEntityPersister(instanceEntityName);
1243:
1244: if (log.isTraceEnabled()) {
1245: log.trace("Initializing object from ResultSet: "
1246: + MessageHelper.infoString(persister, id,
1247: getFactory()));
1248: }
1249:
1250: boolean eagerPropertyFetch = isEagerPropertyFetchEnabled(i);
1251:
1252: // add temp entry so that the next step is circular-reference
1253: // safe - only needed because some types don't take proper
1254: // advantage of two-phase-load (esp. components)
1255: TwoPhaseLoad.addUninitializedEntity(key, object, persister,
1256: lockMode, !eagerPropertyFetch, session);
1257:
1258: //This is not very nice (and quite slow):
1259: final String[][] cols = persister == rootPersister ? getEntityAliases()[i]
1260: .getSuffixedPropertyAliases()
1261: : getEntityAliases()[i]
1262: .getSuffixedPropertyAliases(persister);
1263:
1264: final Object[] values = persister.hydrate(rs, id, object,
1265: rootPersister, cols, eagerPropertyFetch, session);
1266:
1267: final Object rowId = persister.hasRowId() ? rs
1268: .getObject(rowIdAlias) : null;
1269:
1270: final AssociationType[] ownerAssociationTypes = getOwnerAssociationTypes();
1271: if (ownerAssociationTypes != null
1272: && ownerAssociationTypes[i] != null) {
1273: String ukName = ownerAssociationTypes[i]
1274: .getRHSUniqueKeyPropertyName();
1275: if (ukName != null) {
1276: final int index = ((UniqueKeyLoadable) persister)
1277: .getPropertyIndex(ukName);
1278: final Type type = persister.getPropertyTypes()[index];
1279:
1280: // polymorphism not really handled completely correctly,
1281: // perhaps...well, actually its ok, assuming that the
1282: // entity name used in the lookup is the same as the
1283: // the one used here, which it will be
1284:
1285: EntityUniqueKey euk = new EntityUniqueKey(rootPersister
1286: .getEntityName(), //polymorphism comment above
1287: ukName, type.semiResolve(values[index],
1288: session, object), type, session
1289: .getEntityMode(), session.getFactory());
1290: session.getPersistenceContext().addEntity(euk, object);
1291: }
1292: }
1293:
1294: TwoPhaseLoad.postHydrate(persister, id, values, rowId, object,
1295: lockMode, !eagerPropertyFetch, session);
1296:
1297: }
1298:
1299: /**
1300: * Determine the concrete class of an instance in the <tt>ResultSet</tt>
1301: */
1302: private String getInstanceClass(final ResultSet rs, final int i,
1303: final Loadable persister, final Serializable id,
1304: final SessionImplementor session)
1305: throws HibernateException, SQLException {
1306:
1307: if (persister.hasSubclasses()) {
1308:
1309: // Code to handle subclasses of topClass
1310: Object discriminatorValue = persister
1311: .getDiscriminatorType().nullSafeGet(
1312: rs,
1313: getEntityAliases()[i]
1314: .getSuffixedDiscriminatorAlias(),
1315: session, null);
1316:
1317: final String result = persister
1318: .getSubclassForDiscriminatorValue(discriminatorValue);
1319:
1320: if (result == null) {
1321: //woops we got an instance of another class hierarchy branch
1322: throw new WrongClassException("Discriminator: "
1323: + discriminatorValue, id, persister
1324: .getEntityName());
1325: }
1326:
1327: return result;
1328:
1329: } else {
1330: return persister.getEntityName();
1331: }
1332: }
1333:
1334: /**
1335: * Advance the cursor to the first required row of the <tt>ResultSet</tt>
1336: */
1337: private void advance(final ResultSet rs,
1338: final RowSelection selection) throws SQLException {
1339:
1340: final int firstRow = getFirstRow(selection);
1341: if (firstRow != 0) {
1342: if (getFactory().getSettings()
1343: .isScrollableResultSetsEnabled()) {
1344: // we can go straight to the first required row
1345: rs.absolute(firstRow);
1346: } else {
1347: // we need to step through the rows one row at a time (slow)
1348: for (int m = 0; m < firstRow; m++)
1349: rs.next();
1350: }
1351: }
1352: }
1353:
1354: private static boolean hasMaxRows(RowSelection selection) {
1355: return selection != null && selection.getMaxRows() != null;
1356: }
1357:
1358: private static int getFirstRow(RowSelection selection) {
1359: if (selection == null || selection.getFirstRow() == null) {
1360: return 0;
1361: } else {
1362: return selection.getFirstRow().intValue();
1363: }
1364: }
1365:
1366: /**
1367: * Should we pre-process the SQL string, adding a dialect-specific
1368: * LIMIT clause.
1369: */
1370: private static boolean useLimit(final RowSelection selection,
1371: final Dialect dialect) {
1372: return dialect.supportsLimit() && hasMaxRows(selection);
1373: }
1374:
1375: /**
1376: * Obtain a <tt>PreparedStatement</tt> with all parameters pre-bound.
1377: * Bind JDBC-style <tt>?</tt> parameters, named parameters, and
1378: * limit parameters.
1379: */
1380: protected final PreparedStatement prepareQueryStatement(
1381: final QueryParameters queryParameters,
1382: final boolean scroll, final SessionImplementor session)
1383: throws SQLException, HibernateException {
1384: String sql = processFilters(queryParameters, session);
1385: final Dialect dialect = getFactory().getDialect();
1386: final RowSelection selection = queryParameters
1387: .getRowSelection();
1388: boolean useLimit = useLimit(selection, dialect);
1389: boolean hasFirstRow = getFirstRow(selection) > 0;
1390: boolean useOffset = hasFirstRow && useLimit
1391: && dialect.supportsLimitOffset();
1392: boolean callable = queryParameters.isCallable();
1393:
1394: boolean useScrollableResultSetToSkip = hasFirstRow
1395: && !useOffset
1396: && getFactory().getSettings()
1397: .isScrollableResultSetsEnabled();
1398: ScrollMode scrollMode = scroll ? queryParameters
1399: .getScrollMode() : ScrollMode.SCROLL_INSENSITIVE;
1400:
1401: if (useLimit) {
1402: sql = dialect.getLimitString(
1403: sql.trim(), //use of trim() here is ugly?
1404: useOffset ? getFirstRow(selection) : 0,
1405: getMaxOrLimit(selection, dialect));
1406: }
1407:
1408: sql = preprocessSQL(sql, queryParameters, dialect);
1409:
1410: PreparedStatement st = null;
1411:
1412: if (callable) {
1413: st = session.getBatcher().prepareCallableQueryStatement(
1414: sql, scroll || useScrollableResultSetToSkip,
1415: scrollMode);
1416: } else {
1417: st = session.getBatcher().prepareQueryStatement(sql,
1418: scroll || useScrollableResultSetToSkip, scrollMode);
1419: }
1420:
1421: try {
1422:
1423: int col = 1;
1424: //TODO: can we limit stored procedures ?!
1425: if (useLimit && dialect.bindLimitParametersFirst()) {
1426: col += bindLimitParameters(st, col, selection);
1427: }
1428: if (callable) {
1429: col = dialect.registerResultSetOutParameter(
1430: (CallableStatement) st, col);
1431: }
1432:
1433: col += bindParameterValues(st, queryParameters, col,
1434: session);
1435:
1436: if (useLimit && !dialect.bindLimitParametersFirst()) {
1437: col += bindLimitParameters(st, col, selection);
1438: }
1439:
1440: if (!useLimit) {
1441: setMaxRows(st, selection);
1442: }
1443:
1444: if (selection != null) {
1445: if (selection.getTimeout() != null) {
1446: st.setQueryTimeout(selection.getTimeout()
1447: .intValue());
1448: }
1449: if (selection.getFetchSize() != null) {
1450: st
1451: .setFetchSize(selection.getFetchSize()
1452: .intValue());
1453: }
1454: }
1455: } catch (SQLException sqle) {
1456: session.getBatcher().closeQueryStatement(st, null);
1457: throw sqle;
1458: } catch (HibernateException he) {
1459: session.getBatcher().closeQueryStatement(st, null);
1460: throw he;
1461: }
1462:
1463: return st;
1464: }
1465:
1466: protected String processFilters(QueryParameters queryParameters,
1467: SessionImplementor session) {
1468: queryParameters.processFilters(getSQLString(), session);
1469: return queryParameters.getFilteredSQL();
1470: }
1471:
1472: /**
1473: * Some dialect-specific LIMIT clauses require the maximium last row number
1474: * (aka, first_row_number + total_row_count), while others require the maximum
1475: * returned row count (the total maximum number of rows to return).
1476: *
1477: * @param selection The selection criteria
1478: * @param dialect The dialect
1479: * @return The appropriate value to bind into the limit clause.
1480: */
1481: private static int getMaxOrLimit(final RowSelection selection,
1482: final Dialect dialect) {
1483: final int firstRow = getFirstRow(selection);
1484: final int lastRow = selection.getMaxRows().intValue();
1485: if (dialect.useMaxForLimit()) {
1486: return lastRow + firstRow;
1487: } else {
1488: return lastRow;
1489: }
1490: }
1491:
1492: /**
1493: * Bind parameter values needed by the dialect-specific LIMIT clause.
1494: *
1495: * @param statement The statement to which to bind limit param values.
1496: * @param index The bind position from which to start binding
1497: * @param selection The selection object containing the limit information.
1498: * @return The number of parameter values bound.
1499: * @throws java.sql.SQLException Indicates problems binding parameter values.
1500: */
1501: private int bindLimitParameters(final PreparedStatement statement,
1502: final int index, final RowSelection selection)
1503: throws SQLException {
1504: Dialect dialect = getFactory().getDialect();
1505: if (!dialect.supportsVariableLimit()) {
1506: return 0;
1507: }
1508: if (!hasMaxRows(selection)) {
1509: throw new AssertionFailure("no max results set");
1510: }
1511: int firstRow = getFirstRow(selection);
1512: int lastRow = getMaxOrLimit(selection, dialect);
1513: boolean hasFirstRow = firstRow > 0
1514: && dialect.supportsLimitOffset();
1515: boolean reverse = dialect.bindLimitParametersInReverseOrder();
1516: if (hasFirstRow) {
1517: statement.setInt(index + (reverse ? 1 : 0), firstRow);
1518: }
1519: statement.setInt(index + (reverse || !hasFirstRow ? 0 : 1),
1520: lastRow);
1521: return hasFirstRow ? 2 : 1;
1522: }
1523:
1524: /**
1525: * Use JDBC API to limit the number of rows returned by the SQL query if necessary
1526: */
1527: private void setMaxRows(final PreparedStatement st,
1528: final RowSelection selection) throws SQLException {
1529: if (hasMaxRows(selection)) {
1530: st.setMaxRows(selection.getMaxRows().intValue()
1531: + getFirstRow(selection));
1532: }
1533: }
1534:
1535: /**
1536: * Bind all parameter values into the prepared statement in preparation
1537: * for execution.
1538: *
1539: * @param statement The JDBC prepared statement
1540: * @param queryParameters The encapsulation of the parameter values to be bound.
1541: * @param startIndex The position from which to start binding parameter values.
1542: * @param session The originating session.
1543: * @return The number of JDBC bind positions actually bound during this method execution.
1544: * @throws SQLException Indicates problems performing the binding.
1545: */
1546: protected int bindParameterValues(PreparedStatement statement,
1547: QueryParameters queryParameters, int startIndex,
1548: SessionImplementor session) throws SQLException {
1549: int span = 0;
1550: span += bindPositionalParameters(statement, queryParameters,
1551: startIndex, session);
1552: span += bindNamedParameters(statement, queryParameters
1553: .getNamedParameters(), startIndex + span, session);
1554: return span;
1555: }
1556:
1557: /**
1558: * Bind positional parameter values to the JDBC prepared statement.
1559: * <p/>
1560: * Postional parameters are those specified by JDBC-style ? parameters
1561: * in the source query. It is (currently) expected that these come
1562: * before any named parameters in the source query.
1563: *
1564: * @param statement The JDBC prepared statement
1565: * @param queryParameters The encapsulation of the parameter values to be bound.
1566: * @param startIndex The position from which to start binding parameter values.
1567: * @param session The originating session.
1568: * @return The number of JDBC bind positions actually bound during this method execution.
1569: * @throws SQLException Indicates problems performing the binding.
1570: * @throws org.hibernate.HibernateException Indicates problems delegating binding to the types.
1571: */
1572: protected int bindPositionalParameters(
1573: final PreparedStatement statement,
1574: final QueryParameters queryParameters,
1575: final int startIndex, final SessionImplementor session)
1576: throws SQLException, HibernateException {
1577: final Object[] values = queryParameters
1578: .getFilteredPositionalParameterValues();
1579: final Type[] types = queryParameters
1580: .getFilteredPositionalParameterTypes();
1581: int span = 0;
1582: for (int i = 0; i < values.length; i++) {
1583: types[i].nullSafeSet(statement, values[i], startIndex
1584: + span, session);
1585: span += types[i].getColumnSpan(getFactory());
1586: }
1587: return span;
1588: }
1589:
1590: /**
1591: * Bind named parameters to the JDBC prepared statement.
1592: * <p/>
1593: * This is a generic implementation, the problem being that in the
1594: * general case we do not know enough information about the named
1595: * parameters to perform this in a complete manner here. Thus this
1596: * is generally overridden on subclasses allowing named parameters to
1597: * apply the specific behavior. The most usual limitation here is that
1598: * we need to assume the type span is always one...
1599: *
1600: * @param statement The JDBC prepared statement
1601: * @param namedParams A map of parameter names to values
1602: * @param startIndex The position from which to start binding parameter values.
1603: * @param session The originating session.
1604: * @return The number of JDBC bind positions actually bound during this method execution.
1605: * @throws SQLException Indicates problems performing the binding.
1606: * @throws org.hibernate.HibernateException Indicates problems delegating binding to the types.
1607: */
1608: protected int bindNamedParameters(
1609: final PreparedStatement statement, final Map namedParams,
1610: final int startIndex, final SessionImplementor session)
1611: throws SQLException, HibernateException {
1612: if (namedParams != null) {
1613: // assumes that types are all of span 1
1614: Iterator iter = namedParams.entrySet().iterator();
1615: int result = 0;
1616: while (iter.hasNext()) {
1617: Map.Entry e = (Map.Entry) iter.next();
1618: String name = (String) e.getKey();
1619: TypedValue typedval = (TypedValue) e.getValue();
1620: int[] locs = getNamedParameterLocs(name);
1621: for (int i = 0; i < locs.length; i++) {
1622: if (log.isDebugEnabled()) {
1623: log.debug("bindNamedParameters() "
1624: + typedval.getValue() + " -> " + name
1625: + " [" + (locs[i] + startIndex) + "]");
1626: }
1627: typedval.getType().nullSafeSet(statement,
1628: typedval.getValue(), locs[i] + startIndex,
1629: session);
1630: }
1631: result += locs.length;
1632: }
1633: return result;
1634: } else {
1635: return 0;
1636: }
1637: }
1638:
1639: public int[] getNamedParameterLocs(String name) {
1640: throw new AssertionFailure("no named parameters");
1641: }
1642:
1643: /**
1644: * Fetch a <tt>PreparedStatement</tt>, call <tt>setMaxRows</tt> and then execute it,
1645: * advance to the first result and return an SQL <tt>ResultSet</tt>
1646: */
1647: protected final ResultSet getResultSet(final PreparedStatement st,
1648: final boolean autodiscovertypes, final boolean callable,
1649: final RowSelection selection,
1650: final SessionImplementor session) throws SQLException,
1651: HibernateException {
1652:
1653: ResultSet rs = null;
1654: try {
1655: Dialect dialect = getFactory().getDialect();
1656: if (callable) {
1657: rs = session.getBatcher().getResultSet(
1658: (CallableStatement) st, dialect);
1659: } else {
1660: rs = session.getBatcher().getResultSet(st);
1661: }
1662: rs = wrapResultSetIfEnabled(rs, session);
1663:
1664: if (!dialect.supportsLimitOffset()
1665: || !useLimit(selection, dialect)) {
1666: advance(rs, selection);
1667: }
1668:
1669: if (autodiscovertypes) {
1670: autoDiscoverTypes(rs);
1671: }
1672: return rs;
1673: } catch (SQLException sqle) {
1674: session.getBatcher().closeQueryStatement(st, rs);
1675: throw sqle;
1676: }
1677: }
1678:
1679: protected void autoDiscoverTypes(ResultSet rs) {
1680: throw new AssertionFailure(
1681: "Auto discover types not supported in this loader");
1682:
1683: }
1684:
1685: private synchronized ResultSet wrapResultSetIfEnabled(
1686: final ResultSet rs, final SessionImplementor session) {
1687: // synchronized to avoid multi-thread access issues; defined as method synch to avoid
1688: // potential deadlock issues due to nature of code.
1689: if (session.getFactory().getSettings()
1690: .isWrapResultSetsEnabled()) {
1691: try {
1692: log.debug("Wrapping result set [" + rs + "]");
1693: return new ResultSetWrapper(rs,
1694: retreiveColumnNameToIndexCache(rs));
1695: } catch (SQLException e) {
1696: log.info("Error wrapping result set", e);
1697: return rs;
1698: }
1699: } else {
1700: return rs;
1701: }
1702: }
1703:
1704: private ColumnNameCache retreiveColumnNameToIndexCache(ResultSet rs)
1705: throws SQLException {
1706: if (columnNameCache == null) {
1707: log.trace("Building columnName->columnIndex cache");
1708: columnNameCache = new ColumnNameCache(rs.getMetaData()
1709: .getColumnCount());
1710: }
1711:
1712: return columnNameCache;
1713: }
1714:
1715: /**
1716: * Called by subclasses that load entities
1717: * @param persister only needed for logging
1718: */
1719: protected final List loadEntity(final SessionImplementor session,
1720: final Object id, final Type identifierType,
1721: final Object optionalObject,
1722: final String optionalEntityName,
1723: final Serializable optionalIdentifier,
1724: final EntityPersister persister) throws HibernateException {
1725:
1726: if (log.isDebugEnabled()) {
1727: log.debug("loading entity: "
1728: + MessageHelper.infoString(persister, id,
1729: identifierType, getFactory()));
1730: }
1731:
1732: List result;
1733: try {
1734: result = doQueryAndInitializeNonLazyCollections(session,
1735: new QueryParameters(new Type[] { identifierType },
1736: new Object[] { id }, optionalObject,
1737: optionalEntityName, optionalIdentifier),
1738: false);
1739: } catch (SQLException sqle) {
1740: final Loadable[] persisters = getEntityPersisters();
1741: throw JDBCExceptionHelper.convert(factory
1742: .getSQLExceptionConverter(), sqle,
1743: "could not load an entity: "
1744: + MessageHelper.infoString(
1745: persisters[persisters.length - 1],
1746: id, identifierType, getFactory()),
1747: getSQLString());
1748: }
1749:
1750: log.debug("done entity load");
1751:
1752: return result;
1753:
1754: }
1755:
1756: /**
1757: * Called by subclasses that load entities
1758: * @param persister only needed for logging
1759: */
1760: protected final List loadEntity(final SessionImplementor session,
1761: final Object key, final Object index, final Type keyType,
1762: final Type indexType, final EntityPersister persister)
1763: throws HibernateException {
1764:
1765: if (log.isDebugEnabled()) {
1766: log.debug("loading collection element by index");
1767: }
1768:
1769: List result;
1770: try {
1771: result = doQueryAndInitializeNonLazyCollections(session,
1772: new QueryParameters(
1773: new Type[] { keyType, indexType },
1774: new Object[] { key, index }), false);
1775: } catch (SQLException sqle) {
1776: throw JDBCExceptionHelper.convert(factory
1777: .getSQLExceptionConverter(), sqle,
1778: "could not collection element by index",
1779: getSQLString());
1780: }
1781:
1782: log.debug("done entity load");
1783:
1784: return result;
1785:
1786: }
1787:
1788: /**
1789: * Called by wrappers that batch load entities
1790: * @param persister only needed for logging
1791: */
1792: public final List loadEntityBatch(final SessionImplementor session,
1793: final Serializable[] ids, final Type idType,
1794: final Object optionalObject,
1795: final String optionalEntityName,
1796: final Serializable optionalId,
1797: final EntityPersister persister) throws HibernateException {
1798:
1799: if (log.isDebugEnabled()) {
1800: log.debug("batch loading entity: "
1801: + MessageHelper.infoString(persister, ids,
1802: getFactory()));
1803: }
1804:
1805: Type[] types = new Type[ids.length];
1806: Arrays.fill(types, idType);
1807: List result;
1808: try {
1809: result = doQueryAndInitializeNonLazyCollections(session,
1810: new QueryParameters(types, ids, optionalObject,
1811: optionalEntityName, optionalId), false);
1812: } catch (SQLException sqle) {
1813: throw JDBCExceptionHelper.convert(factory
1814: .getSQLExceptionConverter(), sqle,
1815: "could not load an entity batch: "
1816: + MessageHelper.infoString(
1817: getEntityPersisters()[0], ids,
1818: getFactory()), getSQLString());
1819: }
1820:
1821: log.debug("done entity batch load");
1822:
1823: return result;
1824:
1825: }
1826:
1827: /**
1828: * Called by subclasses that initialize collections
1829: */
1830: public final void loadCollection(final SessionImplementor session,
1831: final Serializable id, final Type type)
1832: throws HibernateException {
1833:
1834: if (log.isDebugEnabled()) {
1835: log.debug("loading collection: "
1836: + MessageHelper.collectionInfoString(
1837: getCollectionPersisters()[0], id,
1838: getFactory()));
1839: }
1840:
1841: Serializable[] ids = new Serializable[] { id };
1842: try {
1843: doQueryAndInitializeNonLazyCollections(session,
1844: new QueryParameters(new Type[] { type }, ids, ids),
1845: true);
1846: } catch (SQLException sqle) {
1847: throw JDBCExceptionHelper.convert(factory
1848: .getSQLExceptionConverter(), sqle,
1849: "could not initialize a collection: "
1850: + MessageHelper.collectionInfoString(
1851: getCollectionPersisters()[0], id,
1852: getFactory()), getSQLString());
1853: }
1854:
1855: log.debug("done loading collection");
1856:
1857: }
1858:
1859: /**
1860: * Called by wrappers that batch initialize collections
1861: */
1862: public final void loadCollectionBatch(
1863: final SessionImplementor session, final Serializable[] ids,
1864: final Type type) throws HibernateException {
1865:
1866: if (log.isDebugEnabled()) {
1867: log.debug("batch loading collection: "
1868: + MessageHelper.collectionInfoString(
1869: getCollectionPersisters()[0], ids,
1870: getFactory()));
1871: }
1872:
1873: Type[] idTypes = new Type[ids.length];
1874: Arrays.fill(idTypes, type);
1875: try {
1876: doQueryAndInitializeNonLazyCollections(session,
1877: new QueryParameters(idTypes, ids, ids), true);
1878: } catch (SQLException sqle) {
1879: throw JDBCExceptionHelper.convert(factory
1880: .getSQLExceptionConverter(), sqle,
1881: "could not initialize a collection batch: "
1882: + MessageHelper.collectionInfoString(
1883: getCollectionPersisters()[0], ids,
1884: getFactory()), getSQLString());
1885: }
1886:
1887: log.debug("done batch load");
1888:
1889: }
1890:
1891: /**
1892: * Called by subclasses that batch initialize collections
1893: */
1894: protected final void loadCollectionSubselect(
1895: final SessionImplementor session, final Serializable[] ids,
1896: final Object[] parameterValues,
1897: final Type[] parameterTypes, final Map namedParameters,
1898: final Type type) throws HibernateException {
1899:
1900: Type[] idTypes = new Type[ids.length];
1901: Arrays.fill(idTypes, type);
1902: try {
1903: doQueryAndInitializeNonLazyCollections(session,
1904: new QueryParameters(parameterTypes,
1905: parameterValues, namedParameters, ids),
1906: true);
1907: } catch (SQLException sqle) {
1908: throw JDBCExceptionHelper.convert(factory
1909: .getSQLExceptionConverter(), sqle,
1910: "could not load collection by subselect: "
1911: + MessageHelper.collectionInfoString(
1912: getCollectionPersisters()[0], ids,
1913: getFactory()), getSQLString());
1914: }
1915: }
1916:
1917: /**
1918: * Return the query results, using the query cache, called
1919: * by subclasses that implement cacheable queries
1920: */
1921: protected List list(final SessionImplementor session,
1922: final QueryParameters queryParameters,
1923: final Set querySpaces, final Type[] resultTypes)
1924: throws HibernateException {
1925:
1926: final boolean cacheable = factory.getSettings()
1927: .isQueryCacheEnabled()
1928: && queryParameters.isCacheable();
1929:
1930: if (cacheable) {
1931: return listUsingQueryCache(session, queryParameters,
1932: querySpaces, resultTypes);
1933: } else {
1934: return listIgnoreQueryCache(session, queryParameters);
1935: }
1936: }
1937:
1938: private List listIgnoreQueryCache(SessionImplementor session,
1939: QueryParameters queryParameters) {
1940: return getResultList(doList(session, queryParameters),
1941: queryParameters.getResultTransformer());
1942: }
1943:
1944: private List listUsingQueryCache(final SessionImplementor session,
1945: final QueryParameters queryParameters,
1946: final Set querySpaces, final Type[] resultTypes) {
1947:
1948: QueryCache queryCache = factory.getQueryCache(queryParameters
1949: .getCacheRegion());
1950:
1951: Set filterKeys = FilterKey.createFilterKeys(session
1952: .getEnabledFilters(), session.getEntityMode());
1953: QueryKey key = new QueryKey(getSQLString(), queryParameters,
1954: filterKeys, session.getEntityMode());
1955:
1956: List result = getResultFromQueryCache(session, queryParameters,
1957: querySpaces, resultTypes, queryCache, key);
1958:
1959: if (result == null) {
1960: result = doList(session, queryParameters);
1961:
1962: putResultInQueryCache(session, queryParameters,
1963: resultTypes, queryCache, key, result);
1964: }
1965:
1966: return getResultList(result, queryParameters
1967: .getResultTransformer());
1968: }
1969:
1970: private List getResultFromQueryCache(
1971: final SessionImplementor session,
1972: final QueryParameters queryParameters,
1973: final Set querySpaces, final Type[] resultTypes,
1974: final QueryCache queryCache, final QueryKey key) {
1975: List result = null;
1976:
1977: if (session.getCacheMode().isGetEnabled()) {
1978: result = queryCache.get(key, resultTypes, queryParameters
1979: .isNaturalKeyLookup(), querySpaces, session);
1980:
1981: if (factory.getStatistics().isStatisticsEnabled()) {
1982: if (result == null) {
1983: factory.getStatisticsImplementor().queryCacheMiss(
1984: getQueryIdentifier(),
1985: queryCache.getRegionName());
1986: } else {
1987: factory.getStatisticsImplementor().queryCacheHit(
1988: getQueryIdentifier(),
1989: queryCache.getRegionName());
1990: }
1991: }
1992:
1993: }
1994:
1995: return result;
1996: }
1997:
1998: private void putResultInQueryCache(
1999: final SessionImplementor session,
2000: final QueryParameters queryParameters,
2001: final Type[] resultTypes, final QueryCache queryCache,
2002: final QueryKey key, final List result) {
2003:
2004: if (session.getCacheMode().isPutEnabled()) {
2005: boolean put = queryCache.put(key, resultTypes, result,
2006: queryParameters.isNaturalKeyLookup(), session);
2007: if (put && factory.getStatistics().isStatisticsEnabled()) {
2008: factory.getStatisticsImplementor().queryCachePut(
2009: getQueryIdentifier(),
2010: queryCache.getRegionName());
2011: }
2012: }
2013: }
2014:
2015: /**
2016: * Actually execute a query, ignoring the query cache
2017: */
2018: protected List doList(final SessionImplementor session,
2019: final QueryParameters queryParameters)
2020: throws HibernateException {
2021:
2022: final boolean stats = getFactory().getStatistics()
2023: .isStatisticsEnabled();
2024: long startTime = 0;
2025: if (stats)
2026: startTime = System.currentTimeMillis();
2027:
2028: List result;
2029: try {
2030: result = doQueryAndInitializeNonLazyCollections(session,
2031: queryParameters, true);
2032: } catch (SQLException sqle) {
2033: throw JDBCExceptionHelper.convert(factory
2034: .getSQLExceptionConverter(), sqle,
2035: "could not execute query", getSQLString());
2036: }
2037:
2038: if (stats) {
2039: getFactory().getStatisticsImplementor().queryExecuted(
2040: getQueryIdentifier(), result.size(),
2041: System.currentTimeMillis() - startTime);
2042: }
2043:
2044: return result;
2045: }
2046:
2047: /**
2048: * Check whether the current loader can support returning ScrollableResults.
2049: *
2050: * @throws HibernateException
2051: */
2052: protected void checkScrollability() throws HibernateException {
2053: // Allows various loaders (ok mainly the QueryLoader :) to check
2054: // whether scrolling of their result set should be allowed.
2055: //
2056: // By default it is allowed.
2057: return;
2058: }
2059:
2060: /**
2061: * Does the result set to be scrolled contain collection fetches?
2062: *
2063: * @return True if it does, and thus needs the special fetching scroll
2064: * functionality; false otherwise.
2065: */
2066: protected boolean needsFetchingScroll() {
2067: return false;
2068: }
2069:
2070: /**
2071: * Return the query results, as an instance of <tt>ScrollableResults</tt>
2072: *
2073: * @param queryParameters The parameters with which the query should be executed.
2074: * @param returnTypes The expected return types of the query
2075: * @param holderInstantiator If the return values are expected to be wrapped
2076: * in a holder, this is the thing that knows how to wrap them.
2077: * @param session The session from which the scroll request originated.
2078: * @return The ScrollableResults instance.
2079: * @throws HibernateException Indicates an error executing the query, or constructing
2080: * the ScrollableResults.
2081: */
2082: protected ScrollableResults scroll(
2083: final QueryParameters queryParameters,
2084: final Type[] returnTypes,
2085: final HolderInstantiator holderInstantiator,
2086: final SessionImplementor session) throws HibernateException {
2087:
2088: checkScrollability();
2089:
2090: final boolean stats = getQueryIdentifier() != null
2091: && getFactory().getStatistics().isStatisticsEnabled();
2092: long startTime = 0;
2093: if (stats)
2094: startTime = System.currentTimeMillis();
2095:
2096: try {
2097:
2098: PreparedStatement st = prepareQueryStatement(
2099: queryParameters, true, session);
2100: ResultSet rs = getResultSet(st, queryParameters
2101: .hasAutoDiscoverScalarTypes(), queryParameters
2102: .isCallable(), queryParameters.getRowSelection(),
2103: session);
2104:
2105: if (stats) {
2106: getFactory().getStatisticsImplementor().queryExecuted(
2107: getQueryIdentifier(), 0,
2108: System.currentTimeMillis() - startTime);
2109: }
2110:
2111: if (needsFetchingScroll()) {
2112: return new FetchingScrollableResultsImpl(rs, st,
2113: session, this , queryParameters, returnTypes,
2114: holderInstantiator);
2115: } else {
2116: return new ScrollableResultsImpl(rs, st, session, this ,
2117: queryParameters, returnTypes,
2118: holderInstantiator);
2119: }
2120:
2121: } catch (SQLException sqle) {
2122: throw JDBCExceptionHelper.convert(factory
2123: .getSQLExceptionConverter(), sqle,
2124: "could not execute query using scroll",
2125: getSQLString());
2126: }
2127:
2128: }
2129:
2130: /**
2131: * Calculate and cache select-clause suffixes. Must be
2132: * called by subclasses after instantiation.
2133: */
2134: protected void postInstantiate() {
2135: }
2136:
2137: /**
2138: * Get the result set descriptor
2139: */
2140: protected abstract EntityAliases[] getEntityAliases();
2141:
2142: protected abstract CollectionAliases[] getCollectionAliases();
2143:
2144: /**
2145: * Identifies the query for statistics reporting, if null,
2146: * no statistics will be reported
2147: */
2148: protected String getQueryIdentifier() {
2149: return null;
2150: }
2151:
2152: public final SessionFactoryImplementor getFactory() {
2153: return factory;
2154: }
2155:
2156: public String toString() {
2157: return getClass().getName() + '(' + getSQLString() + ')';
2158: }
2159:
2160: }
|