| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| // 04/01/2011-2.3 Guy Pelletier |
| // - 337323: Multi-tenant with shared schema support (part 2) |
| // 09/09/2011-2.3.1 Guy Pelletier |
| // - 356197: Add new VPD type to MultitenantType |
| package org.eclipse.persistence.queries; |
| |
| import java.sql.ResultSet; |
| import java.sql.ResultSetMetaData; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Vector; |
| |
| import org.eclipse.persistence.exceptions.DatabaseException; |
| import org.eclipse.persistence.exceptions.QueryException; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.expressions.ExpressionBuilder; |
| import org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor; |
| import org.eclipse.persistence.internal.databaseaccess.DatabaseCall; |
| import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform; |
| import org.eclipse.persistence.internal.descriptors.ObjectBuilder; |
| import org.eclipse.persistence.internal.expressions.QueryKeyExpression; |
| import org.eclipse.persistence.internal.helper.ClassConstants; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.helper.InvalidObject; |
| import org.eclipse.persistence.internal.helper.ThreadCursoredList; |
| import org.eclipse.persistence.internal.queries.ContainerPolicy; |
| import org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.ResultSetRecord; |
| import org.eclipse.persistence.internal.sessions.SimpleResultSetRecord; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.internal.sessions.remote.RemoteSessionController; |
| import org.eclipse.persistence.internal.sessions.remote.Transporter; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.mappings.OneToManyMapping; |
| import org.eclipse.persistence.sessions.DatabaseRecord; |
| import org.eclipse.persistence.sessions.SessionProfiler; |
| import org.eclipse.persistence.sessions.remote.DistributedSession; |
| import org.eclipse.persistence.tools.profiler.QueryMonitor; |
| |
| /** |
| * <p><b>Purpose</b>: |
| * Concrete class for all read queries involving a collection of objects. |
| * |
| * <p><b>Responsibilities</b>: |
| * Return a container of the objects generated by the query. |
| * Implements the inheritance feature when dealing with abstract descriptors |
| * |
| * @author Yvon Lavoie |
| * @since TOPLink/Java 1.0 |
| */ |
| public class ReadAllQuery extends ObjectLevelReadQuery { |
| /** Used for collection and stream support. */ |
| protected ContainerPolicy containerPolicy; |
| |
| /** Used for Oracle HierarchicalQuery support */ |
| protected Expression startWithExpression; |
| protected Expression connectByExpression; |
| protected List<Expression> orderSiblingsByExpressions; |
| protected Direction direction; |
| |
| /** |
| * Specifies the direction in which the hierarchy is traversed in a |
| * hierarchical query. |
| */ |
| public static enum Direction { |
| /** |
| * Hierarchy will be traversed from parent to child - PRIOR keyword is |
| * generated on the left side of the equation |
| */ |
| PARENT_TO_CHILD, |
| /** |
| * Hierarchy will be traversed from child to parent - PRIOR keyword is |
| * generated on the right side of the equation |
| */ |
| CHILD_TO_PARENT; |
| |
| /** |
| * PUBLIC: Returns the default hierarchy traversal direction for the |
| * specified mapping.<br> |
| * For OneToOne mappings, source in parent object goes to target in |
| * child object, collections are the opposite way. |
| * |
| * @param mapping |
| * The mapping for which to return default hierarchy |
| * traversal direction |
| * @return The default hierarchy traversal direction for the mapping |
| * passed |
| */ |
| public static Direction getDefault(DatabaseMapping mapping) { |
| return mapping != null && mapping.isOneToOneMapping() ? CHILD_TO_PARENT : PARENT_TO_CHILD; |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a new read all query. |
| * A reference class must be specified before execution. |
| * It is better to provide the class and expression builder on construction to ensure a single expression builder is used. |
| * If no selection criteria is specified this will read all objects of the class from the database. |
| */ |
| public ReadAllQuery() { |
| super(); |
| setContainerPolicy(ContainerPolicy.buildDefaultPolicy()); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a new read all query. |
| * It is better to provide the class and expression builder on construction to ensure a single expression builder is used. |
| * If no selection criteria is specified this will read all objects of the class from the database. |
| */ |
| public ReadAllQuery(Class classToRead) { |
| this(); |
| this.referenceClass = classToRead; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a new read all query for the class and the selection criteria. |
| */ |
| public ReadAllQuery(Class classToRead, Expression selectionCriteria) { |
| this(); |
| this.referenceClass = classToRead; |
| setSelectionCriteria(selectionCriteria); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a new read all query for the class. |
| * The expression builder must be used for all associated expressions used with the query. |
| */ |
| public ReadAllQuery(Class classToRead, ExpressionBuilder builder) { |
| this(); |
| this.defaultBuilder = builder; |
| this.referenceClass = classToRead; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a new read all query. |
| * The call represents a database interaction such as SQL, Stored Procedure. |
| */ |
| public ReadAllQuery(Class classToRead, Call call) { |
| this(); |
| this.referenceClass = classToRead; |
| setCall(call); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a query by example query to find all objects matching the attributes of the example object. |
| */ |
| public ReadAllQuery(Object exampleObject, QueryByExamplePolicy policy) { |
| this(); |
| setExampleObject(exampleObject); |
| setQueryByExamplePolicy(policy); |
| } |
| |
| /** |
| * PUBLIC: |
| * The expression builder should be provide on creation to ensure only one is used. |
| */ |
| public ReadAllQuery(ExpressionBuilder builder) { |
| this(); |
| this.defaultBuilder = builder; |
| } |
| |
| /** |
| * PUBLIC: |
| * Create a read all query with the database call. |
| */ |
| public ReadAllQuery(Call call) { |
| this(); |
| setCall(call); |
| } |
| |
| /** |
| * PUBLIC: |
| * Order the query results by the object's attribute or query key name. |
| */ |
| public void addAscendingOrdering(String queryKeyName) { |
| addOrdering(getExpressionBuilder().get(queryKeyName).ascending()); |
| } |
| |
| /** |
| * INTERNAL: |
| * <P> This method is called by the object builder when building an original. |
| * It will cause the original to be cached in the query results if the query |
| * is set to do so. |
| */ |
| @Override |
| public void cacheResult(Object unwrappedOriginal) { |
| Collection container = (Collection)getTemporaryCachedQueryResults(); |
| if (container == null) { |
| container = (Collection)getContainerPolicy().containerInstance(); |
| setTemporaryCachedQueryResults(container); |
| } |
| getContainerPolicy().addInto(unwrappedOriginal, container, getSession()); |
| } |
| |
| /** |
| * INTERNAL: |
| * The cache check is done before the prepare as a hit will not require the work to be done. |
| */ |
| @Override |
| protected Object checkEarlyReturnLocal(AbstractSession session, AbstractRecord translationRow) { |
| // Check for in-memory only query. |
| if (shouldCheckCacheOnly()) { |
| // assert !isReportQuery(); |
| if (shouldUseWrapperPolicy()) { |
| getContainerPolicy().setElementDescriptor(this.descriptor); |
| } |
| |
| // PERF: Fixed to not query each unit of work cache (is not conforming), |
| // avoid hashtable and primary key indexing. |
| // At some point we may need to support some kind of in-memory with conforming option, |
| // but we do not currently allow this. |
| AbstractSession rootSession = session; |
| while (rootSession.isUnitOfWork()) { |
| rootSession = rootSession.getParent(); |
| } |
| Vector allCachedVector = rootSession.getIdentityMapAccessor().getAllFromIdentityMap(getSelectionCriteria(), getReferenceClass(), translationRow, getInMemoryQueryIndirectionPolicyState(), false); |
| |
| // Must ensure that all of the objects returned are correctly registered in the unit of work. |
| if (session.isUnitOfWork()) { |
| allCachedVector = ((UnitOfWorkImpl)session).registerAllObjects(allCachedVector); |
| } |
| |
| this.isCacheCheckComplete = true; |
| |
| return getContainerPolicy().buildContainerFromVector(allCachedVector, session); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Check and return custom query flag. Custom query flag value is initialized when stored value is {@code null}. |
| * Called from {@link #checkForCustomQuery(AbstractSession, AbstractRecord)} to retrieve custom query flag. |
| * @param session Current session (not used). |
| * @param translationRow Database record (not used). |
| * @return Current custom query flag. Value will never be {@code null}. |
| */ |
| @Override |
| protected Boolean checkCustomQueryFlag(final AbstractSession session, final AbstractRecord translationRow) { |
| // #436871 - Use local copy to avoid NPE from concurrent modification. |
| final Boolean useCustomQuery = isCustomQueryUsed; |
| if (useCustomQuery != null) { |
| return useCustomQuery; |
| // Initialize custom query flag. |
| } else { |
| final boolean useCustomQueryValue = |
| !isUserDefined() && isExpressionQuery() && getSelectionCriteria() == null |
| && isDefaultPropertiesQuery() && (!hasOrderByExpressions()) |
| && descriptor.getQueryManager().hasReadAllQuery(); |
| setIsCustomQueryUsed(useCustomQueryValue); |
| return useCustomQueryValue; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Get custom all read query from query manager. |
| * Called from {@link #checkForCustomQuery(AbstractSession, AbstractRecord)} to retrieve custom read query. |
| * @return Custom all read query from query manager. |
| */ |
| @Override |
| protected ObjectLevelReadQuery getReadQuery() { |
| return descriptor.getQueryManager().getReadAllQuery(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Creates and returns a copy of this query. |
| * @return A clone of this instance. |
| */ |
| @Override |
| public Object clone() { |
| final ReadAllQuery cloneQuery = (ReadAllQuery)super.clone(); |
| // Don't use setters as that will trigger unprepare. |
| cloneQuery.containerPolicy = containerPolicy.clone(cloneQuery); |
| return cloneQuery; |
| } |
| |
| /** |
| * INTERNAL: |
| * Conform the result if specified. |
| */ |
| protected Object conformResult(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord arguments, boolean buildDirectlyFromRows) { |
| Expression selectionCriteria = getSelectionCriteria(); |
| if (selectionCriteria != null) { |
| ExpressionBuilder builder = getSelectionCriteria().getBuilder(); |
| builder.setSession(unitOfWork.getRootSession(null)); |
| builder.setQueryClass(getReferenceClass()); |
| if (getQueryMechanism().isExpressionQueryMechanism() && selectionCriteria.isLogicalExpression()) { |
| // bug #526546 |
| if (builder.derivedExpressions != null) { |
| for (Expression e : builder.derivedExpressions) { |
| if (e.isQueryKeyExpression() && ((QueryKeyExpression) e).shouldQueryToManyRelationship()) { |
| DatabaseMapping mapping = ((QueryKeyExpression) e).getMapping(); |
| if (mapping.isOneToManyMapping()) { |
| OneToManyMapping otm = (OneToManyMapping) mapping; |
| Expression join = otm.buildSelectionCriteria(); |
| selectionCriteria = selectionCriteria.and(join); |
| } |
| } |
| } |
| } |
| } |
| |
| } |
| |
| // If the query is redirected then the collection returned might no longer |
| // correspond to the original container policy. CR#2342-S.M. |
| ContainerPolicy cp; |
| if (getRedirector() != null) { |
| cp = ContainerPolicy.buildPolicyFor(result.getClass()); |
| } else { |
| cp = getContainerPolicy(); |
| } |
| |
| // This code is now a great deal different... For one, registration is done |
| // as part of conforming. Also, this should only be called if one actually |
| // is conforming. |
| // First scan the UnitOfWork for conforming instances. |
| // This will walk through the entire cache of registered objects. |
| // Let p be objects from result not in the cache. |
| // Let c be objects from cache. |
| // Presently p intersect c = empty set, but later p subset c. |
| // By checking cache now doesConform will be called p fewer times. |
| Map<Object, Object> indexedInterimResult = unitOfWork.scanForConformingInstances(selectionCriteria, getReferenceClass(), arguments, this); |
| |
| Cursor cursor = null; |
| // In the case of cursors just conform/register the initially read collection. |
| if (cp.isCursorPolicy()) { |
| cursor = (Cursor)result; |
| cp = ContainerPolicy.buildPolicyFor(ClassConstants.Vector_class); |
| // In nested UnitOfWork session might have been session of the parent. |
| cursor.setSession(unitOfWork); |
| result = cursor.getObjectCollection(); |
| // for later incremental conforming... |
| cursor.setInitiallyConformingIndex(indexedInterimResult); |
| cursor.setSelectionCriteriaClone(getSelectionCriteria()); |
| cursor.setTranslationRow(arguments); |
| } |
| |
| // Now conform the result from the database. |
| // Remove any deleted or changed objects that no longer conform. |
| // Deletes will only work for simple queries, queries with or's or anyof's may not return |
| // correct results when untriggered indirection is in the model. |
| List fromDatabase = null; |
| |
| // When building directly from rows, one of the performance benefits |
| // is that we no longer have to wrap and then unwrap the originals. |
| // result is just a vector, not a container of wrapped originals. |
| if (buildDirectlyFromRows) { |
| List<AbstractRecord> rows = (List<AbstractRecord>)result; |
| int size = rows.size(); |
| fromDatabase = new ArrayList(size); |
| for (int index = 0; index < size; index++) { |
| AbstractRecord row = rows.get(index); |
| // null is placed in the row collection for 1-m joining to filter duplicate rows. |
| if (row != null) { |
| Object clone = conformIndividualResult(buildObject(row), unitOfWork, arguments, getSelectionCriteria(), indexedInterimResult); |
| if (clone != null) { |
| fromDatabase.add(clone); |
| } |
| } |
| } |
| } else { |
| fromDatabase = new ArrayList(cp.sizeFor(result)); |
| AbstractSession sessionToUse = unitOfWork.getParent(); |
| for (Object iter = cp.iteratorFor(result); cp.hasNext(iter);) { |
| Object object = cp.next(iter, sessionToUse); |
| Object clone = conformIndividualResult(registerIndividualResult(object, null, unitOfWork, null, null), unitOfWork, arguments, getSelectionCriteria(), indexedInterimResult); |
| if (clone != null) { |
| fromDatabase.add(clone); |
| } |
| } |
| } |
| |
| // Now add the unwrapped conforming instances into an appropriate container. |
| // Wrapping is done automatically. |
| // Make sure a vector of exactly the right size is returned. |
| Object conformedResult = cp.containerInstance(indexedInterimResult.size() + fromDatabase.size()); |
| for (Iterator enumtr = indexedInterimResult.values().iterator(); enumtr.hasNext();) { |
| Object eachClone = enumtr.next(); |
| cp.addInto(eachClone, conformedResult, unitOfWork); |
| } |
| int size = fromDatabase.size(); |
| for (int index = 0; index < size; index++) { |
| Object eachClone = fromDatabase.get(index); |
| cp.addInto(eachClone, conformedResult, unitOfWork); |
| } |
| |
| if (cursor != null) { |
| cursor.setObjectCollection((List)conformedResult); |
| |
| // For nested UOW must copy all in object collection to |
| // initiallyConformingIndex, as some of these could have been from |
| // the parent UnitOfWork. |
| if (unitOfWork.isNestedUnitOfWork()) { |
| for (Object clone : cursor.getObjectCollection()) { |
| indexedInterimResult.put(clone, clone); |
| } |
| } |
| return cursor; |
| } else { |
| return conformedResult; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Execute the query. If there are cached results return those. |
| * This must override the super to support result caching. |
| * |
| * @param session - the session in which the receiver will be executed. |
| * @return An object or vector, the result of executing the query. |
| * @exception DatabaseException - an error has occurred on the database |
| */ |
| @Override |
| public Object execute(AbstractSession session, AbstractRecord row) throws DatabaseException { |
| if (shouldCacheQueryResults()) { |
| if (getContainerPolicy().overridesRead()) { |
| throw QueryException.cannotCacheCursorResultsOnQuery(this); |
| } |
| if (shouldConformResultsInUnitOfWork()) { |
| throw QueryException.cannotConformAndCacheQueryResults(this); |
| } |
| if (isPrepared()) {// only prepared queries can have cached results. |
| Object queryResults = getQueryResults(session, row, true); |
| if (queryResults != null) { |
| if (QueryMonitor.shouldMonitor()) { |
| QueryMonitor.incrementReadAllHits(this); |
| } |
| session.incrementProfile(SessionProfiler.CacheHits, this); |
| // bug6138532 - check for "cached no results" (InvalidObject singleton) in query |
| // results, and return an empty container instance as configured |
| if (queryResults == InvalidObject.instance) { |
| return getContainerPolicy().containerInstance(0); |
| } |
| Collection results = (Collection)queryResults; |
| if (session.isUnitOfWork()) { |
| ContainerPolicy policy = getContainerPolicy(); |
| Object resultCollection = policy.containerInstance(results.size()); |
| Object iterator = policy.iteratorFor(results); |
| while (policy.hasNext(iterator)) { |
| Object result = ((UnitOfWorkImpl)session).registerExistingObject(policy.next(iterator, session), this.descriptor, null, true); |
| policy.addInto(result, resultCollection, session); |
| } |
| return resultCollection; |
| } |
| return results; |
| } |
| } |
| session.incrementProfile(SessionProfiler.CacheMisses, this); |
| } |
| if (QueryMonitor.shouldMonitor()) { |
| QueryMonitor.incrementReadAllMisses(this); |
| } |
| return super.execute(session, row); |
| } |
| |
| /** |
| * INTERNAL: |
| * Execute the query. |
| * Get the rows and build the object from the rows. |
| * @exception DatabaseException - an error has occurred on the database |
| * @return java.lang.Object collection of objects resulting from execution of query. |
| */ |
| @Override |
| protected Object executeObjectLevelReadQuery() throws DatabaseException { |
| Object result = null; |
| |
| if (this.containerPolicy.overridesRead()) { |
| this.executionTime = System.currentTimeMillis(); |
| return this.containerPolicy.execute(); |
| } |
| |
| if (this.descriptor.isDescriptorForInterface()) { |
| Object returnValue = this.descriptor.getInterfacePolicy().selectAllObjectsUsingMultipleTableSubclassRead(this); |
| this.executionTime = System.currentTimeMillis(); |
| return returnValue; |
| } |
| |
| if (this.descriptor.hasTablePerClassPolicy() && this.descriptor.isAbstract()) { |
| result = this.containerPolicy.containerInstance(); |
| |
| if (this.shouldIncludeData) { |
| ComplexQueryResult complexResult = new ComplexQueryResult(); |
| complexResult.setResult(result); |
| complexResult.setData(new ArrayList()); |
| result = complexResult; |
| } |
| } else { |
| Object sopObject = getTranslationRow().getSopObject(); |
| boolean useOptimization = false; |
| if (sopObject == null) { |
| useOptimization = usesResultSetAccessOptimization(); |
| } |
| |
| if (useOptimization) { |
| DatabaseCall call = ((DatasourceCallQueryMechanism)this.queryMechanism).selectResultSet(); |
| this.executionTime = System.currentTimeMillis(); |
| Statement statement = call.getStatement(); |
| ResultSet resultSet = call.getResult(); |
| DatabaseAccessor dbAccessor = (DatabaseAccessor)getAccessor(); |
| boolean exceptionOccured = false; |
| try { |
| if (this.session.isUnitOfWork()) { |
| result = registerResultSetInUnitOfWork(resultSet, call.getFields(), call.getFieldsArray(), (UnitOfWorkImpl)this.session, this.translationRow); |
| } else { |
| result = this.containerPolicy.containerInstance(); |
| this.descriptor.getObjectBuilder().buildObjectsFromResultSetInto(this, resultSet, call.getFields(), call.getFieldsArray(), result); |
| } |
| } catch (SQLException exception) { |
| exceptionOccured = true; |
| DatabaseException commException = dbAccessor.processExceptionForCommError(this.session, exception, call); |
| if (commException != null) { |
| throw commException; |
| } |
| throw DatabaseException.sqlException(exception, call, dbAccessor, this.session, false); |
| } finally { |
| try { |
| if (resultSet != null) { |
| resultSet.close(); |
| } |
| if (dbAccessor != null) { |
| if (statement != null) { |
| dbAccessor.releaseStatement(statement, call.getSQLString(), call, this.session); |
| } |
| } |
| if (call.hasAllocatedConnection()) { |
| getExecutionSession().releaseConnectionAfterCall(this); |
| } |
| } catch (RuntimeException cleanupException) { |
| if (!exceptionOccured) { |
| throw cleanupException; |
| } |
| } catch (SQLException cleanupSQLException) { |
| if (!exceptionOccured) { |
| throw DatabaseException.sqlException(cleanupSQLException, call, dbAccessor, this.session, false); |
| } |
| } |
| } |
| } else { |
| List<AbstractRecord> rows; |
| if (sopObject != null) { |
| Object valuesIterator = this.containerPolicy.iteratorFor(getTranslationRow().getSopObject()); |
| int size = this.containerPolicy.sizeFor(sopObject); |
| rows = new ArrayList<>(size); |
| while (this.containerPolicy.hasNext(valuesIterator)) { |
| Object memberSopObject = this.containerPolicy.next(valuesIterator, this.session); |
| DatabaseRecord memberRow = new DatabaseRecord(0); |
| memberRow.setSopObject(memberSopObject); |
| rows.add(memberRow); |
| } |
| this.executionTime = System.currentTimeMillis(); |
| } else { |
| rows = getQueryMechanism().selectAllRows(); |
| this.executionTime = System.currentTimeMillis(); |
| |
| // If using 1-m joins, must set all rows. |
| if (hasJoining() && this.joinedAttributeManager.isToManyJoin()) { |
| this.joinedAttributeManager.setDataResults(rows, this.session); |
| } |
| // Batch fetching in IN requires access to the rows to build the id array. |
| if ((this.batchFetchPolicy != null) && this.batchFetchPolicy.isIN()) { |
| this.batchFetchPolicy.setDataResults(rows); |
| } |
| } |
| |
| if (this.session.isUnitOfWork()) { |
| result = registerResultInUnitOfWork(rows, (UnitOfWorkImpl)this.session, this.translationRow, true);// |
| } else { |
| if (rows instanceof ThreadCursoredList) { |
| result = this.containerPolicy.containerInstance(); |
| } else { |
| result = this.containerPolicy.containerInstance(rows.size()); |
| } |
| this.descriptor.getObjectBuilder().buildObjectsInto(this, rows, result); |
| } |
| |
| if (sopObject != null) { |
| if (!this.descriptor.getObjectBuilder().isSimple()) { |
| // remove sopObject so it's not stuck in any value holder. |
| for (AbstractRecord row : rows) { |
| row.setSopObject(null); |
| } |
| } |
| } else { |
| if (this.shouldIncludeData) { |
| ComplexQueryResult complexResult = new ComplexQueryResult(); |
| complexResult.setResult(result); |
| complexResult.setData(rows); |
| result = complexResult; |
| } |
| } |
| } |
| } |
| |
| // Add the other (already registered) results and return them. |
| if (this.descriptor.hasTablePerClassPolicy()) { |
| result = this.containerPolicy.concatenateContainers( |
| result, this.descriptor.getTablePerClassPolicy().selectAllObjectsUsingMultipleTableSubclassRead(this), this.session); |
| } |
| |
| // If the results were empty, then ensure they get cached still. |
| if (shouldCacheQueryResults() && this.containerPolicy.isEmpty(result)) { |
| this.temporaryCachedQueryResults = InvalidObject.instance(); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * INTERNAL: |
| * Execute the query building the objects directly from the database result-set. |
| * @exception DatabaseException - an error has occurred on the database |
| * @return an ArrayList of the resulting objects. |
| */ |
| @Override |
| protected Object executeObjectLevelReadQueryFromResultSet() throws DatabaseException { |
| AbstractSession session = this.session; |
| DatabasePlatform platform = session.getPlatform(); |
| DatabaseCall call = ((DatasourceCallQueryMechanism)this.queryMechanism).selectResultSet(); |
| Statement statement = call.getStatement(); |
| ResultSet resultSet = call.getResult(); |
| DatabaseAccessor accessor = (DatabaseAccessor)getAccessor(); |
| boolean exceptionOccured = false; |
| try { |
| ResultSetMetaData metaData = resultSet.getMetaData(); |
| List results = new ArrayList(); |
| ObjectBuilder builder = this.descriptor.getObjectBuilder(); |
| while (resultSet.next()) { |
| results.add(builder.buildObjectFromResultSet(this, this.joinedAttributeManager, resultSet, session, accessor, metaData, platform, call.getFields(), call.getFieldsArray())); |
| } |
| return results; |
| } catch (SQLException exception) { |
| exceptionOccured = true; |
| DatabaseException commException = accessor.processExceptionForCommError(session, exception, call); |
| if (commException != null) { |
| throw commException; |
| } |
| throw DatabaseException.sqlException(exception, call, accessor, session, false); |
| } finally { |
| try { |
| if (resultSet != null) { |
| resultSet.close(); |
| } |
| if (statement != null) { |
| accessor.releaseStatement(statement, call.getSQLString(), call, session); |
| } |
| if (accessor != null) { |
| session.releaseReadConnection(accessor); |
| } |
| } catch (SQLException exception) { |
| if (!exceptionOccured) { |
| //in the case of an external connection pool the connection may be null after the statement release |
| // if it is null we will be unable to check the connection for a comm error and |
| //therefore must return as if it was not a comm error. |
| DatabaseException commException = accessor.processExceptionForCommError(session, exception, call); |
| if (commException != null) { |
| throw commException; |
| } |
| throw DatabaseException.sqlException(exception, call, accessor, session, false); |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Extract the correct query result from the transporter. |
| */ |
| @Override |
| public Object extractRemoteResult(Transporter transporter) { |
| return ((DistributedSession)getSession()).getObjectsCorrespondingToAll(transporter.getObject(), transporter.getObjectDescriptors(), new IdentityHashMap(), this, getContainerPolicy()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the query's container policy. |
| */ |
| public ContainerPolicy getContainerPolicy() { |
| return containerPolicy; |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the specific default redirector for this query type. There are numerous default query redirectors. |
| * See ClassDescriptor for their types. |
| */ |
| @Override |
| protected QueryRedirector getDefaultRedirector() { |
| return descriptor.getDefaultReadAllQueryRedirector(); |
| } |
| |
| /** |
| * PUBLIC: |
| * @return Expression - the start with expression used to generated the hierarchical query clause in |
| * Oracle |
| */ |
| public Expression getStartWithExpression() { |
| return startWithExpression; |
| } |
| |
| /** |
| * PUBLIC: |
| * @return Expression - the connect by expression used to generate the hierarchical query caluse in Oracle |
| */ |
| public Expression getConnectByExpression() { |
| return connectByExpression; |
| } |
| |
| /** |
| * PUBLIC: |
| * @return {@literal List<Expression>} - the ordering expressions used to generate the hierarchical query clause in Oracle |
| */ |
| public List<Expression> getOrderSiblingsByExpressions() { |
| return orderSiblingsByExpressions; |
| } |
| |
| /** |
| * PUBLIC: |
| * @return Direction - the direction in which the hierarchy is traversed |
| */ |
| public Direction getDirection() { |
| return direction; |
| } |
| |
| /** |
| * INTERNAL: |
| * Verify that we have hierarchical query expressions |
| */ |
| public boolean hasHierarchicalExpressions() { |
| return ((this.startWithExpression != null) || (this.connectByExpression != null) || (this.orderSiblingsByExpressions != null)); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if the query uses default properties. |
| * This is used to determine if this query is cacheable. |
| * i.e. does not use any properties that may conflict with another query |
| * with the same JPQL or selection criteria. |
| */ |
| @Override |
| public boolean isDefaultPropertiesQuery() { |
| return super.isDefaultPropertiesQuery() |
| && (!hasBatchReadAttributes()) |
| && (!hasHierarchicalExpressions()) |
| && (!this.containerPolicy.isCursorPolicy()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the query is equal to the other. |
| * This is used to allow dynamic expression query SQL to be cached. |
| */ |
| @Override |
| public boolean equals(Object object) { |
| if (this == object) { |
| return true; |
| } |
| if (!super.equals(object)) { |
| return false; |
| } |
| ReadAllQuery query = (ReadAllQuery) object; |
| if (!this.containerPolicy.equals(query.containerPolicy)) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return if this is a read all query. |
| */ |
| @Override |
| public boolean isReadAllQuery() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare the receiver for execution in a session. |
| */ |
| @Override |
| protected void prepare() throws QueryException { |
| if ((!isReportQuery()) && prepareFromCachedQuery()) { |
| return; |
| } |
| super.prepare(); |
| |
| this.containerPolicy.prepare(this, getSession()); |
| |
| if (hasJoining() && isExpressionQuery()) { |
| // 1-m join fetching with pagination requires an order by. |
| if (this.joinedAttributeManager.isToManyJoin() |
| && ((this.maxRows > 0) || (this.firstResult > 0) || this.containerPolicy.isCursorPolicy())) { |
| if (!hasOrderByExpressions()) { |
| for (DatabaseField primaryKey : this.descriptor.getPrimaryKeyFields()) { |
| addOrdering(getExpressionBuilder().getField(primaryKey)); |
| } |
| } |
| } |
| } |
| |
| if (this.containerPolicy.overridesRead()) { |
| return; |
| } |
| |
| if (this.descriptor.isDescriptorForInterface()) { |
| return; |
| } |
| |
| prepareSelectAllRows(); |
| |
| if (!isReportQuery()) { |
| // should be called after prepareSelectRow so that the call knows whether it returns ResultSet |
| prepareResultSetAccessOptimization(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare the query from the prepared query. |
| * This allows a dynamic query to prepare itself directly from a prepared query instance. |
| * This is used in the JPQL parse cache to allow preparsed queries to be used to prepare |
| * dynamic queries. |
| * This only copies over properties that are configured through JPQL. |
| */ |
| @Override |
| public void prepareFromQuery(DatabaseQuery query) { |
| super.prepareFromQuery(query); |
| if (query.isReadAllQuery()) { |
| ReadAllQuery readQuery = (ReadAllQuery)query; |
| this.containerPolicy = readQuery.containerPolicy; |
| if (readQuery.hasHierarchicalExpressions()) { |
| this.orderSiblingsByExpressions = readQuery.orderSiblingsByExpressions; |
| this.connectByExpression = readQuery.connectByExpression; |
| this.startWithExpression = readQuery.startWithExpression; |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the properties needed to be cascaded into the custom query. |
| */ |
| @Override |
| protected void prepareCustomQuery(DatabaseQuery customQuery) { |
| super.prepareCustomQuery(customQuery); |
| ReadAllQuery customReadQuery = (ReadAllQuery)customQuery; |
| customReadQuery.containerPolicy = this.containerPolicy; |
| customReadQuery.cascadePolicy = this.cascadePolicy; |
| customReadQuery.shouldRefreshIdentityMapResult = this.shouldRefreshIdentityMapResult; |
| customReadQuery.shouldMaintainCache = this.shouldMaintainCache; |
| customReadQuery.shouldUseWrapperPolicy = this.shouldUseWrapperPolicy; |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare the receiver for execution in a session. |
| */ |
| @Override |
| public void prepareForExecution() throws QueryException { |
| super.prepareForExecution(); |
| |
| this.containerPolicy.prepareForExecution(); |
| |
| // Modifying the translation row here will modify it on the original |
| // query which is not good. So we have to clone the translation row if |
| // we are going to append tenant discriminator fields to it. |
| if (descriptor.hasMultitenantPolicy()) { |
| translationRow = translationRow.clone(); |
| descriptor.getMultitenantPolicy().addFieldsToRow(translationRow, getSession()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare the mechanism. |
| */ |
| protected void prepareSelectAllRows() { |
| getQueryMechanism().prepareSelectAllRows(); |
| } |
| |
| /** |
| * INTERNAL: |
| * All objects queried via a UnitOfWork get registered here. If the query |
| * went to the database. |
| * <p> |
| * Involves registering the query result individually and in totality, and |
| * hence refreshing / conforming is done here. |
| * |
| * @param result may be collection (read all) or an object (read one), |
| * or even a cursor. If in transaction the shared cache will |
| * be bypassed, meaning the result may not be originals from the parent |
| * but raw database rows. |
| * @param unitOfWork the unitOfWork the result is being registered in. |
| * @param arguments the original arguments/parameters passed to the query |
| * execution. Used by conforming |
| * @param buildDirectlyFromRows If in transaction must construct |
| * a registered result from raw database rows. |
| * |
| * @return the final (conformed, refreshed, wrapped) UnitOfWork query result |
| */ |
| @Override |
| public Object registerResultInUnitOfWork(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord arguments, boolean buildDirectlyFromRows) { |
| // For bug 2612366: Conforming results in UOW extremely slow. |
| // Replacing results with registered versions in the UOW is a part of |
| // conforming and is now done while conforming to maximize performance. |
| if (unitOfWork.hasCloneMapping() // PERF: Avoid conforming empty uow. |
| && (shouldConformResultsInUnitOfWork() || this.descriptor.shouldAlwaysConformResultsInUnitOfWork())) { |
| return conformResult(result, unitOfWork, arguments, buildDirectlyFromRows); |
| } |
| |
| // When building directly from rows, one of the performance benefits |
| // is that we no longer have to wrap and then unwrap the originals. |
| // result is just a vector, not a collection of wrapped originals. |
| // Also for cursors the initial connection is automatically registered. |
| if (buildDirectlyFromRows) { |
| List<AbstractRecord> rows = (List<AbstractRecord>)result; |
| ContainerPolicy cp = this.containerPolicy; |
| int size = rows.size(); |
| Object clones = cp.containerInstance(size); |
| if(cp.shouldAddAll()) { |
| List clonesIn = new ArrayList(size); |
| List<AbstractRecord> rowsIn = new ArrayList(size); |
| for (int index = 0; index < size; index++) { |
| AbstractRecord row = rows.get(index); |
| |
| // null is placed in the row collection for 1-m joining to filter duplicate rows. |
| if (row != null) { |
| Object clone = buildObject(row); |
| clonesIn.add(clone); |
| rowsIn.add(row); |
| } |
| } |
| cp.addAll(clonesIn, clones, unitOfWork, rowsIn, this, null, true); |
| } else { |
| boolean quickAdd = (clones instanceof Collection) && !this.descriptor.getObjectBuilder().hasWrapperPolicy(); |
| if (this.descriptor.getCachePolicy().shouldPrefetchCacheKeys() |
| && shouldMaintainCache() |
| && ! shouldRetrieveBypassCache() |
| && ((!(unitOfWork.hasCommitManager() && unitOfWork.getCommitManager().isActive()) |
| && ! unitOfWork.wasTransactionBegunPrematurely() |
| && !this.descriptor.getCachePolicy().shouldIsolateObjectsInUnitOfWork() |
| && ! this.descriptor.getCachePolicy().shouldIsolateProtectedObjectsInUnitOfWork()) |
| || (unitOfWork.isClassReadOnly(this.getReferenceClass(), this.getDescriptor())))){ |
| Object[] pkList = new Object[size]; |
| for (int i = 0; i< size; ++i){ |
| pkList[i] = getDescriptor().getObjectBuilder().extractPrimaryKeyFromRow(rows.get(i), session); |
| } |
| setPrefetchedCacheKeys(unitOfWork.getParentIdentityMapSession(this).getIdentityMapAccessorInstance().getAllCacheKeysFromIdentityMapWithEntityPK(pkList, descriptor)); |
| } |
| for (int index = 0; index < size; index++) { |
| AbstractRecord row = rows.get(index); |
| |
| // null is placed in the row collection for 1-m joining to filter duplicate rows. |
| if (row != null) { |
| Object clone = buildObject(row); |
| if (quickAdd) { |
| ((Collection)clones).add(clone); |
| } else { |
| cp.addInto(clone, clones, unitOfWork, row, this, null, true); |
| } |
| } |
| } |
| } |
| return clones; |
| } |
| |
| ContainerPolicy cp; |
| Cursor cursor = null; |
| |
| // If the query is redirected then the collection returned might no longer |
| // correspond to the original container policy. CR#2342-S.M. |
| if (getRedirector() != null) { |
| cp = ContainerPolicy.buildPolicyFor(result.getClass()); |
| } else { |
| cp = this.containerPolicy; |
| } |
| |
| // In the case of cursors just register the initially read collection. |
| if (cp.isCursorPolicy()) { |
| cursor = (Cursor)result; |
| // In nested UnitOfWork session might have been session of the parent. |
| cursor.setSession(unitOfWork); |
| cp = ContainerPolicy.buildPolicyFor(ClassConstants.Vector_class); |
| result = cursor.getObjectCollection(); |
| } |
| |
| Object clones = cp.containerInstance(cp.sizeFor(result)); |
| AbstractSession sessionToUse = unitOfWork.getParent(); |
| for (Object iter = cp.iteratorFor(result); cp.hasNext(iter);) { |
| Object object = cp.next(iter, sessionToUse); |
| Object clone = registerIndividualResult(object, null, unitOfWork, this.joinedAttributeManager, null); |
| cp.addInto(clone, clones, unitOfWork); |
| } |
| if (cursor != null) { |
| cursor.setObjectCollection((Vector)clones); |
| return cursor; |
| } else { |
| return clones; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Version of the previous method for ResultSet optimization. |
| * |
| * @return the final (conformed, refreshed, wrapped) UnitOfWork query result |
| */ |
| public Object registerResultSetInUnitOfWork(ResultSet resultSet, Vector fields, DatabaseField[] fieldsArray, UnitOfWorkImpl unitOfWork, AbstractRecord arguments) throws SQLException { |
| // TODO: add support for Conforming results in UOW - currently conforming in uow is not compatible with ResultSet optimization. |
| |
| ContainerPolicy cp = this.containerPolicy; |
| Object clones = cp.containerInstance(); |
| ResultSetMetaData metaData = resultSet.getMetaData(); |
| boolean hasNext = resultSet.next(); |
| if (hasNext) { |
| // TODO: possibly add support for SortedListContainerPolicy (cp.shouldAddAll() == true) - this policy currently is not compatible with ResultSet optimization |
| boolean quickAdd = (clones instanceof Collection) && !this.descriptor.getObjectBuilder().hasWrapperPolicy(); |
| DatabaseAccessor dbAccessor = (DatabaseAccessor)getAccessor(); |
| boolean useSimple = this.descriptor.getObjectBuilder().isSimple(); |
| AbstractSession executionSession = getExecutionSession(); |
| DatabasePlatform platform = dbAccessor.getPlatform(); |
| boolean optimizeData = platform.shouldOptimizeDataConversion(); |
| if (useSimple) { |
| // None of the fields are relational - the row could be reused, just clear all the values. |
| SimpleResultSetRecord row = new SimpleResultSetRecord(fields, fieldsArray, resultSet, metaData, dbAccessor, executionSession, platform, optimizeData); |
| if (this.descriptor.isDescriptorTypeAggregate()) { |
| // Aggregate Collection may have an unmapped primary key referencing the owner, the corresponding field will not be used when the object is populated and therefore may not be cleared. |
| row.setShouldKeepValues(true); |
| } |
| while (hasNext) { |
| Object clone = buildObject(row); |
| if (quickAdd) { |
| ((Collection)clones).add(clone); |
| } else { |
| // TODO: investigate is it possible to support MappedKeyMapPolicy - this policy currently is not compatible with ResultSet optimization |
| cp.addInto(clone, clones, unitOfWork); |
| } |
| row.reset(); |
| hasNext = resultSet.next(); |
| } |
| } else { |
| boolean shouldKeepRow = this.descriptor.getObjectBuilder().shouldKeepRow(); |
| while (hasNext) { |
| ResultSetRecord row = new ResultSetRecord(fields, fieldsArray, resultSet, metaData, dbAccessor, executionSession, platform, optimizeData); |
| Object clone = buildObject(row); |
| if (quickAdd) { |
| ((Collection)clones).add(clone); |
| } else { |
| // TODO: investigate is it possible to support MappedKeyMapPolicy - this policy currently is not compatible with ResultSet optimization |
| cp.addInto(clone, clones, unitOfWork); |
| } |
| |
| if (shouldKeepRow) { |
| if (row.hasResultSet()) { |
| // ResultSet has not been fully triggered - that means the cached object was used. |
| // Yet the row still may be cached in a value holder (see loadBatchReadAttributes and loadJoinedAttributes methods). |
| // Remove ResultSet to avoid attempt to trigger it (already closed) when pk or fk values (already extracted) accessed when the value holder is instantiated. |
| row.removeResultSet(); |
| } else { |
| row.removeNonIndirectionValues(); |
| } |
| } |
| hasNext = resultSet.next(); |
| } |
| } |
| } |
| return clones; |
| } |
| |
| /** |
| * INTERNAL: |
| * Execute the query through remote session. |
| */ |
| @Override |
| public Object remoteExecute() { |
| if (this.containerPolicy.overridesRead()) { |
| return this.containerPolicy.remoteExecute(); |
| } |
| |
| Object cacheHit = checkEarlyReturn(this.session, this.translationRow); |
| if (cacheHit != null) { |
| return cacheHit; |
| } |
| |
| return super.remoteExecute(); |
| } |
| |
| /** |
| * INTERNAL: |
| * replace the value holders in the specified result object(s) |
| */ |
| @Override |
| public Map replaceValueHoldersIn(Object object, RemoteSessionController controller) { |
| return controller.replaceValueHoldersInAll(object, getContainerPolicy()); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the container policy. Used to support different containers |
| * (e.g. Collections, Maps). |
| */ |
| public void setContainerPolicy(ContainerPolicy containerPolicy) { |
| // CR#... a container policy is always required, default is vector, |
| // required for deployment XML. |
| if (containerPolicy == null) { |
| return; |
| } |
| this.containerPolicy = containerPolicy; |
| setIsPrepared(false); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the Hierarchical Query Clause for the query |
| * <p>Example: |
| * <p>Expression startWith = builder.get("id").equal(Integer.valueOf(100)); //can be any expression which identifies a set of employees |
| * <p>Expression connectBy = builder.get("managedEmployees"); //indicated the relationship that the hierarchy is based on, must be self-referential |
| * <p>Vector orderBy = new Vector(); |
| * <p>orderBy.addElement(builder.get("startDate")); |
| * <p>readAllQuery.setHierarchicalQueryClause(startWith, connectBy, orderBy); |
| * |
| * <p>This query would generate SQL like this: |
| * <p>SELECT * FROM EMPLOYEE START WITH ID=100 CONNECT BY PRIOR ID = MANAGER_ID ORDER SIBLINGS BY START_DATE |
| * |
| * @param startWith Describes the START WITH clause of the query - null if not needed |
| * @param connectBy This should be a query key expression which indicates an attribute who's mapping describes the hierarchy |
| * @param orderSiblingsExpressions Contains expressions which indicate fields to be included in the ORDER SIBLINGS BY clause - null if not required |
| */ |
| public void setHierarchicalQueryClause(Expression startWith, Expression connectBy, List<Expression> orderSiblingsExpressions) { |
| setHierarchicalQueryClause(startWith, connectBy, orderSiblingsExpressions, null); |
| } |
| |
| /** |
| * PUBLIC: Set the Hierarchical Query Clause for the query, specifying the |
| * hierarchy traversal direction |
| * <p> |
| * Example: |
| * <p> |
| * Expression startWith = builder.get("id").equal(Integer.valueOf(100)); //can |
| * be any expression which identifies a set of employees <br> |
| * Expression connectBy = builder.get("managedEmployees"); //indicated the |
| * relationship that the hierarchy is based on, must be self-referential <br> |
| * Vector orderBy = new Vector(); <br> |
| * orderBy.addElement(builder.get("startDate")); <br> |
| * readAllQuery.setHierarchicalQueryClause(startWith, connectBy, orderBy, |
| * Direction.CHILD_TO_PARENT); |
| * |
| * <p> |
| * This query would generate SQL like this: |
| * <p> |
| * SELECT * FROM EMPLOYEE START WITH ID=100 CONNECT BY ID = PRIOR MANAGER_ID |
| * ORDER SIBLINGS BY START_DATE |
| * |
| * @param startWith |
| * Describes the START WITH clause of the query - null if not |
| * needed |
| * @param connectBy |
| * This should be a query key expression which indicates an |
| * attribute who's mapping describes the hierarchy |
| * @param orderSiblingsExpressions |
| * Contains expressions which indicate fields to be included in |
| * the ORDER SIBLINGS BY clause - null if not required |
| * @param direction |
| * The direction in which the hierarchy is traversed; if not |
| * specified, CHILD_TO_PARENT is used for OneToOne relationships |
| * and PARENT_TO_CHILD is used for collections |
| */ |
| public void setHierarchicalQueryClause(Expression startWith, Expression connectBy, List<Expression> orderSiblingsExpressions, Direction direction) { |
| this.startWithExpression = startWith; |
| this.connectByExpression = connectBy; |
| this.orderSiblingsByExpressions = orderSiblingsExpressions; |
| this.direction = direction; |
| setIsPrepared(false); |
| } |
| |
| /** |
| * PUBLIC: |
| * Configure the mapping to use an instance of the specified container class |
| * to hold the target objects. |
| * <p>jdk1.2.x: The container class must implement (directly or indirectly) the Collection interface. |
| * <p>jdk1.1.x: The container class must be a subclass of Vector. |
| */ |
| public void useCollectionClass(Class concreteClass) { |
| // Set container policy. |
| setContainerPolicy(ContainerPolicy.buildPolicyFor(concreteClass)); |
| |
| } |
| |
| /** |
| * PUBLIC: |
| * Use a CursoredStream as the result collection. |
| * The initial read size is 10 and page size is 5. |
| */ |
| public void useCursoredStream() { |
| useCursoredStream(10, 5); |
| } |
| |
| /** |
| * PUBLIC: |
| * Use a CursoredStream as the result collection. |
| * @param initialReadSize the initial number of objects to read |
| * @param pageSize the number of objects to read when more objects |
| * are needed from the database |
| */ |
| public void useCursoredStream(int initialReadSize, int pageSize) { |
| setContainerPolicy(new CursoredStreamPolicy(this, initialReadSize, pageSize)); |
| } |
| |
| /** |
| * PUBLIC: |
| * Use a CursoredStream as the result collection. |
| * @param initialReadSize the initial number of objects to read |
| * @param pageSize the number of objects to read when more objects |
| * are needed from the database |
| * @param sizeQuery a query that will return the size of the result set; |
| * this must be set if an expression is not used (i.e. custom SQL) |
| */ |
| public void useCursoredStream(int initialReadSize, int pageSize, ValueReadQuery sizeQuery) { |
| setContainerPolicy(new CursoredStreamPolicy(this, initialReadSize, pageSize, sizeQuery)); |
| } |
| |
| /** |
| * PUBLIC: |
| * Configure the query to use an instance of the specified container class |
| * to hold the result objects. The key used to index the value in the Map |
| * is the value returned by a call to the specified zero-argument method. |
| * The method must be implemented by the class (or a superclass) of the |
| * value to be inserted into the Map. |
| * <p>jdk1.2.x: The container class must implement (directly or indirectly) the Map interface. |
| * <p>jdk1.1.x: The container class must be a subclass of Hashtable. |
| * <p>The referenceClass must set before calling this method. |
| */ |
| public void useMapClass(Class concreteClass, String methodName) { |
| // the reference class has to be specified before coming here |
| if (getReferenceClass() == null) { |
| throw QueryException.referenceClassMissing(this); |
| } |
| ContainerPolicy policy = ContainerPolicy.buildPolicyFor(concreteClass); |
| policy.setKeyName(methodName, getReferenceClass().getName()); |
| setContainerPolicy(policy); |
| } |
| |
| /** |
| * PUBLIC: |
| * Use a ScrollableCursor as the result collection. |
| */ |
| public void useScrollableCursor() { |
| useScrollableCursor(10); |
| } |
| |
| /** |
| * PUBLIC: |
| * Use a ScrollableCursor as the result collection. |
| * @param pageSize the number of elements to be read into a the cursor |
| * when more elements are needed from the database. |
| */ |
| public void useScrollableCursor(int pageSize) { |
| setContainerPolicy(new ScrollableCursorPolicy(this, pageSize)); |
| } |
| |
| /** |
| * PUBLIC: |
| * Use a ScrollableCursor as the result collection. |
| * @param policy the scrollable cursor policy allows for additional result set options. |
| * Example:<p> |
| * ScrollableCursorPolicy policy = new ScrollableCursorPolicy()<p> |
| * policy.setResultSetType(ScrollableCursorPolicy.TYPE_SCROLL_INSENSITIVE);<p> |
| * query.useScrollableCursor(policy); |
| */ |
| public void useScrollableCursor(ScrollableCursorPolicy policy) { |
| policy.setQuery(this); |
| setContainerPolicy(policy); |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether the query can use ResultSet optimization. |
| * The method is called when the query is prepared, |
| * so it should refer only to the attributes that cannot be altered without re-preparing the query. |
| * If the query is a clone and the original has been already prepared |
| * this method will be called to set a (transient and therefore set to null) usesResultSetOptimization attribute. |
| */ |
| @Override |
| public boolean supportsResultSetAccessOptimizationOnPrepare() { |
| if (!super.supportsResultSetAccessOptimizationOnPrepare()) { |
| return false; |
| } |
| return !this.containerPolicy.isMappedKeyMapPolicy() && !this.containerPolicy.shouldAddAll() && // MappedKeyMapPolicy requires the whole row, OrderListContainerPolicy requires all rows. |
| !this.descriptor.shouldAlwaysConformResultsInUnitOfWork(); // will be supported when conformResult method is adapted to use ResultSet; |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether the query can use ResultSet optimization. |
| * Note that the session must be already set. |
| * The method is called when the query is executed, |
| * so it should refer only to the attributes that can be altered without re-preparing the query. |
| */ |
| @Override |
| public boolean supportsResultSetAccessOptimizationOnExecute() { |
| if (!super.supportsResultSetAccessOptimizationOnExecute()) { |
| return false; |
| } |
| return !shouldConformResultsInUnitOfWork(); // could be supported if conformResult method is adapted to use ResultSetAccessOptimization |
| } |
| } |