| /* |
| * 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 |
| // 01/06/2011-2.3 Guy Pelletier |
| // - 371453: JPA Multi-Tenancy in Bidirectional OneToOne Relation throws ArrayIndexOutOfBoundsException |
| package org.eclipse.persistence.queries; |
| |
| import java.sql.ResultSet; |
| import java.sql.ResultSetMetaData; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| 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.Accessor; |
| 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.helper.InvalidObject; |
| import org.eclipse.persistence.internal.identitymaps.CacheId; |
| import org.eclipse.persistence.internal.indirection.ProxyIndirectionPolicy; |
| 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.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 single object. |
| * |
| * <p><b>Responsibilities</b>: |
| * Return a single object for the query. |
| * Implements the inheritance feature when dealing with abstract descriptors. |
| * |
| * @author Yvon Lavoie |
| * @since TOPLink/Java 1.0 |
| */ |
| public class ReadObjectQuery extends ObjectLevelReadQuery { |
| /** Object that can be used in place of a selection criteria. */ |
| protected transient Object selectionObject; |
| |
| /** Key that can be used in place of a selection criteria. */ |
| protected Object selectionId; |
| |
| /** Can be used to refresh a specific non-cached instance from the database. */ |
| protected boolean shouldLoadResultIntoSelectionObject = false; |
| |
| /** |
| * PUBLIC: |
| * Return a new read object query. |
| * A reference class must be specified before execution. |
| * It is better to provide the class and expression builder on construction to esnure a single expression builder is used. |
| * If no selection criteria is specified this will reads the first object found in the database. |
| */ |
| public ReadObjectQuery() { |
| super(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a new read object query. |
| * By default, the query has no selection criteria. Executing this query without |
| * selection criteria will always result in a database access to read the first |
| * instance of the specified Class found in the database. This is true no |
| * matter how cache usage is configured and even if an instance of the |
| * specified Class exists in the cache. |
| * Executing a query with selection criteria allows you to avoid a database |
| * access if the selected instance is in the cache. For this reason, you may whish to use a ReadObjectQuery constructor that takes selection criteria, such as: {@link #ReadObjectQuery(Class, Call)}, {@link #ReadObjectQuery(Class, Expression)}, {@link #ReadObjectQuery(Class, ExpressionBuilder)}, {@link #ReadObjectQuery(ExpressionBuilder)}, {@link #ReadObjectQuery(Object)}, or {@link #ReadObjectQuery(Object, QueryByExamplePolicy)}. |
| */ |
| public ReadObjectQuery(Class<?> classToRead) { |
| this(); |
| this.referenceClass = classToRead; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a new read object query for the class and the selection criteria. |
| */ |
| public ReadObjectQuery(Class<?> classToRead, Expression selectionCriteria) { |
| this(); |
| this.referenceClass = classToRead; |
| setSelectionCriteria(selectionCriteria); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a new read object query for the class. |
| * The expression builder must be used for all associated expressions used with the query. |
| */ |
| public ReadObjectQuery(Class<?> classToRead, ExpressionBuilder builder) { |
| this(); |
| this.defaultBuilder = builder; |
| this.referenceClass = classToRead; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a new read object query. |
| * The call represents a database interaction such as SQL, Stored Procedure. |
| */ |
| public ReadObjectQuery(Class<?> classToRead, Call call) { |
| this(); |
| this.referenceClass = classToRead; |
| setCall(call); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a new read object query. |
| * The call represents a database interaction such as SQL, Stored Procedure. |
| */ |
| public ReadObjectQuery(Call call) { |
| this(); |
| setCall(call); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a query to read the object with the same primary key as the provided object. |
| * Note: This is not a query by example object, only the primary key will be used for the selection criteria. |
| */ |
| public ReadObjectQuery(Object objectToRead) { |
| this(); |
| setSelectionObject(objectToRead); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return a query by example query to find an object matching the attributes of the example object. |
| */ |
| public ReadObjectQuery(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 ReadObjectQuery(ExpressionBuilder builder) { |
| this(); |
| this.defaultBuilder = builder; |
| } |
| |
| /** |
| * 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 object) { |
| Object cachableObject = object; |
| if (object == null) { |
| this.temporaryCachedQueryResults = InvalidObject.instance(); |
| } else { |
| if (this.shouldUseWrapperPolicy) { |
| cachableObject = this.session.wrapObject(object); |
| } |
| this.temporaryCachedQueryResults = cachableObject; |
| } |
| } |
| |
| |
| /** |
| * PUBLIC: |
| * The cache will be checked only if the query contains exactly the primary key. |
| * Queries can be configured to use the cache at several levels. |
| * Other caching option are available. |
| * @see #setCacheUsage(int) |
| */ |
| public void checkCacheByExactPrimaryKey() { |
| setCacheUsage(CheckCacheByExactPrimaryKey); |
| } |
| |
| /** |
| * PUBLIC: |
| * This is the default, the cache will be checked only if the query contains the primary key. |
| * Queries can be configured to use the cache at several levels. |
| * Other caching option are available. |
| * @see #setCacheUsage(int) |
| */ |
| public void checkCacheByPrimaryKey() { |
| setCacheUsage(CheckCacheByPrimaryKey); |
| } |
| |
| /** |
| * PUBLIC: |
| * The cache will be checked completely, then if the object is not found or the query too complex the database will be queried. |
| * Queries can be configured to use the cache at several levels. |
| * Other caching option are available. |
| * @see #setCacheUsage(int) |
| */ |
| public void checkCacheThenDatabase() { |
| setCacheUsage(CheckCacheThenDatabase); |
| } |
| |
| /** |
| * INTERNAL: |
| * Ensure that the descriptor has been set. |
| */ |
| @Override |
| public void checkDescriptor(AbstractSession session) throws QueryException { |
| if (this.descriptor == null) { |
| if (getReferenceClass() == null) { |
| throw QueryException.referenceClassMissing(this); |
| } |
| ClassDescriptor referenceDescriptor; |
| //Bug#3947714 In case getSelectionObject() is proxy |
| if (getSelectionObject() != null && session.getProject().hasProxyIndirection()) { |
| referenceDescriptor = session.getDescriptor(getSelectionObject()); |
| } else { |
| referenceDescriptor = session.getDescriptor(getReferenceClass()); |
| } |
| if (referenceDescriptor == null) { |
| throw QueryException.descriptorIsMissing(getReferenceClass(), this); |
| } |
| setDescriptor(referenceDescriptor); |
| } |
| } |
| |
| /** |
| * 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) { |
| if (shouldCheckCache() && shouldMaintainCache() && (!shouldRefreshIdentityMapResult() && (!shouldRetrieveBypassCache())) |
| && (!(session.isRemoteSession() && (shouldRefreshRemoteIdentityMapResult() || this.descriptor.shouldDisableCacheHitsOnRemote()))) |
| && (!(shouldCheckDescriptorForCacheUsage() && this.descriptor.shouldDisableCacheHits())) && (!this.descriptor.isDescriptorForInterface())) { |
| Object cachedObject = getQueryMechanism().checkCacheForObject(translationRow, session); |
| this.isCacheCheckComplete = true; |
| |
| // Optimization: If find deleted object by exact primary |
| // key expression or selection object/key just abort. |
| if (cachedObject == InvalidObject.instance) { |
| return cachedObject; |
| } |
| if (cachedObject != null) { |
| if (shouldLoadResultIntoSelectionObject()) { |
| ObjectBuilder builder = this.descriptor.getObjectBuilder(); |
| builder.copyInto(cachedObject, getSelectionObject()); |
| //put this object into the cache. This may cause some loss of identity |
| session.getIdentityMapAccessorInstance().putInIdentityMap(getSelectionObject()); |
| cachedObject = getSelectionObject(); |
| } |
| |
| // check locking. If clone has not been locked, do not early return cached object |
| if (isLockQuery() && (session.isUnitOfWork() && !((UnitOfWorkImpl)session).isPessimisticLocked(cachedObject))) { |
| return null; |
| } |
| if (QueryMonitor.shouldMonitor()) { |
| QueryMonitor.incrementReadObjectHits(this); |
| } |
| session.incrementProfile(SessionProfiler.CacheHits, this); |
| } else { |
| if (!session.isUnitOfWork()) { |
| if (QueryMonitor.shouldMonitor()) { |
| QueryMonitor.incrementReadObjectMisses(this); |
| } |
| session.incrementProfile(SessionProfiler.CacheMisses, this); |
| } |
| } |
| if (shouldUseWrapperPolicy()) { |
| cachedObject = this.descriptor.getObjectBuilder().wrapObject(cachedObject, session); |
| } |
| return cachedObject; |
| } else { |
| if (!session.isUnitOfWork()) { |
| if (QueryMonitor.shouldMonitor()) { |
| QueryMonitor.incrementReadObjectMisses(this); |
| } |
| session.incrementProfile(SessionProfiler.CacheMisses, this); |
| } |
| 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. |
| * @param translationRow Database record. |
| * @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. |
| Boolean useCustomQuery = isCustomQueryUsed; |
| if (useCustomQuery != null) { |
| return useCustomQuery; |
| } |
| // Check if user defined a custom query in the query manager. |
| if (!this.isUserDefined) { |
| if (!isCallQuery() |
| // By default all descriptors have a custom ("static") read-object query. |
| // This allows the read-object query and SQL to be prepare once. |
| && this.descriptor.getQueryManager().hasReadObjectQuery()) { |
| // If the query require special SQL generation or execution do not use the static read object query. |
| // PERF: the read-object query should always be static to ensure no regeneration of SQL. |
| if ((!hasJoining() || !this.joinedAttributeManager.hasJoinedAttributeExpressions()) |
| && (!hasPartialAttributeExpressions()) && (redirector == null) && !doNotRedirect |
| && (!hasAsOfClause()) && (!hasNonDefaultFetchGroup()) |
| && (this.shouldUseSerializedObjectPolicy == shouldUseSerializedObjectPolicyDefault) |
| && this.wasDefaultLockMode && (shouldBindAllParameters == null) && (this.hintString == null)) { |
| if ((this.selectionId != null) || (this.selectionObject != null)) {// Must be primary key. |
| return Boolean.TRUE; |
| } else { |
| Expression selectionCriteria = getSelectionCriteria(); |
| if (selectionCriteria != null) { |
| AbstractRecord primaryKeyRow = |
| this.descriptor.getObjectBuilder().extractPrimaryKeyRowFromExpression( |
| selectionCriteria, translationRow, session); |
| // Only execute the query if the selection criteria has the primary key fields set |
| if (primaryKeyRow != null) { |
| return Boolean.TRUE; |
| } |
| } |
| } |
| } |
| } |
| } |
| return useCustomQuery; |
| } |
| |
| /** |
| * INTERNAL: |
| * Get custom single object read query from query manager. |
| * Called from {@link #checkForCustomQuery(AbstractSession, AbstractRecord)} to retrieve custom read query. |
| * @return Custom single object read query from query manager. |
| */ |
| @Override |
| protected ObjectLevelReadQuery getReadQuery() { |
| return descriptor.getQueryManager().getReadObjectQuery(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Conform the result in the UnitOfWork. |
| */ |
| protected Object conformResult(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord databaseRow, boolean buildDirectlyFromRows) { |
| // Note that if the object does not conform even though other objects might exist on the database null is returned. |
| // Note that new objects is checked before the read is executed so does not have to be re-checked. |
| // Must unwrap as the built object is always wrapped. |
| // Note the object is unwrapped on the parent which it belongs to, as we |
| // do not want to trigger a registration just yet. |
| Object clone = null; |
| if (buildDirectlyFromRows) { |
| clone = buildObject((AbstractRecord)result); |
| } else { |
| clone = registerIndividualResult( |
| this.descriptor.getObjectBuilder().unwrapObject(result, unitOfWork.getParent()), |
| null, unitOfWork, null, null); |
| } |
| Expression selectionCriteria = getSelectionCriteria(); |
| if ((selectionCriteria != null) && (this.selectionId == null) && (this.selectionObject == null)) { |
| ExpressionBuilder builder = selectionCriteria.getBuilder(); |
| builder.setSession(unitOfWork.getRootSession(null)); |
| builder.setQueryClass(getReferenceClass()); |
| } |
| |
| clone = conformIndividualResult(clone, unitOfWork, databaseRow, selectionCriteria, null); |
| if (clone == null) { |
| return clone; |
| } |
| |
| if (shouldUseWrapperPolicy()) { |
| return this.descriptor.getObjectBuilder().wrapObject(clone, unitOfWork); |
| } else { |
| return clone; |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Do not refesh/load into the selection object, this is the default. |
| * This property allows for the selection object of the query to be refreshed or put into the TopLink cache. |
| * By default on a read or refresh the object in the cache is refreshed and returned or a new object is built from the database, |
| * in some cases such as EJB BMP it is desirable to refresh or load into the object passed into the read object query. |
| * <p>Note: This forces the selection object into the cache a replaces any existing object that may already be there, |
| * this is a strict violation of object identity and other objects can still be refering to the old object. |
| */ |
| public void dontLoadResultIntoSelectionObject() { |
| setShouldLoadResultIntoSelectionObject(false); |
| } |
| |
| /** |
| * 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 (shouldConformResultsInUnitOfWork()) { |
| throw QueryException.cannotConformAndCacheQueryResults(this); |
| } |
| if (isPrepared()) {// only prepared queries can have cached results. |
| Object result = getQueryResults(session, row, true); |
| // Bug6138532 - if result is "cached no results", return null immediately |
| if (result == InvalidObject.instance) { |
| return null; |
| } |
| if (result != null) { |
| if (session.isUnitOfWork()) { |
| result = ((UnitOfWorkImpl)session).registerExistingObject(result); |
| } |
| return result; |
| } |
| } |
| } |
| return super.execute(session, row); |
| } |
| |
| /** |
| * INTERNAL: |
| * Execute the query. |
| * Do a cache lookup and build object from row if required. |
| * @exception DatabaseException - an error has occurred on the database |
| * @return object - the first object found or null if none. |
| */ |
| @Override |
| protected Object executeObjectLevelReadQuery() throws DatabaseException { |
| if (this.descriptor.isDescriptorForInterface() || this.descriptor.hasTablePerClassPolicy()) { |
| Object returnValue = this.descriptor.getInterfacePolicy().selectOneObjectUsingMultipleTableSubclassRead(this); |
| |
| if (this.descriptor.hasTablePerClassPolicy() && (!this.descriptor.isAbstract()) && (returnValue == null)) { |
| // let it fall through to query the root. |
| } else { |
| this.executionTime = System.currentTimeMillis(); |
| return returnValue; |
| } |
| } |
| |
| boolean shouldSetRowsForJoins = hasJoining() && this.joinedAttributeManager.isToManyJoin(); |
| AbstractSession session = getSession(); |
| Object result = null; |
| AbstractRecord row = null; |
| |
| Object sopObject = getTranslationRow().getSopObject(); |
| boolean useOptimization = false; |
| if (sopObject == null) { |
| useOptimization = usesResultSetAccessOptimization(); |
| } |
| |
| if (useOptimization) { |
| DatabaseCall call = ((DatasourceCallQueryMechanism)this.queryMechanism).selectResultSet(); |
| this.executionTime = System.currentTimeMillis(); |
| boolean exceptionOccured = false; |
| ResultSet resultSet = call.getResult(); |
| DatabaseAccessor dbAccessor = (DatabaseAccessor)getAccessor(); |
| try { |
| if (resultSet.next()) { |
| ResultSetMetaData metaData = call.getResult().getMetaData(); |
| boolean useSimple = this.descriptor.getObjectBuilder().isSimple(); |
| DatabasePlatform platform = dbAccessor.getPlatform(); |
| boolean optimizeData = platform.shouldOptimizeDataConversion(); |
| if (useSimple) { |
| row = new SimpleResultSetRecord(call.getFields(), call.getFieldsArray(), resultSet, metaData, dbAccessor, getExecutionSession(), 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. |
| ((SimpleResultSetRecord)row).setShouldKeepValues(true); |
| } |
| } else { |
| row = new ResultSetRecord(call.getFields(), call.getFieldsArray(), resultSet, metaData, dbAccessor, getExecutionSession(), platform, optimizeData); |
| } |
| if (session.isUnitOfWork()) { |
| result = registerResultInUnitOfWork(row, (UnitOfWorkImpl)session, this.translationRow, true); |
| } else { |
| result = buildObject(row); |
| } |
| |
| if (!useSimple && this.descriptor.getObjectBuilder().shouldKeepRow()) { |
| if (((ResultSetRecord)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. |
| ((ResultSetRecord)row).removeResultSet(); |
| } else { |
| ((ResultSetRecord)row).removeNonIndirectionValues(); |
| } |
| } |
| } |
| } catch (SQLException exception) { |
| exceptionOccured = true; |
| DatabaseException commException = dbAccessor.processExceptionForCommError(session, exception, call); |
| if (commException != null) { |
| throw commException; |
| } |
| throw DatabaseException.sqlException(exception, call, getAccessor(), session, false); |
| } finally { |
| try { |
| if (resultSet != null) { |
| resultSet.close(); |
| } |
| if (dbAccessor != null) { |
| if (call.getStatement() != null) { |
| dbAccessor.releaseStatement(call.getStatement(), call.getSQLString(), call, 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, session, false); |
| } |
| } |
| } |
| } else { |
| if (sopObject != null) { |
| row = new DatabaseRecord(0); |
| row.setSopObject(sopObject); |
| } else { |
| // If using 1-m joins, must select all rows. |
| if (shouldSetRowsForJoins) { |
| List rows = getQueryMechanism().selectAllRows(); |
| if (rows.size() > 0) { |
| row = (AbstractRecord)rows.get(0); |
| } |
| getJoinedAttributeManager().setDataResults(rows, session); |
| } else { |
| row = getQueryMechanism().selectOneRow(); |
| } |
| } |
| |
| this.executionTime = System.currentTimeMillis(); |
| if (row != null) { |
| if (session.isUnitOfWork()) { |
| result = registerResultInUnitOfWork(row, (UnitOfWorkImpl)session, this.translationRow, true); |
| } else { |
| result = buildObject(row); |
| } |
| if (sopObject != null) { |
| // remove sopObject so it's not stuck in a value holder. |
| row.setSopObject(null); |
| } |
| } |
| } |
| if ((result == null) && shouldCacheQueryResults()) { |
| cacheResult(null); |
| } |
| if ((result == null) && this.shouldRefreshIdentityMapResult) { |
| // bug5955326, should invalidate the shared cached if refreshed object no longer exists. |
| if (this.selectionId != null) { |
| session.getParentIdentityMapSession(this.descriptor, true, true).getIdentityMapAccessor().invalidateObject(this.selectionId, this.referenceClass); |
| } else if (this.selectionObject != null) { |
| session.getParentIdentityMapSession(this.descriptor, true, true).getIdentityMapAccessor().invalidateObject(this.selectionObject); |
| } |
| } |
| |
| if (this.shouldIncludeData && (sopObject == null)) { |
| ComplexQueryResult complexResult = new ComplexQueryResult(); |
| complexResult.setResult(result); |
| complexResult.setData(row); |
| return complexResult; |
| } |
| |
| 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 object - the first object found or null if none. |
| */ |
| @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)((List<Accessor>)this.accessors).get(0); |
| boolean exceptionOccured = false; |
| try { |
| if (!resultSet.next()) { |
| return null; |
| } |
| ResultSetMetaData metaData = resultSet.getMetaData(); |
| return this.descriptor.getObjectBuilder().buildObjectFromResultSet(this, null, resultSet, session, accessor, metaData, platform, call.getFields(), call.getFieldsArray()); |
| } 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()).getObjectCorrespondingTo(transporter.getObject(), transporter.getObjectDescriptors(), new IdentityHashMap(), this); |
| } |
| |
| /** |
| * 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.getDefaultReadObjectQueryRedirector(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the selection object of the query. |
| * This can be used instead of a where clause expression for single object primary key queries. |
| * The selection object given should have a primary key defined, |
| * this primary key will be used to query the database instance of the same object. |
| * This is a basic form of query by example where only the primary key is required, |
| * it can be used for simple query forms, or testing. |
| */ |
| public Object getSelectionObject() { |
| return selectionObject; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return if this is a read object query. |
| */ |
| @Override |
| public boolean isReadObjectQuery() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the query is by primary key. |
| */ |
| @Override |
| public boolean isPrimaryKeyQuery() { |
| return (this.selectionId != null) || (this.selectionObject != null); |
| } |
| |
| /** |
| * PUBLIC: |
| * Allow for the selection object of the query to be refreshed or put into the EclipseLink cache. |
| * By default on a read or refresh the object in the cache is refreshed and returned or a new object is built from the database, |
| * in some cases such as EJB BMP it is desirable to refresh or load into the object passed into the read object query. |
| * <p>Note: This forces the selection object into the cache a replaces any existing object that may already be there, |
| * this is a strict violation of object identity and other objects can still be referring to the old object. |
| */ |
| public void loadResultIntoSelectionObject() { |
| setShouldLoadResultIntoSelectionObject(true); |
| } |
| |
| /** |
| * INTERNAL: |
| * Copy all setting from the query. |
| * This is used to morph queries from one type to the other. |
| * By default this calls prepareFromQuery, but additional properties may be required |
| * to be copied as prepareFromQuery only copies properties that affect the SQL. |
| */ |
| @Override |
| public void copyFromQuery(DatabaseQuery query) { |
| super.copyFromQuery(query); |
| if (query.isReadObjectQuery()) { |
| ReadObjectQuery readQuery = (ReadObjectQuery)query; |
| this.selectionId = readQuery.selectionId; |
| this.selectionObject = readQuery.selectionObject; |
| this.shouldLoadResultIntoSelectionObject = readQuery.shouldLoadResultIntoSelectionObject; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare the receiver for execution in a session. |
| */ |
| @Override |
| protected void prepare() throws QueryException { |
| if (prepareFromCachedQuery()) { |
| return; |
| } |
| super.prepare(); |
| |
| if ((this.selectionId != null) || (this.selectionObject != null)) { |
| // The expression is set in the prepare as params. |
| setSelectionCriteria(this.descriptor.getObjectBuilder().getPrimaryKeyExpression()); |
| setExpressionBuilder(getSelectionCriteria().getBuilder()); |
| extendPessimisticLockScope(); |
| // For bug 2989998 the translation row is required to be set at this point. |
| if (!shouldPrepare()) { |
| if (this.selectionId != null) { |
| // Row must come from the key. |
| setTranslationRow(this.descriptor.getObjectBuilder().buildRowFromPrimaryKeyValues(this.selectionId, this.session)); |
| } else {//(getSelectionObject() != null) |
| setTranslationRow(this.descriptor.getObjectBuilder().buildRowForTranslation(this.selectionObject, this.session)); |
| } |
| } |
| } |
| |
| if (this.descriptor.isDescriptorForInterface()) { |
| return; |
| } |
| |
| // PERF: Disable cache check if not a primary key query. |
| if (isExpressionQuery()) { |
| Expression selectionCriteria = getSelectionCriteria(); |
| if (selectionCriteria != null) { |
| if (((this.cacheUsage == CheckCacheByPrimaryKey) |
| && (!this.descriptor.getObjectBuilder().isPrimaryKeyExpression(false, selectionCriteria, this.session))) |
| || ((this.cacheUsage == CheckCacheByExactPrimaryKey) |
| && (!this.descriptor.getObjectBuilder().isPrimaryKeyExpression(true, selectionCriteria, this.session)))) { |
| this.cacheUsage = DoNotCheckCache; |
| } |
| } |
| } |
| |
| // If using 1-m joining select all rows. |
| if ((this.joinedAttributeManager != null) && this.joinedAttributeManager.isToManyJoin()) { |
| getQueryMechanism().prepareSelectAllRows(); |
| } else { |
| getQueryMechanism().prepareSelectOneRow(); |
| } |
| |
| // should be called after prepareSelectRow so that the call knows whether it returns ResultSet |
| prepareResultSetAccessOptimization(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the properties needed to be cascaded into the custom query including the translation row. |
| * This is used only for primary key queries, as the descriptor query manager |
| * stores a predefined query for this query to avoid having to re-prepare and allow for customization. |
| */ |
| @Override |
| protected void prepareCustomQuery(DatabaseQuery customQuery) { |
| super.prepareCustomQuery(customQuery); |
| ReadObjectQuery customReadQuery = (ReadObjectQuery)customQuery; |
| customReadQuery.shouldRefreshIdentityMapResult = this.shouldRefreshIdentityMapResult; |
| customReadQuery.cascadePolicy = this.cascadePolicy; |
| customReadQuery.shouldMaintainCache = this.shouldMaintainCache; |
| customReadQuery.shouldUseWrapperPolicy = this.shouldUseWrapperPolicy; |
| // CR... was missing some values, execution could cause infinite loop. |
| customReadQuery.queryId = this.queryId; |
| customReadQuery.executionTime = this.executionTime; |
| customReadQuery.shouldLoadResultIntoSelectionObject = this.shouldLoadResultIntoSelectionObject; |
| AbstractRecord primaryKeyRow; |
| if (this.selectionObject != null) { |
| // CR#... Must also set the selection object as may be loading into the object (refresh) |
| customReadQuery.selectionObject = this.selectionObject; |
| // The translation/primary key row will be set in prepareForExecution. |
| } else if (this.selectionId != null) { |
| customReadQuery.selectionId = this.selectionId; |
| } else { |
| // The primary key row must be used. |
| primaryKeyRow = customQuery.getDescriptor().getObjectBuilder().extractPrimaryKeyRowFromExpression(getSelectionCriteria(), customQuery.getTranslationRow(), customReadQuery.getSession()); |
| customReadQuery.setTranslationRow(primaryKeyRow); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare the receiver for execution in a session. |
| */ |
| @Override |
| public void prepareForExecution() throws QueryException { |
| super.prepareForExecution(); |
| |
| // For bug 2989998 the translation row now sometimes set earlier in prepare. |
| if (shouldPrepare()) { |
| if (this.selectionId != null) { |
| // Row must come from the key. |
| this.translationRow = this.descriptor.getObjectBuilder().buildRowFromPrimaryKeyValues(this.selectionId, getSession()); |
| } else if (this.selectionObject != null) { |
| // The expression is set in the prepare as params. |
| this.translationRow = this.descriptor.getObjectBuilder().buildRowForTranslation(this.selectionObject, getSession()); |
| } |
| } |
| |
| // If we have tenant discriminator fields we need to add them to the |
| // database row when doing a primary key query. |
| // Modifying the translation row here will modify it on the original |
| // query which is not good (will append the tenant field to the sql call |
| // for subsequent queries) The translation row must be cloned to isolate |
| // this. |
| if (getDescriptor().hasMultitenantPolicy()) { |
| this.translationRow = this.translationRow.clone(); |
| getDescriptor().getMultitenantPolicy().addFieldsToRow(this.translationRow, getSession()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare the receiver for execution in a session. |
| */ |
| @Override |
| protected void prePrepare() throws QueryException { |
| super.prePrepare(); |
| //Bug#3947714 In case getSelectionObject() is proxy |
| if (getSelectionObject() != null && getSession().getProject().hasProxyIndirection()) { |
| setSelectionObject(ProxyIndirectionPolicy.getValueFromProxy(getSelectionObject())); |
| } |
| } |
| |
| /** |
| * 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) { |
| if (result == null) { |
| return null; |
| } |
| if (unitOfWork.hasCloneMapping() // PERF: Avoid conforming empty uow. |
| && (shouldConformResultsInUnitOfWork() || this.descriptor.shouldAlwaysConformResultsInUnitOfWork())) { |
| return conformResult(result, unitOfWork, arguments, buildDirectlyFromRows); |
| } |
| Object clone = null; |
| if (buildDirectlyFromRows) { |
| clone = buildObject((AbstractRecord)result); |
| } else { |
| clone = registerIndividualResult(result, null, unitOfWork, null, null); |
| } |
| |
| if (shouldUseWrapperPolicy()) { |
| clone = this.descriptor.getObjectBuilder().wrapObject(clone, unitOfWork); |
| } |
| return clone; |
| } |
| |
| @Override |
| protected Object remoteExecute() { |
| // Do a cache lookup. |
| checkDescriptor(session); |
| // As the selection object is transient, compute the key. |
| if (getSelectionObject() != null) { |
| // Must be checked separately as the expression and row is not yet set. |
| setSelectionId(getDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(getSelectionObject(), session)); |
| } |
| |
| Object cacheHit = checkEarlyReturn(getSession(), getTranslationRow()); |
| if ((cacheHit != null) || shouldCheckCacheOnly()) { |
| if (cacheHit == InvalidObject.instance) { |
| return 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.replaceValueHoldersIn(object); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the primary key stored in this query |
| * |
| * @return the selection id of this ReadObjectQuery |
| */ |
| @Override |
| protected Object getQueryPrimaryKey(){ |
| return getSelectionId(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return Id of the object to be selected by the query. |
| */ |
| public Object getSelectionId() { |
| return this.selectionId; |
| } |
| |
| /** |
| * INTERNAL: |
| * Clear the selection id and object. |
| * This is done after cloning queries to prepare them in inheritance. |
| */ |
| public void clearSelectionId() { |
| this.selectionId = null; |
| this.selectionObject = null; |
| } |
| |
| /** |
| * PUBLIC: |
| * The Id of the object to be selected by the query. |
| * This will generate a query by primary key. |
| * This can be used instead of an Expression, SQL, or JPQL, or example object. |
| * The Id is the Id value for a singleton primary key, |
| * for a composite it is an instance of CacheId. |
| * @see CacheId |
| */ |
| public void setSelectionId(Object id) { |
| this.selectionId = id; |
| } |
| |
| /** |
| * PUBLIC: |
| * Used to set the where clause of the query. |
| * This can be used instead of a where clause expression for single object primary key queries. |
| * The selection object given should have a primary key defined, |
| * this primary key will be used to query the database instance of the same object. |
| * This is a basic form of query by example where only the primary key is required, |
| * it can be used for simple query forms, or testing. |
| */ |
| public void setSelectionObject(Object selectionObject) { |
| if (selectionObject == null) { |
| throw QueryException.selectionObjectCannotBeNull(this); |
| } |
| setSelectionId(null); |
| // Check if the query needs to be unprepared. |
| if ((this.selectionObject == null) || (this.selectionObject.getClass() != selectionObject.getClass())) { |
| setIsPrepared(false); |
| } |
| setReferenceClass(selectionObject.getClass()); |
| this.selectionObject = selectionObject; |
| } |
| |
| /** |
| * PUBLIC: |
| * Allow for the selection object of the query to be refreshed or put into the EclipseLink cache. |
| * By default on a read or refresh the object in the cache is refreshed and returned or a new object is built from the database, |
| * in some cases such as EJB BMP it is desirable to refresh or load into the object passed into the read object query. |
| * <p>Note: This forces the selection object into the cache a replaces any existing object that may already be there, |
| * this is a strict violation of object identity and other objects can still be referring to the old object. |
| */ |
| public void setShouldLoadResultIntoSelectionObject(boolean shouldLoadResultIntoSelectionObject) { |
| this.shouldLoadResultIntoSelectionObject = shouldLoadResultIntoSelectionObject; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return if cache should be checked. |
| */ |
| public boolean shouldCheckCacheByExactPrimaryKey() { |
| return this.cacheUsage == CheckCacheByExactPrimaryKey; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return if cache should be checked. |
| */ |
| public boolean shouldCheckCacheByPrimaryKey() { |
| return (this.cacheUsage == CheckCacheByPrimaryKey) || (this.cacheUsage == UseDescriptorSetting); |
| } |
| |
| /** |
| * PUBLIC: |
| * Return if cache should be checked. |
| */ |
| public boolean shouldCheckCacheThenDatabase() { |
| return this.cacheUsage == CheckCacheThenDatabase; |
| } |
| |
| /** |
| * PUBLIC: |
| * return true if the result should be loaded into the passed in selection Object |
| */ |
| public boolean shouldLoadResultIntoSelectionObject() { |
| return shouldLoadResultIntoSelectionObject; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the query has an non-default fetch group defined for itself. |
| */ |
| protected boolean hasNonDefaultFetchGroup() { |
| return this.descriptor.hasFetchGroupManager() && ((this.fetchGroup != null) || (this.fetchGroupName != null) |
| || (!this.shouldUseDefaultFetchGroup && (this.descriptor.getFetchGroupManager().getDefaultFetchGroup() != null))); |
| |
| } |
| } |