All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.queries.ReadAllQuery Maven / Gradle / Ivy

There is a newer version: 5.0.0-B03
Show newest version
/*
 * 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;

/**
 * 

Purpose: * Concrete class for all read queries involving a collection of objects. * *

Responsibilities: * 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 orderSiblingsByExpressions; protected Direction direction; /** * Specifies the direction in which the hierarchy is traversed in a * hierarchical query. */ public 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.
* 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: *

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 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 rows = (List)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 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} - the ordering expressions used to generate the hierarchical query clause in Oracle */ public List 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. *

* 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 rows = (List)result; ContainerPolicy cp = this.containerPolicy; int size = rows.size(); Object clones = cp.containerInstance(size); if(cp.shouldAddAll()) { List clonesIn = new ArrayList(size); List 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 *

Example: *

Expression startWith = builder.get("id").equal(Integer.valueOf(100)); //can be any expression which identifies a set of employees *

Expression connectBy = builder.get("managedEmployees"); //indicated the relationship that the hierarchy is based on, must be self-referential *

Vector orderBy = new Vector(); *

orderBy.addElement(builder.get("startDate")); *

readAllQuery.setHierarchicalQueryClause(startWith, connectBy, orderBy); * *

This query would generate SQL like this: *

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 orderSiblingsExpressions) { setHierarchicalQueryClause(startWith, connectBy, orderSiblingsExpressions, null); } /** * PUBLIC: Set the Hierarchical Query Clause for the query, specifying the * hierarchy traversal direction *

* Example: *

* Expression startWith = builder.get("id").equal(Integer.valueOf(100)); //can * be any expression which identifies a set of employees
* Expression connectBy = builder.get("managedEmployees"); //indicated the * relationship that the hierarchy is based on, must be self-referential
* Vector orderBy = new Vector();
* orderBy.addElement(builder.get("startDate"));
* readAllQuery.setHierarchicalQueryClause(startWith, connectBy, orderBy, * Direction.CHILD_TO_PARENT); * *

* This query would generate SQL like this: *

* 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 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. *

jdk1.2.x: The container class must implement (directly or indirectly) the Collection interface. *

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. *

jdk1.2.x: The container class must implement (directly or indirectly) the Map interface. *

jdk1.1.x: The container class must be a subclass of Hashtable. *

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:

* ScrollableCursorPolicy policy = new ScrollableCursorPolicy()

* policy.setResultSetType(ScrollableCursorPolicy.TYPE_SCROLL_INSENSITIVE);

* 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 } }