| /* |
| * 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 |
| package org.eclipse.persistence.queries; |
| |
| import java.util.*; |
| import org.eclipse.persistence.exceptions.*; |
| import org.eclipse.persistence.expressions.*; |
| import org.eclipse.persistence.descriptors.DescriptorEvent; |
| import org.eclipse.persistence.descriptors.DescriptorEventManager; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| |
| /** |
| * <p><b>Purpose</b>: |
| * Query used to delete a collection of objects. |
| * This is used by mappings to delete all of their target objects in a single database call. |
| * The SQL/SQLStatements must be provided. |
| * <p> |
| * DeleteAll can also be used with an Expression (or JPQL) to dynamically delete |
| * a set of objects from the database, and invalidate them in the cache. |
| * |
| * <p><b>Responsibilities</b>: |
| * <ul> |
| * <li> Stores {@literal &} retrieves the objects to delete. |
| * <li> Store the where clause used for the deletion. |
| * </ul> |
| * |
| * @author Yvon Lavoie |
| * @since TOPLink/Java 1.0 |
| */ |
| public class DeleteAllQuery extends ModifyAllQuery { |
| |
| /** List containing objects to be deleted, these should be removed from the identity map after deletion. */ |
| protected List<Object> objects; |
| |
| /** |
| * Defines if objects should be remove from the persistence context only (no database). |
| * This is used if delete was already cascaded by the database. |
| */ |
| protected boolean isInMemoryOnly; |
| |
| /** |
| * PUBLIC: |
| */ |
| public DeleteAllQuery() { |
| super(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Create a new delete all query for the class specified. |
| */ |
| public DeleteAllQuery(Class referenceClass) { |
| super(referenceClass); |
| } |
| |
| /** |
| * PUBLIC: |
| * Create a new delete all query for the class and the selection criteria |
| * specified. |
| */ |
| public DeleteAllQuery(Class referenceClass, Expression selectionCriteria) { |
| super(referenceClass, selectionCriteria); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if objects should be remove from the persistence context only (no database). |
| * This is used if delete was already cascaded by the database. |
| */ |
| public boolean isInMemoryOnly() { |
| return isInMemoryOnly; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set if objects should be remove from the persistence context only (no database). |
| * This is used if delete was already cascaded by the database. |
| */ |
| public void setIsInMemoryOnly(boolean isInMemoryOnly) { |
| this.isInMemoryOnly = isInMemoryOnly; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return if this is a delete all query. |
| */ |
| @Override |
| public boolean isDeleteAllQuery() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * This method has to be broken. If commit manager is not active either |
| * an exception should be thrown (ObjectLevelModify case), or a transaction |
| * should be started early and execute on parent if remote (dataModify case). |
| * A modify query is NEVER executed on the parent, unless remote session. |
| */ |
| @Override |
| public Object executeInUnitOfWork(UnitOfWorkImpl unitOfWork, AbstractRecord translationRow) throws DatabaseException { |
| if (this.objects != null) { |
| if (unitOfWork.isAfterWriteChangesButBeforeCommit()) { |
| throw ValidationException.illegalOperationForUnitOfWorkLifecycle(unitOfWork.getLifecycle(), "executeQuery(DeleteAllQuery)"); |
| } |
| |
| // This must be broken, see comment. |
| if (!unitOfWork.getCommitManager().isActive()) { |
| return unitOfWork.getParent().executeQuery(this, translationRow); |
| } |
| result = (Integer)super.execute(unitOfWork, translationRow); |
| return result; |
| } else { |
| return super.executeInUnitOfWork(unitOfWork, translationRow); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Perform the work to delete a collection of objects. |
| * This skips the optimistic lock check and should not called for objects using locking. |
| * @exception DatabaseException - an error has occurred on the database. |
| * @return Integer the number of objects (rows) deleted. |
| */ |
| @Override |
| public Object executeDatabaseQuery() throws DatabaseException { |
| // CR# 4286 |
| if (this.objects != null) { |
| |
| if (isExpressionQuery() && getSelectionCriteria() == null) { |
| // DeleteAllQuery has objects so it *must* have selectionCriteria, too |
| throw QueryException.deleteAllQuerySpecifiesObjectsButNotSelectionCriteria(getDescriptor(), this, this.objects.toString()); |
| } |
| |
| // Optimistic lock check not required because objects are deleted individually in that case. |
| try { |
| this.session.beginTransaction(); |
| |
| // Need to run pre-delete selector if available. |
| // PERF: Avoid events if no listeners. |
| if (this.descriptor.getEventManager().hasAnyEventListeners()) { |
| for (Object object : this.objects) { |
| DescriptorEvent event = new DescriptorEvent(object); |
| event.setEventCode(DescriptorEventManager.PreDeleteEvent); |
| event.setSession(this.session); |
| event.setQuery(this); |
| this.descriptor.getEventManager().executeEvent(event); |
| } |
| } |
| |
| if (this.isInMemoryOnly) { |
| result = 0; |
| } else { |
| result = this.queryMechanism.deleteAll(); |
| } |
| |
| // Need to run post-delete selector if available. |
| // PERF: Avoid events if no listeners. |
| if (this.descriptor.getEventManager().hasAnyEventListeners()) { |
| for (Object object : this.objects) { |
| DescriptorEvent event = new DescriptorEvent(object); |
| event.setEventCode(DescriptorEventManager.PostDeleteEvent); |
| event.setSession(this.session); |
| event.setQuery(this); |
| this.descriptor.getEventManager().executeEvent(event); |
| } |
| } |
| |
| if (shouldMaintainCache()) { |
| // remove from the cache. |
| for (Object deleted : this.objects) { |
| if (this.session.isUnitOfWork()) { |
| //BUG #2612169: Unwrap is needed |
| deleted = this.descriptor.getObjectBuilder().unwrapObject(deleted, getSession()); |
| ((UnitOfWorkImpl)this.session).addObjectDeletedDuringCommit(deleted, this.descriptor); |
| } else { |
| this.session.getIdentityMapAccessor().removeFromIdentityMap(deleted); |
| } |
| } |
| } |
| |
| this.session.commitTransaction(); |
| |
| } catch (RuntimeException exception) { |
| this.session.rollbackTransaction(); |
| throw exception; |
| } |
| } else { |
| if (this.isInMemoryOnly) { |
| result = 0; |
| } else { |
| result = this.queryMechanism.deleteAll();// fire the SQL to the database |
| } |
| mergeChangesIntoSharedCache(); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * INTERNAL: |
| * Delete all queries are executed specially to avoid cloning and ensure preparing. |
| */ |
| public void executeDeleteAll(AbstractSession session, AbstractRecord translationRow, Vector objects) throws DatabaseException { |
| this.checkPrepare(session, translationRow); |
| DeleteAllQuery queryToExecute = (DeleteAllQuery)clone(); |
| |
| // Then prepare for the single execution. |
| queryToExecute.setTranslationRow(translationRow); |
| queryToExecute.setSession(session); |
| queryToExecute.setObjects(objects); |
| queryToExecute.prepareForExecution(); |
| queryToExecute.executeDatabaseQuery(); |
| } |
| |
| /** |
| * 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(){ |
| // 364001 - Do not return redirector if objects list is null. |
| if (objects == null) { |
| return null; |
| } |
| |
| return descriptor.getDefaultDeleteObjectQueryRedirector(); |
| } |
| |
| |
| /** |
| * PUBLIC: |
| * Return the objects that are to be deleted |
| */ |
| public List<Object> getObjects() { |
| return objects; |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare the receiver for execution in a session. |
| */ |
| @Override |
| protected void prepare() throws QueryException { |
| super.prepare(); |
| |
| if (getReferenceClass() == null) { |
| throw QueryException.referenceClassMissing(this); |
| } |
| |
| if (getDescriptor() == null) { |
| ClassDescriptor referenceDescriptor = getSession().getDescriptor(getReferenceClass()); |
| if (referenceDescriptor == null) { |
| throw QueryException.descriptorIsMissing(getReferenceClass(), this); |
| } |
| setDescriptor(referenceDescriptor); |
| } |
| |
| if (getDescriptor().isAggregateDescriptor()) { |
| throw QueryException.aggregateObjectCannotBeDeletedOrWritten(getDescriptor(), this); |
| } |
| |
| getQueryMechanism().prepareDeleteAll(); |
| } |
| |
| /** |
| * PUBLIC (REQUIRED): |
| * Set the objects to be deleted. |
| * Also REQUIRED is a selection criteria or SQL string that performs the deletion of the objects. |
| * This does not generate the SQL call from the deleted objects. |
| * <p> |
| * List objects used as an indicator of one of two possible |
| * ways the query may behave: |
| * <p> objects != null - the "old" functionality used by OneToMany mapping |
| * objects deleted from the cache, either selection expression or custom sql |
| * should be provided for deletion from db; |
| * <p> objects == null - the "new" functionality (on par with UpdateAllQuery) |
| * the cache is either left alone or in-memory query finds the cached objects to be deleted, |
| * and these objects are invalidated in cache. |
| * <p> |
| * Note that empty objects is still objects != case. |
| * Signal that no cache altering is required. |
| * Used by AggregationCollectionMapping and OneToManyMapping in case they use indirection |
| * and the ValueHolder has not been instantiated. |
| */ |
| public void setObjects(List<Object> objectCollection) { |
| this.objects = objectCollection; |
| } |
| } |