org.eclipse.persistence.internal.jpa.QueryImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022 IBM Corporation. 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
// Zoltan NAGY & tware - updated support for MaxRows
// 11/01/2010-2.2 Guy Pelletier
// - 322916: getParameter on Query throws NPE
// 11/09/2010-2.1 Michael O'Brien
// - 329089: PERF: EJBQueryImpl.setParamenterInternal() move indexOf check inside non-native block
// 02/08/2012-2.4 Guy Pelletier
// - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
// 06/20/2012-2.5 Guy Pelletier
// - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
// 08/24/2012-2.5 Guy Pelletier
// - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
// 11/05/2012-2.5 Guy Pelletier
// - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
// 08/11/2012-2.5 Guy Pelletier
// - 393867: Named queries do not work when using EM level Table Per Tenant Multitenancy.
package org.eclipse.persistence.internal.jpa;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.LockTimeoutException;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Parameter;
import javax.persistence.PersistenceException;
import javax.persistence.PessimisticLockException;
import javax.persistence.Query;
import javax.persistence.QueryTimeoutException;
import javax.persistence.TemporalType;
import javax.persistence.TransactionRequiredException;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.internal.helper.BasicTypeHelperImpl;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.ConversionManager;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.jpa.querydef.ParameterExpressionImpl;
import org.eclipse.persistence.internal.localization.ExceptionLocalization;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.jpa.JpaEntityManager;
import org.eclipse.persistence.platform.database.oracle.plsql.PLSQLStoredProcedureCall;
import org.eclipse.persistence.platform.database.oracle.plsql.PLSQLargument;
import org.eclipse.persistence.queries.Call;
import org.eclipse.persistence.queries.DataModifyQuery;
import org.eclipse.persistence.queries.DataReadQuery;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.queries.ModifyQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.ReadAllQuery;
import org.eclipse.persistence.queries.ReadQuery;
import org.eclipse.persistence.queries.StoredProcedureCall;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.sessions.Session;
/**
* Concrete JPA query class. The JPA query wraps a DatabaseQuery which is
* executed.
*/
public class QueryImpl {
public static final int UNDEFINED = -1;
/**
* Wrapped native query. The query may be {@link #isShared}
*/
protected DatabaseQuery databaseQuery = null;
protected EntityManagerImpl entityManager = null;
protected String queryName = null;
protected Map parameterValues = null;
protected Map> parameters;
protected int firstResultIndex = UNDEFINED;
protected int maxResults = UNDEFINED;
protected LockModeType lockMode = null;
/**
* Stores if the wrapped query is shared, and requires cloning before being
* changed.
*/
protected boolean isShared;
/**
* Base constructor for EJBQueryImpl. Initializes basic variables.
*/
protected QueryImpl(EntityManagerImpl entityManager) {
this.parameterValues = new HashMap();
this.entityManager = entityManager;
this.isShared = true;
}
/**
* Create an EJBQueryImpl with a DatabaseQuery.
*/
public QueryImpl(DatabaseQuery query, EntityManagerImpl entityManager) {
this(entityManager);
this.databaseQuery = query;
}
/**
* This method should be called to close any left over open connection to
* the database (if there is one).
*/
public void close() {
// Currently nothing to do at this level. Connections are not left open.
}
/**
* INTERNAL:
* Change the internal query to data modify query.
*/
protected void setAsDataModifyQuery() {
DataModifyQuery query = new DataModifyQuery();
query.setIsUserDefined(this.databaseQuery.isUserDefined());
// By default, do not batch user native queries, as row count must be returned.
query.setIsBatchExecutionSupported(false);
query.copyFromQuery(this.databaseQuery);
// Need to clone call, in case was executed as read.
query.setDatasourceCall((Call) this.databaseQuery.getDatasourceCall().clone());
this.databaseQuery = query;
}
/**
* Internal method to change the wrapped query to a DataModifyQuery if
* necessary. When created, the query is created as a DataReadQuery as it is
* unknown if it is a SELECT or UPDATE. Note that this prevents the original
* named query from every being prepared.
*/
protected void setAsSQLModifyQuery() {
if (getDatabaseQueryInternal().isDataReadQuery()) {
setAsDataModifyQuery();
}
}
/**
* Internal method to change the wrapped query to a DataReadQuery if
* necessary. This should never occur, but could possibly if the same query
* was executed as executeUpdate() then as getResultList(). Note that the
* initial conversion to modify would loose any read settings that had been
* set.
*/
protected void setAsSQLReadQuery() {
if (getDatabaseQueryInternal().isDataModifyQuery()) {
DataReadQuery query = new DataReadQuery();
query.setResultType(DataReadQuery.AUTO);
query.setIsUserDefined(databaseQuery.isUserDefined());
query.copyFromQuery(this.databaseQuery);
this.databaseQuery = query;
}
}
/**
* Execute a ReadQuery by assigning the stored parameter values and running
* it in the database
*
* @return the results of the query execution
*/
protected Object executeReadQuery() {
List parameterValues = processParameters();
// TODO: the following performFlush() call is a temporary workaround for
// bug 4752493:
// CTS: INMEMORY QUERYING IN EJBQUERY BROKEN DUE TO CHANGE TO USE
// REPORTQUERY.
// Ideally we should only flush in case the selectionExpression can't be
// conformed in memory.
// There are two alternative ways to implement that:
// 1. Try running the query with conformInUOW flag first - if it fails
// with
// QueryException.cannotConformExpression then flush and run the query
// again -
// now without conforming.
// 2. Implement a new isComformable method on Expression which would
// determine whether the expression
// could be conformed in memory, flush only in case it returns false.
// Note that doesConform method currently implemented on Expression
// requires object(s) to be confirmed as parameter(s).
// The new isComformable method should not take any objects as
// parameters,
// it should return false if there could be such an object that
// passed to doesConform causes it to throw
// QueryException.cannotConformExpression -
// and true otherwise.
boolean shouldResetConformResultsInUnitOfWork = false;
DatabaseQuery query = getDatabaseQueryInternal();
boolean isObjectLevelReadQuery = query.isObjectLevelReadQuery();
if (isFlushModeAUTO() && (!isObjectLevelReadQuery || !((ObjectLevelReadQuery)query).isReadOnly())) {
performPreQueryFlush();
if (isObjectLevelReadQuery) {
if (((ObjectLevelReadQuery)query).shouldConformResultsInUnitOfWork()) {
cloneSharedQuery();
query = getDatabaseQueryInternal();
((ObjectLevelReadQuery)query).setCacheUsage(ObjectLevelReadQuery.UseDescriptorSetting);
shouldResetConformResultsInUnitOfWork = true;
}
}
}
// Set a pessimistic locking on the query if specified.
if (this.lockMode != null && !this.lockMode.equals(LockModeType.NONE)) {
// We need to throw TransactionRequiredException if there is no
// active transaction
this.entityManager.checkForTransaction(true);
// The lock mode setters and getters validate the query type
// so should be safe to make the casting.
cloneSharedQuery();
query = getDatabaseQueryInternal();
// Set the lock mode (the session is passed in to do some validation
// checks)
// If the return value from the set returns true, it indicates that
// we were unable to set the lock mode.
if (((ObjectLevelReadQuery)query).setLockModeType(lockMode.name(), (AbstractSession) getActiveSession())) {
throw new PersistenceException(ExceptionLocalization.buildMessage("ejb30-wrong-lock_called_without_version_locking-index", null));
}
}
Session session = getActiveSession();
try {
// in case it's a user-defined query
if (query.isUserDefined()) {
// and there is an active transaction
if (this.entityManager.checkForTransaction(false) != null) {
// verify whether uow has begun early transaction
if (session.isUnitOfWork() && !((UnitOfWorkImpl)session).wasTransactionBegunPrematurely()) {
// uow begins early transaction in case it hasn't
// already begun.
// TODO: This is not good, it means that no SQL queries
// can ever use the cache,
// using isUserDefined to mean an SQL query is also
// wrong.
((UnitOfWorkImpl)session).beginEarlyTransaction();
}
}
}
// Execute the query and return the result.
return session.executeQuery(query, parameterValues);
} catch (DatabaseException e) {
throw getDetailedException(e);
} catch (RuntimeException e) {
setRollbackOnly();
throw e;
} finally {
this.lockMode = null;
if (shouldResetConformResultsInUnitOfWork) {
((ObjectLevelReadQuery)query).conformResultsInUnitOfWork();
}
}
}
/**
* Execute an update or delete statement.
*
* @return the number of entities updated or deleted
*/
public int executeUpdate() {
// bug51411440: need to throw IllegalStateException if query
// executed on closed em
this.entityManager.verifyOpenWithSetRollbackOnly();
try {
setAsSQLModifyQuery();
// bug:4294241, only allow modify queries - UpdateAllQuery preferred
if (!(getDatabaseQueryInternal() instanceof ModifyQuery)) {
throw new IllegalStateException(ExceptionLocalization.buildMessage("incorrect_query_for_execute_update"));
}
// need to throw TransactionRequiredException if there is no active
// transaction
entityManager.checkForTransaction(true);
// fix for bug:4288845, did not add the parameters to the query
List parameterValues = processParameters();
if (isFlushModeAUTO()) {
performPreQueryFlush();
}
Integer changedRows = (Integer) getActiveSession().executeQuery(databaseQuery, parameterValues);
return changedRows.intValue();
} catch (PersistenceException exception) {
setRollbackOnly();
throw exception;
} catch (IllegalStateException exception) {
setRollbackOnly();
throw exception;
}catch (RuntimeException exception) {
setRollbackOnly();
throw new PersistenceException(exception);
}
}
/**
* Return the wrapped {@link DatabaseQuery} ensuring that if it
* {@link #isShared} it is cloned before returning to prevent corruption of
* the query cache.
*
* @see #getDatabaseQueryInternal()
*/
public DatabaseQuery getDatabaseQuery() {
cloneSharedQuery();
return getDatabaseQueryInternal();
}
/**
* INTERNAL: Return the cached database query for this EJBQueryImpl. If the
* query is a named query and it has not yet been looked up, the query will
* be looked up and stored as the cached query.
*/
public DatabaseQuery getDatabaseQueryInternal() {
if ((this.queryName != null) && (this.databaseQuery == null)) {
// Always ask for the query from the active session. Table per
// tenant multitenant entity queries may be isolated per EM meaning
// those queries will not have been initialized (and made available)
// from their parent session.
this.databaseQuery = this.entityManager.getActiveSessionIfExists().getQuery(this.queryName);
// need error checking and appropriate exception for non-existing query
if (this.databaseQuery != null) {
if (!this.databaseQuery.isPrepared()) {
// prepare the query before cloning, this ensures we do not
// have to continually prepare on each usage
try {
this.databaseQuery.checkPrepare(this.entityManager.getActiveSessionIfExists(), new DatabaseRecord());
} catch(RuntimeException re){
throw new IllegalArgumentException(re);
}
}
if (this.databaseQuery.isObjectLevelReadQuery() && ((ObjectLevelReadQuery)this.databaseQuery).getLockModeType() != null){
this.lockMode = LockModeType.valueOf(((ObjectLevelReadQuery)this.databaseQuery).getLockModeType());
}
if (this.databaseQuery.isReadQuery()){
this.maxResults = ((ReadQuery)this.databaseQuery).getInternalMax();
// Bug 501272
// Do not reset a Query's uninitialized first result index, unless the parameter is greater than 0 (default for ReadQuery).
int queryFirstResult = ((ReadQuery)this.databaseQuery).getFirstResult();
if ((this.firstResultIndex != UNDEFINED) || (this.firstResultIndex == UNDEFINED && queryFirstResult > 0)) {
this.firstResultIndex = queryFirstResult;
}
}
} else {
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("unable_to_find_named_query", new Object[] { this.queryName }));
}
}
return this.databaseQuery;
}
/**
* Given a DatabaseException, this method will determine if we should
* throw a different more specific exception like a lock timeout exception.
*/
protected RuntimeException getDetailedException(DatabaseException e) {
// If we catch a database exception as a result of executing a
// pessimistic locking query we need to ask the platform which
// JPA 2.0 locking exception we should throw. It will be either
// be a PessimisticLockException or a LockTimeoutException (if
// the query was executed using a wait timeout value)
if (this.lockMode != null && this.lockMode.name().contains(ObjectLevelReadQuery.PESSIMISTIC_)) {
// ask the platform if it is a lock timeout
if (getActiveSession().getPlatform().isLockTimeoutException(e)) {
return new LockTimeoutException(e);
} else {
return new PessimisticLockException(e);
}
} else {
setRollbackOnly();
return new PersistenceException(e);
}
}
/**
* Return the entityManager this query is tied to.
*/
public JpaEntityManager getEntityManager() {
return entityManager;
}
/**
* Return the internal map of parameters.
*/
protected Map> getInternalParameters() {
if (this.parameters == null) {
this.parameters = new HashMap>();
DatabaseQuery query = getDatabaseQueryInternal(); // Retrieve named
// query
int count = 0;
if (query.getArguments() != null && !query.getArguments().isEmpty()) {
boolean checkParameterType = query.getArgumentParameterTypes().size() == query.getArguments().size();
for (String argName : query.getArguments()) {
Parameter> param = null;
org.eclipse.persistence.queries.DatabaseQuery.ParameterType type = null;
if (checkParameterType){
type = query.getArgumentParameterTypes().get(count);
}
if (type == org.eclipse.persistence.queries.DatabaseQuery.ParameterType.POSITIONAL){
Integer position = Integer.parseInt(argName);
param = new ParameterExpressionImpl(null, query.getArgumentTypes().get(count), position);
} else {
param = new ParameterExpressionImpl(null, query.getArgumentTypes().get(count), argName);
}
this.parameters.put(argName, param);
++count;
}
}
}
return this.parameters;
}
/**
* Get the current lock mode for the query.
*
* @return lock mode
* @throws IllegalStateException
* if not a Java Persistence query language SELECT query
*/
public LockModeType getLockMode() {
entityManager.verifyOpen();
if (!getDatabaseQueryInternal().isObjectLevelReadQuery()) {
throw new IllegalStateException(ExceptionLocalization.buildMessage("invalid_lock_query", (Object[]) null));
}
return this.lockMode;
}
/**
* Execute the query and return the query results as a List.
*
* @return a list of the results
*/
public List getResultList() {
// bug51411440: need to throw IllegalStateException if query
// executed on closed em
this.entityManager.verifyOpenWithSetRollbackOnly();
try {
setAsSQLReadQuery();
propagateResultProperties();
// bug:4297903, check container policy class and throw exception if
// its not the right type
DatabaseQuery query = getDatabaseQueryInternal();
if (query.isReadAllQuery()) {
Class containerClass = ((ReadAllQuery) query).getContainerPolicy().getContainerClass();
if (!Helper.classImplementsInterface(containerClass, ClassConstants.List_Class)) {
throw QueryException.invalidContainerClass(containerClass, ClassConstants.List_Class);
}
} else if (query.isReadObjectQuery()) {
List resultList = new ArrayList();
Object result = executeReadQuery();
if (result != null) {
resultList.add(result);
}
return resultList;
} else if (!query.isReadQuery()) {
throw new IllegalStateException(ExceptionLocalization.buildMessage("incorrect_query_for_get_result_list"));
}
return (List) executeReadQuery();
} catch (LockTimeoutException exception) {
throw exception;
} catch (PersistenceException exception) {
setRollbackOnly();
throw exception;
} catch (IllegalStateException exception) {
setRollbackOnly();
throw exception;
} catch (RuntimeException exception) {
setRollbackOnly();
throw new PersistenceException(exception);
}
}
/**
* Execute a SELECT query that returns a single untyped result.
*
* @return the result
* @throws NoResultException if there is no result
* @throws NonUniqueResultException if more than one result
* @throws IllegalStateException if called for a Java Persistence query
* language UPDATE or DELETE statement
* @throws QueryTimeoutException if the query execution exceeds the query
* timeout value set and only the statement is rolled back
* @throws TransactionRequiredException if a lock mode other than NONE has
* been been set and there is no transaction or the persistence
* context has not been joined to the transaction
* @throws PessimisticLockException if pessimistic locking fails and the
* transaction is rolled back
* @throws LockTimeoutException if pessimistic locking fails and only the
* statement is rolled back
* @throws PersistenceException if the query execution exceeds the query
* timeout value set and the transaction is rolled back
*/
public Object getSingleResult() {
boolean rollbackOnException = true;
// bug51411440: need to throw IllegalStateException if query
// executed on closed em
this.entityManager.verifyOpenWithSetRollbackOnly();
try {
setAsSQLReadQuery();
propagateResultProperties();
// This API is used to return non-List results, so no other validation is done.
// It could be Cursor or other Collection or Map type.
if (!(getDatabaseQueryInternal().isReadQuery())) {
throw new IllegalStateException(ExceptionLocalization.buildMessage("incorrect_query_for_get_single_result"));
}
Object result = executeReadQuery();
if (result instanceof List) {
List results = (List) result;
if (results.isEmpty()) {
rollbackOnException = false;
throwNoResultException(ExceptionLocalization.buildMessage("no_entities_retrieved_for_get_single_result", (Object[]) null));
} else if (results.size() > 1) {
rollbackOnException = false;
throwNonUniqueResultException(ExceptionLocalization.buildMessage("too_many_results_for_get_single_result", (Object[]) null));
}
return results.get(0);
} else {
if (result == null) {
rollbackOnException = false;
throwNoResultException(ExceptionLocalization.buildMessage("no_entities_retrieved_for_get_single_result", (Object[]) null));
}
return result;
}
} catch (LockTimeoutException exception) {
throw exception;
} catch (PersistenceException exception) {
if (rollbackOnException) {
setRollbackOnly();
}
throw exception;
} catch (IllegalStateException exception) {
setRollbackOnly();
throw exception;
} catch (RuntimeException exception) {
setRollbackOnly();
throw new PersistenceException(exception);
}
}
/**
* Internal method to add the parameters values to the query prior to
* execution. Returns a list of parameter values in the order the parameters
* are defined for the databaseQuery.
*/
protected List