org.eclipse.persistence.internal.queries.DatabaseQueryMechanism Maven / Gradle / Ivy
Show all versions of eclipselink Show documentation
/*
* Copyright (c) 1998, 2024 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
// 07/13/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
// 09/12/2018 - Will Dazey
// - 391279: Add support for Unidirectional OneToMany mappings with non-nullable values
package org.eclipse.persistence.internal.queries;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.descriptors.DescriptorEventManager;
import org.eclipse.persistence.descriptors.DescriptorQueryManager;
import org.eclipse.persistence.descriptors.VersionLockingPolicy;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.OptimisticLockException;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor;
import org.eclipse.persistence.internal.databaseaccess.DatabaseCall;
import org.eclipse.persistence.internal.databaseaccess.DatasourceCall;
import org.eclipse.persistence.internal.descriptors.OptimisticLockingPolicy;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.CommitManager;
import org.eclipse.persistence.internal.sessions.ObjectChangeSet;
import org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork;
import org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.mappings.DatabaseMapping.WriteType;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.queries.DoesExistQuery;
import org.eclipse.persistence.queries.ModifyQuery;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
import org.eclipse.persistence.queries.ReadObjectQuery;
import org.eclipse.persistence.queries.WriteObjectQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.tools.profiler.QueryMonitor;
import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
/**
* Purpose:
* Abstract class for all database query mechanism objects.
* DatabaseQueryMechanism is actually a helper class and currently is required
* for all types of queries. Most of the work performed by the query framework is
* performed in the query mechanism. The query mechanism contains the internal
* knowledge necessary to perform the specific database operation.
*
* Responsibilities:
* Provide a common protocol for query mechanism objects.
* Provides all of the database specific work for the assigned query.
*
* @author Yvon Lavoie
* @since TOPLink/Java 1.0
*/
public abstract class DatabaseQueryMechanism implements Cloneable, Serializable {
/** The database query that uses this mechanism. */
protected DatabaseQuery query;
/**
* Initialize the state of the query.
*/
protected DatabaseQueryMechanism() {
}
/**
* Initialize the state of the query
* @param query - owner of mechanism
*/
protected DatabaseQueryMechanism(DatabaseQuery query) {
this.query = query;
}
/**
* Add the initial write lock value to the row for insert.
*/
protected void addWriteLockFieldForInsert() {
if (getDescriptor().usesOptimisticLocking()) {
getDescriptor().getOptimisticLockingPolicy().setupWriteFieldsForInsert(getWriteObjectQuery());
}
}
/**
* Internal:
* In the case of EJBQL, an expression needs to be generated. Build the required expression.
*/
public void buildSelectionCriteria(AbstractSession session) {
// Default is do nothing
}
/**
* Perform a cache lookup for the query. If the translation row contains
* all the parameters (which are part of the primary key) from the prepared
* call, then a cache check will be performed.
*
* If the object is found in the cache, return it; otherwise return null.
*/
public Object checkCacheForObject(AbstractRecord translationRow, AbstractSession session) {
// Null check added for CR#4295 - TW
if ((translationRow == null) || (translationRow.isEmpty())) {
return null;
}
// Bug 5529564
// The query wasn't prepared most likely because arguments were
// provided. Use them for the cache lookup.
List queryFields;
if (query.getCall() == null) {
// TODO: This is a bug, arguments is a list of Strings, not fields.
// Also if the call was null, it would be an expression, if it was not
// prepared the parameters would be empty.
queryFields = query.getArguments();
} else {
// The query was prepared. Use the parameters of the call to look
// up the object in cache.
queryFields = query.getCall().getParameters();
}
ClassDescriptor descriptor = getDescriptor();
List primaryKeyFields = descriptor.getPrimaryKeyFields();
// Check that the query is by primary key.
for (DatabaseField primaryKeyField : primaryKeyFields) {
if (!queryFields.contains(primaryKeyField)) {
return null;
}
}
Object primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromRow(translationRow, session);
if (primaryKey == null) {
return null;
}
if (query.isObjectBuildingQuery() && ((ObjectBuildingQuery)query).requiresDeferredLocks() || descriptor.shouldAcquireCascadedLocks()) {
return session.getIdentityMapAccessorInstance().getFromIdentityMapWithDeferredLock(primaryKey, getReadObjectQuery().getReferenceClass(), false, descriptor);
} else {
return session.getIdentityMapAccessorInstance().getFromLocalIdentityMap(primaryKey, getReadObjectQuery().getReferenceClass(), false, descriptor);
}
}
/**
* Clone the mechanism
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
/**
* Clone the mechanism for the specified query clone.
*/
public DatabaseQueryMechanism clone(DatabaseQuery queryClone) {
DatabaseQueryMechanism clone = (DatabaseQueryMechanism)clone();
clone.setQuery(queryClone);
return clone;
}
/**
* Read all rows from the database using a cursored stream.
* @exception DatabaseException - an error has occurred on the database
*/
public abstract DatabaseCall cursorSelectAllRows() throws DatabaseException;
/**
* Delete a collection of objects
* This should be overridden by subclasses.
* @exception DatabaseException - an error has occurred on the database
*/
public boolean isJPQLCallQueryMechanism() {
return false;
}
public abstract Integer deleteAll() throws DatabaseException;
/**
* Delete an object
* This should be overridden by subclasses.
* @return the row count.
*/
public abstract Integer deleteObject() throws DatabaseException;
/**
* Execute a execute SQL call.
* This should be overridden by subclasses.
* @return true if the first result is a result set and false if it is an
* update count or there are no results other than through INOUT and OUT
* parameterts, if any.
*/
public abstract Object execute() throws DatabaseException;
/**
* Execute a non selecting SQL call
* This should be overridden by subclasses.
* @return the row count.
*/
public abstract Object executeNoSelect() throws DatabaseException;
/**
* Execute a non selecting SQL call, that returns the generated keys
* This should be overridden by subclasses.
* @exception DatabaseException
*/
public abstract DatabaseCall generateKeysExecuteNoSelect() throws DatabaseException;
/**
* Execute a select SQL call and return the rows.
* This should be overriden by subclasses.
*/
public abstract Vector executeSelect() throws DatabaseException;
/**
* Check whether the object already exists on the database; then
* perform an insert or update, as appropriate.
* This write is used for non-unit of work (old-commit) operations.
* Return the object being written.
*/
public Object executeWrite() throws DatabaseException, OptimisticLockException {
WriteObjectQuery writeQuery = getWriteObjectQuery();
Object object = writeQuery.getObject();
CommitManager commitManager = getSession().getCommitManager();
// if the object has already been committed, no work is required
if (commitManager.isCommitCompletedInPostOrIgnore(object)) {
return object;
}
// check whether the object is already being committed -
// if it is and it is new, then a shallow insert must be done
if (commitManager.isCommitInPreModify(object)) {
shallowInsertObjectForWrite(object, writeQuery, commitManager);
return object;
}
try {
getSession().beginTransaction();
if (writeQuery.getObjectChangeSet() == null) {
// PERF: Avoid events if no listeners.
if (getDescriptor().getEventManager().hasAnyEventListeners()) {
// only throw the events if there is no changeset otherwise the event will be thrown twice
// once by the calculate changes code and here
getDescriptor().getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PreWriteEvent, writeQuery));
}
}
writeQuery.executeCommit();
// PERF: Avoid events if no listeners.
if (getDescriptor().getEventManager().hasAnyEventListeners()) {
getDescriptor().getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PostWriteEvent, writeQuery));
}
getSession().commitTransaction();
// notify the commit manager of the completion to the commit
commitManager.markCommitCompleted(object);
return object;
} catch (RuntimeException exception) {
getSession().rollbackTransaction();
commitManager.markCommitCompleted(object);
throw exception;
}
}
/**
* Execute the call that was deferred to the commit manager.
* This is used to allow multiple table batching and deadlock avoidance.
*/
public void executeDeferredCall(DatasourceCall call) {
// Do nothing by default.
}
/**
* Check whether the object already exists on the cadatabase; then
* perform an insert or update, as appropriate.
* This method was moved here, from WriteObjectQuery.execute(),
* so we can hide the source.
* Return the object being written.
*/
public Object executeWriteWithChangeSet() throws DatabaseException, OptimisticLockException {
WriteObjectQuery writeQuery = getWriteObjectQuery();
ObjectChangeSet objectChangeSet = writeQuery.getObjectChangeSet();
ClassDescriptor descriptor = getDescriptor();
AbstractSession session = getSession();
CommitManager commitManager = session.getCommitManager();
Object object = writeQuery.getObject();
// If there are no changes then there is no work required
// Check for forcedUpdate Version and Optimistic read lock (hasForcedChanges() set in ObjectChangePolicy)
if (!objectChangeSet.hasChanges() && !objectChangeSet.hasForcedChanges()) {
commitManager.markCommitCompleted(object);
return object;
}
// If the object has already been committed, no work is required
// need to check for the object to ensure insert wasn't completed already.
if (commitManager.isCommitCompletedInPostOrIgnore(object)) {
return object;
}
try {
writeQuery.executeCommitWithChangeSet();
// PERF: Avoid events if no listeners.
if (descriptor.getEventManager().hasAnyEventListeners()) {
descriptor.getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PostWriteEvent, writeQuery));
}
// Notify the commit manager of the completion to the commit.
commitManager.markCommitCompleted(object);
return object;
} catch (RuntimeException exception) {
commitManager.markCommitCompleted(object);
throw exception;
}
}
/**
* Convenience method
*/
protected ClassDescriptor getDescriptor() {
return this.query.getDescriptor();
}
/**
* Convenience method
*/
public AbstractRecord getModifyRow() {
if (this.query.isModifyQuery()) {
return ((ModifyQuery)this.query).getModifyRow();
} else {
return null;
}
}
/**
* Return the query that uses the mechanism.
*/
public DatabaseQuery getQuery() {
return query;
}
/**
* Convenience method
*/
protected ReadObjectQuery getReadObjectQuery() {
return (ReadObjectQuery)this.query;
}
/**
* Return the selection criteria for the mechanism.
* By default this is null. This method exists because both statement and expression
* mechanisms use an expression and some code in the mappings depends on returning this.
*/
public Expression getSelectionCriteria() {
return null;
}
/**
* Convenience method
*/
protected AbstractSession getSession() {
return this.query.getSession();
}
/**
* Convenience method
*/
protected AbstractSession getExecutionSession() {
return this.query.getExecutionSession();
}
/**
* Convenience method
*/
protected AbstractRecord getTranslationRow() {
return this.query.getTranslationRow();
}
/**
* Convenience method
*/
protected WriteObjectQuery getWriteObjectQuery() {
return (WriteObjectQuery)this.query;
}
/**
* Insert an object.
*/
public abstract void insertObject() throws DatabaseException;
/**
* Insert an object and provide the opportunity to reprepare prior to the insert.
* This will be overridden
* CR#3237
*/
public void insertObject(boolean reprepare) {
insertObject();
}
/**
* Insert an object in the database.
* This is used for both uow and non-uow (old-commit and change-set) operations.
*/
public void insertObjectForWrite() {
WriteObjectQuery writeQuery = getWriteObjectQuery();
ClassDescriptor descriptor = getDescriptor();
DescriptorQueryManager queryManager = descriptor.getQueryManager();
boolean isFKUpdate = false; // Bug 319276
// check for user-defined query
if ((!writeQuery.isUserDefined())// this is not a user-defined query
&& queryManager.hasInsertQuery()// there is a user-defined query
&& isExpressionQueryMechanism()) {// this is not a hand-coded call (custom SQL etc.)
performUserDefinedInsert();
return;
}
Object object = writeQuery.getObject();
AbstractSession session = writeQuery.getSession();
ObjectChangeSet changeSet = writeQuery.getObjectChangeSet();
CommitManager commitManager = session.getCommitManager();
DescriptorEventManager eventManager = descriptor.getEventManager();
// This must be done after the custom query check, otherwise it will be done twice.
commitManager.markPreModifyCommitInProgress(object);
if (changeSet == null) {
// PERF: Avoid events if no listeners.
if (eventManager.hasAnyEventListeners()) {
// only throw the events if there is no changeset otherwise the event will be thrown twice
// once by the calculate changes code and here
eventManager.executeEvent(new DescriptorEvent(DescriptorEventManager.PreInsertEvent, writeQuery));
}
}
// check whether deep shallow modify is turned on
if (writeQuery.shouldCascadeParts()) {
queryManager.preInsert(writeQuery);
}
// In a unit of work/writeObjects the preInsert may have caused a shallow insert of this object,
// in this case this second write must do an update.
if (commitManager.isShallowCommitted(object)) {
isFKUpdate = true; // Bug 319276
updateForeignKeyFieldAfterInsert();
} else {
AbstractRecord modifyRow = writeQuery.getModifyRow();
if (modifyRow == null) {// Maybe have been passed in as in aggregate collection.
if (writeQuery.shouldCascadeParts()) {
writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRow(object, session, WriteType.INSERT));
} else {
writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRowForShallowInsert(object, session));
}
} else {
if (writeQuery.shouldCascadeParts()) {
writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRow(modifyRow, object, session, WriteType.INSERT));
} else {
writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRowForShallowInsert(modifyRow, object, session));
}
}
modifyRow = getModifyRow();
// the modify row and the translation row are the same for insert
writeQuery.setTranslationRow(modifyRow);
if (!descriptor.isAggregateCollectionDescriptor()) {// Should/cannot be recomputed in aggregate collection.
writeQuery.setPrimaryKey(descriptor.getObjectBuilder().extractPrimaryKeyFromObject(object, session));
}
addWriteLockFieldForInsert();
if (descriptor.hasSerializedObjectPolicy()) {
descriptor.getSerializedObjectPolicy().putObjectIntoRow(modifyRow, object, session);
}
// CR#3237
// Store the size of the modify row so we can determine if the user has added to the row in the insert.
int modifyRowSize = modifyRow.size();
// PERF: Avoid events if no listeners.
if (eventManager.hasAnyEventListeners()) {
DescriptorEvent event = new DescriptorEvent(DescriptorEventManager.AboutToInsertEvent, writeQuery);
event.setRecord(modifyRow);
eventManager.executeEvent(event);
}
if (QueryMonitor.shouldMonitor()) {
QueryMonitor.incrementInsert(writeQuery);
}
// CR#3237
// Call insert with a boolean that tells it to reprepare if the user has altered the modify row.
insertObject(modifyRowSize != modifyRow.size());
// register the object before post insert to resolve possible cycles
registerObjectInIdentityMap(object, descriptor, session);
}
commitManager.markPostModifyCommitInProgress(object);
// Verify if deep shallow modify is turned on.
if (writeQuery.shouldCascadeParts()) {
queryManager.postInsert(writeQuery);
}
if ((descriptor.getHistoryPolicy() != null) && descriptor.getHistoryPolicy().shouldHandleWrites()) {
if (isFKUpdate) { // Bug 319276
descriptor.getHistoryPolicy().postUpdate(writeQuery, true);
}
else {
descriptor.getHistoryPolicy().postInsert(writeQuery);
}
}
// PERF: Avoid events if no listeners.
if (eventManager.hasAnyEventListeners()) {
eventManager.executeEvent(new DescriptorEvent(DescriptorEventManager.PostInsertEvent, writeQuery));
}
}
/**
* Return true if this is a call query mechanism
*/
public boolean isCallQueryMechanism() {
return false;
}
/**
* Return true if this is an expression query mechanism
*/
public boolean isExpressionQueryMechanism() {
return false;
}
/**
* Return true if this is a query by example mechanism
*/
public boolean isQueryByExampleMechanism() {
return false;
}
/**
* Return true if this is a statement query mechanism
*/
public boolean isStatementQueryMechanism() {
return false;
}
/**
* Insert the object using the user defined query.
* This ensures that the query is cloned and prepared correctly.
*/
protected void performUserDefinedInsert() {
performUserDefinedWrite(getDescriptor().getQueryManager().getInsertQuery());
}
/**
* Update the object using the user defined query.
* This ensures that the query is cloned and prepared correctly.
*/
protected void performUserDefinedUpdate() {
performUserDefinedWrite(getDescriptor().getQueryManager().getUpdateQuery());
}
/**
* Write the object using the specified user-defined query.
* This ensures that the query is cloned and prepared correctly.
*/
protected void performUserDefinedWrite(WriteObjectQuery userDefinedWriteQuery) {
WriteObjectQuery query = getWriteObjectQuery();
userDefinedWriteQuery.checkPrepare(query.getSession(), query.getTranslationRow());
WriteObjectQuery writeQuery = (WriteObjectQuery)userDefinedWriteQuery.clone();
writeQuery.setIsExecutionClone(true);
writeQuery.setObject(query.getObject());
writeQuery.setObjectChangeSet(query.getObjectChangeSet());
writeQuery.setCascadePolicy(query.getCascadePolicy());
writeQuery.setShouldMaintainCache(query.shouldMaintainCache());
writeQuery.setTranslationRow(query.getTranslationRow());
writeQuery.setModifyRow(query.getModifyRow());
writeQuery.setPrimaryKey(query.getPrimaryKey());
writeQuery.setSession(query.getSession());
// If there is a changeset, the change set method must be used.
if (writeQuery.getObjectChangeSet() != null) {
writeQuery.executeCommitWithChangeSet();
} else {
writeQuery.executeCommit();
}
}
/**
* This is different from 'prepareForExecution()'
* in that this is called on the original query,
* and the other is called on the clone of the query.
* This query is copied for concurrency so this prepare can only setup things that
* will apply to any future execution of this query.
*/
public void prepare() throws QueryException {
// the default is to do nothing
}
/**
* Pre-pare for a cursored execute.
* This is sent to the original query before cloning.
*/
public abstract void prepareCursorSelectAllRows() throws QueryException;
/**
* Prepare for a delete all.
* This is sent to the original query before cloning.
*/
public abstract void prepareDeleteAll() throws QueryException;
/**
* Prepare for a delete.
* This is sent to the original query before cloning.
*/
public abstract void prepareDeleteObject() throws QueryException;
/**
* Pre-pare for a select execute.
* This is sent to the original query before cloning.
*/
public abstract void prepareDoesExist(DatabaseField field) throws QueryException;
/**
* Prepare for a raw (non-object), non-selecting call.
* This is sent to the original query before cloning.
*/
public abstract void prepareExecuteNoSelect() throws QueryException;
/**
* Prepare for a raw execute call.
* This is sent to the original query before cloning.
*/
public abstract void prepareExecute() throws QueryException;
/**
* Prepare for a raw (non-object) select call.
* This is sent to the original query before cloning.
*/
public abstract void prepareExecuteSelect() throws QueryException;
/**
* Prepare for an insert.
* This is sent to the original query before cloning.
*/
public abstract void prepareInsertObject() throws QueryException;
/**
* Pre-pare for a select execute.
* This is sent to the original query before cloning.
*/
public abstract void prepareReportQuerySelectAllRows() throws QueryException;
/**
* Pre-pare a report query for a sub-select.
*/
public abstract void prepareReportQuerySubSelect() throws QueryException;
/**
* Prepare for a select returning (possibly) multiple rows.
* This is sent to the original query before cloning.
*/
public abstract void prepareSelectAllRows() throws QueryException;
/**
* Prepare for a select returning a single row.
* This is sent to the original query before cloning.
*/
public abstract void prepareSelectOneRow() throws QueryException;
/**
* Prepare for an update.
* This is sent to the original query before cloning.
*/
public abstract void prepareUpdateObject() throws QueryException;
/**
* Prepare for an update all.
* This is sent to the original query before cloning.
*/
public abstract void prepareUpdateAll() throws QueryException;
/**
* Store the query object in the identity map.
*/
protected void registerObjectInIdentityMap(Object object, ClassDescriptor descriptor, AbstractSession session) {
WriteObjectQuery query = getWriteObjectQuery();
if (query.shouldMaintainCache()) {
if (descriptor.usesOptimisticLocking()) {
Object optimisticLockValue = descriptor.getOptimisticLockingPolicy().getValueToPutInCache(query.getModifyRow(), session);
session.getIdentityMapAccessorInstance().putInIdentityMap(object, query.getPrimaryKey(), optimisticLockValue, System.currentTimeMillis(), descriptor);
} else {
session.getIdentityMapAccessorInstance().putInIdentityMap(object, query.getPrimaryKey(), null, System.currentTimeMillis(), descriptor);
}
}
}
/**
* INTERNAL:
* Read all rows from the database.
*/
public abstract Vector selectAllReportQueryRows() throws DatabaseException;
/**
* Read and return rows from the database.
*/
public abstract Vector selectAllRows() throws DatabaseException;
/**
* Read and return a row from the database.
*/
public abstract AbstractRecord selectOneRow() throws DatabaseException;
/**
* Read and return a row from the database for an existence check.
*/
public abstract AbstractRecord selectRowForDoesExist(DatabaseField field) throws DatabaseException;
/**
* Set the query that uses this mechanism.
*/
public void setQuery(DatabaseQuery query) {
this.query = query;
}
/**
* Shallow insert the specified object, if necessary.
*/
protected void shallowInsertObjectForWrite(Object object, WriteObjectQuery writeQuery, CommitManager commitManager) throws DatabaseException, OptimisticLockException {
boolean doesExist;
if (getSession().isUnitOfWork()) {
UnitOfWorkImpl uow = (UnitOfWorkImpl)getSession();
doesExist = !uow.isCloneNewObject(object);
if (doesExist) {
doesExist = uow.isObjectRegistered(object);
}
} else {
// clone and initialize the does exist query
DoesExistQuery existQuery = (DoesExistQuery)getDescriptor().getQueryManager().getDoesExistQuery().clone();
existQuery.setObject(object);
existQuery.setPrimaryKey(writeQuery.getPrimaryKey());
existQuery.setDescriptor(getDescriptor());
existQuery.setTranslationRow(getTranslationRow());
doesExist = (Boolean) getSession().executeQuery(existQuery);
}
if (!doesExist) {
// a shallow insert must be performed
writeQuery.dontCascadeParts();
insertObjectForWrite();
// mark this object as shallow committed so that the insert will do an update
commitManager.markShallowCommit(object);
}
}
/**
* Update the foreign key fields when resolving a bi-directonal reference in a UOW.
* This must always be dynamic as it is called within an insert query and is really part of the insert
* and does not fire update events or worry about locking.
*/
protected void updateForeignKeyFieldAfterInsert() {
WriteObjectQuery writeQuery = getWriteObjectQuery();
Object object = writeQuery.getObject();
writeQuery.setPrimaryKey(getDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(object, getSession()));
// reset the translation row because the insert has occurred and the id has
// been assigned to the object, but not the row
writeQuery.setTranslationRow(getDescriptor().getObjectBuilder().buildRowForTranslation(object, getSession()));
updateForeignKeyFieldAfterInsert(writeQuery);
}
/**
* Issue update SQL statement
*/
public abstract Integer updateAll() throws DatabaseException;
/**
* Update an object.
* Return the row count.
*/
public abstract Integer updateObject() throws DatabaseException;
/**
* Update the foreign key fields when resolving a bi-directonal reference in a UOW.
* This must always be dynamic as it is called within an insert query and is really part of the insert
* and does not fire update events or worry about locking.
*/
protected abstract void updateForeignKeyFieldAfterInsert(WriteObjectQuery writeQuery);
/**
* Update the foreign key fields to null when resolving a deletion cycle.
* This must always be dynamic as it is called within an delete query and is really part of the delete
* and does not fire update events or worry about locking.
*/
public void updateForeignKeyFieldBeforeDelete() {
// Nothing by default.
}
protected void updateObjectAndRowWithReturnRow(Collection returnFields, boolean isFirstCallForInsert) {
WriteObjectQuery writeQuery = getWriteObjectQuery();
AbstractRecord outputRow = (AbstractRecord)writeQuery.getProperties().get("output");
if ((outputRow == null) || outputRow.isEmpty()) {
return;
}
AbstractRecord row = new DatabaseRecord();
for (Iterator iterator = returnFields.iterator(); iterator.hasNext();) {
DatabaseField field = iterator.next();
if (outputRow.containsKey(field)) {
row.put(field, outputRow.get(field));
}
}
if (row.isEmpty()) {
return;
}
Object object = writeQuery.getObject();
ObjectChangeSet objectChangeSet = null;
if (getSession().isUnitOfWork()) {
objectChangeSet = writeQuery.getObjectChangeSet();
if ((objectChangeSet == null) && (((UnitOfWorkImpl)getSession()).getUnitOfWorkChangeSet() != null)) {
objectChangeSet = (ObjectChangeSet)((UnitOfWorkImpl)getSession()).getUnitOfWorkChangeSet().getObjectChangeSetForClone(object);
}
}
getDescriptor().getObjectBuilder().assignReturnRow(object, writeQuery.getSession(), row, objectChangeSet);
Object primaryKey = null;
if (isFirstCallForInsert) {
AbstractRecord pkToModify = new DatabaseRecord();
List primaryKeyFields = getDescriptor().getPrimaryKeyFields();
for (int i = 0; i < primaryKeyFields.size(); i++) {
DatabaseField field = primaryKeyFields.get(i);
if (row.containsKey(field)) {
pkToModify.put(field, row.get(field));
}
}
if (!pkToModify.isEmpty()) {
primaryKey = getDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(object, getSession());
writeQuery.setPrimaryKey(primaryKey);
// Now I need to update the row
getModifyRow().putAll(pkToModify);
getDescriptor().getObjectBuilder().addPrimaryKeyForNonDefaultTable(getModifyRow(), object, getSession());
}
}
if (objectChangeSet != null) {
if (primaryKey != null) {
objectChangeSet.setId(primaryKey);
}
}
}
/**
* Update the object's primary key by fetching a new sequence number from the accessor.
*/
protected void updateObjectAndRowWithSequenceNumber() throws DatabaseException {
WriteObjectQuery writeQuery = getWriteObjectQuery();
writeQuery.getDescriptor().getObjectBuilder().assignSequenceNumber(writeQuery);
}
/**
* Update the object's primary key by getting the generated keys from the call
* If there are no generated keys or the value is NULL, then default back to the {@link #updateObjectAndRowWithSequenceNumber()}
*/
protected void updateObjectAndRowWithSequenceNumber(DatabaseCall call) throws DatabaseException {
WriteObjectQuery writeQuery = getWriteObjectQuery();
AbstractSession session = writeQuery.getSession();
DatabaseAccessor dbAccessor = (DatabaseAccessor)writeQuery.getAccessor();
Object sequenceValue = null;
boolean exceptionOccured = false;
ResultSet resultSet = call.getGeneratedKeys();
try {
if(resultSet.next()) {
sequenceValue = resultSet.getObject(1);
}
if(sequenceValue != null) {
writeQuery.getDescriptor().getObjectBuilder().assignSequenceNumber(writeQuery, sequenceValue);
}
} catch (SQLException exception) {
exceptionOccured = true;
DatabaseException commException = dbAccessor.processExceptionForCommError(session, exception, call);
if (commException != null) {
throw commException;
}
throw DatabaseException.sqlException(exception, call, dbAccessor, session, false);
} catch (RuntimeException exception) {
exceptionOccured = true;
if (exception instanceof DatabaseException dbx) {
dbx.setCall(call);
if(dbx.getAccessor() == null) {
dbx.setAccessor(dbAccessor);
}
}
throw exception;
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException cleanupSQLException) {
if (!exceptionOccured) {
throw DatabaseException.sqlException(cleanupSQLException, call, dbAccessor, session, false);
}
} catch (RuntimeException cleanupException) {
if (!exceptionOccured) {
throw cleanupException;
}
}
}
// Fallback on original implementation if no value was found in the generated keys
if(sequenceValue == null) {
updateObjectAndRowWithSequenceNumber();
}
}
/**
* Update the object.
* This is only used for non-unit-of-work updates.
*/
public void updateObjectForWrite() {
WriteObjectQuery writeQuery = getWriteObjectQuery();
ClassDescriptor descriptor = getDescriptor();
DescriptorQueryManager queryManager = descriptor.getQueryManager();
// check for user-defined query
if ((!writeQuery.isUserDefined())// this is not a user-defined query
&& queryManager.hasUpdateQuery()// there is a user-defined query
&& isExpressionQueryMechanism()) {// this is not a hand-coded call (custom SQL etc.)
performUserDefinedUpdate();
return;
}
Object object = writeQuery.getObject();
AbstractSession session = getSession();
CommitManager commitManager = session.getCommitManager();
// This must be done after the custom query check, otherwise it will be done twice.
commitManager.markPreModifyCommitInProgress(object);
DescriptorEventManager eventManager = descriptor.getEventManager();
if (writeQuery.getObjectChangeSet() == null) {
// PERF: Avoid events if no listeners.
if (eventManager.hasAnyEventListeners()) {
// only throw the events if there is no changeset otherwise the event will be thrown twice
// once by the calculate changes code and here
eventManager.executeEvent(new DescriptorEvent(DescriptorEventManager.PreUpdateEvent, writeQuery));
}
}
// Verify if deep shallow modify is turned on
if (writeQuery.shouldCascadeParts()) {
queryManager.preUpdate(writeQuery);
}
// The row must not be built until after preUpdate in case the object reference has changed.
// For a user defined update in the uow to row must be built twice to check if any update is required.
if ((writeQuery.isUserDefined() || writeQuery.isCallQuery()) && (!getSession().isUnitOfWork())) {
writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRow(object, getSession(), WriteType.UNDEFINED));
} else {
writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRowForUpdate(writeQuery));
}
// Optimistic read lock implementation
Boolean shouldModifyVersionField = null;
if (session.isUnitOfWork() && ((UnitOfWorkImpl)session).hasOptimisticReadLockObjects()) {
shouldModifyVersionField = (Boolean)((UnitOfWorkImpl)session).getOptimisticReadLockObjects().get(writeQuery.getObject());
}
if (!getModifyRow().isEmpty() || (shouldModifyVersionField != null) || ((descriptor.getCMPPolicy() != null) && (descriptor.getCMPPolicy().getForceUpdate()))) {
// If user defined the entire row is required. Must not be built until change is known.
if ((writeQuery.isUserDefined() || writeQuery.isCallQuery()) && getSession().isUnitOfWork()) {
writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRow(object, getSession(), WriteType.UNDEFINED));
}
// Update the write lock field if required.
if (descriptor.usesOptimisticLocking()) {
OptimisticLockingPolicy policy = descriptor.getOptimisticLockingPolicy();
policy.addLockValuesToTranslationRow(writeQuery);
if (!getModifyRow().isEmpty() || shouldModifyVersionField) {
// Update the row with newer lock value.
policy.updateRowAndObjectForUpdate(writeQuery, object);
} else if (!shouldModifyVersionField && (policy instanceof VersionLockingPolicy versionLockingPolicy)) {
// Add the existing write lock value to the for a "read" lock (requires something to update).
versionLockingPolicy.writeLockValueIntoRow(writeQuery, object);
}
}
if (descriptor.hasSerializedObjectPolicy()) {
descriptor.getSerializedObjectPolicy().putObjectIntoRow(getModifyRow(), object, session);
}
// PERF: Avoid events if no listeners.
if (eventManager.hasAnyEventListeners()) {
DescriptorEvent event = new DescriptorEvent(DescriptorEventManager.AboutToUpdateEvent, writeQuery);
event.setRecord(getModifyRow());
eventManager.executeEvent(event);
}
if (QueryMonitor.shouldMonitor()) {
QueryMonitor.incrementUpdate(getWriteObjectQuery());
}
int rowCount = updateObject();
if (rowCount < 1) {
if (session.hasEventManager()) {
session.getEventManager().noRowsModified(writeQuery, object);
}
}
if (descriptor.usesOptimisticLocking()) {
descriptor.getOptimisticLockingPolicy().validateUpdate(rowCount, object, writeQuery);
}
}
commitManager.markPostModifyCommitInProgress(object);
// Verify if deep shallow modify is turned on
if (writeQuery.shouldCascadeParts()) {
queryManager.postUpdate(writeQuery);
}
if ((descriptor.getHistoryPolicy() != null) && descriptor.getHistoryPolicy().shouldHandleWrites()) {
descriptor.getHistoryPolicy().postUpdate(writeQuery);
}
// PERF: Avoid events if no listeners.
if (eventManager.hasAnyEventListeners()) {
eventManager.executeEvent(new DescriptorEvent(DescriptorEventManager.PostUpdateEvent, writeQuery));
}
}
/**
* Update the object.
* This is used by the unit-of-work update.
*/
public void updateObjectForWriteWithChangeSet() {
WriteObjectQuery writeQuery = getWriteObjectQuery();
ObjectChangeSet changeSet = writeQuery.getObjectChangeSet();
Object object = writeQuery.getObject();
ClassDescriptor descriptor = getDescriptor();
DescriptorQueryManager queryManager = descriptor.getQueryManager();
AbstractSession session = getSession();
CommitManager commitManager = session.getCommitManager();
// check for user-defined query
if ((!writeQuery.isUserDefined())// this is not a user-defined query
&& queryManager.hasUpdateQuery()// there is a user-defined query
&& isExpressionQueryMechanism()) {// this is not a hand-coded call (custom SQL etc.)
// This must be done here because the user defined update does not use a changeset so it will not be set otherwise
commitManager.markPreModifyCommitInProgress(object);
performUserDefinedUpdate();
return;
}
// This must be done after the custom query check, otherwise it will be done twice.
commitManager.markPreModifyCommitInProgress(object);
DescriptorEventManager eventManager = descriptor.getEventManager();
if (changeSet.hasChanges()) {
// PERF: Avoid events if no listeners.
if (eventManager.hasAnyEventListeners()) {
DescriptorEvent event = new DescriptorEvent(DescriptorEventManager.PreUpdateWithChangesEvent, writeQuery);
eventManager.executeEvent(event);
// PreUpdateWithChangesEvent listeners may have altered the object - should recalculate the change set.
UnitOfWorkChangeSet uowChangeSet = (UnitOfWorkChangeSet)((UnitOfWorkImpl)session).getUnitOfWorkChangeSet();
if (!uowChangeSet.isChangeSetFromOutsideUOW() && writeQuery.getObjectChangeSet().shouldRecalculateAfterUpdateEvent()){
// writeQuery.getObjectChangeSet() is mapped to object in uowChangeSet.
// It is first cleared then re-populated by calculateChanges method.
if (!descriptor.getObjectChangePolicy().isAttributeChangeTrackingPolicy() ){
// clear the change set without clearing the maps keys since they are not alterable by the event
// if the map is changed, it will be changed in the owning object and the
// change set will be changed there as well.
writeQuery.getObjectChangeSet().clear(false);
}
if (descriptor.getObjectChangePolicy().calculateChangesForExistingObject(object, uowChangeSet, ((UnitOfWorkImpl)session), descriptor, false) == null) {
// calculateChanges returns null in case the changeSet doesn't have changes.
// It should be removed from the list of ObjectChangeSets that have changes in uowChangeSet.
uowChangeSet.getAllChangeSets().remove(writeQuery.getObjectChangeSet());
}
}
}
}
// Verify if deep shallow modify is turned on
if (writeQuery.shouldCascadeParts()) {
queryManager.preUpdate(writeQuery);
}
// The row must not be built until after preUpdate in case the object reference has changed.
// For a user defined update in the uow to row must be built twice to check if any update is required.
writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRowForUpdateWithChangeSet(writeQuery));
Boolean shouldModifyVersionField = changeSet.shouldModifyVersionField();
if (!getModifyRow().isEmpty() || shouldModifyVersionField != null || changeSet.hasCmpPolicyForcedUpdate()) {
// If user defined the entire row is required. Must not be built until change is known.
if (writeQuery.isUserDefined() || writeQuery.isCallQuery()) {
writeQuery.setModifyRow(descriptor.getObjectBuilder().buildRow(object, session, WriteType.UNDEFINED));
}
OptimisticLockingPolicy lockingPolicy = descriptor.getOptimisticLockingPolicy();
// Update the write lock field if required.
if (lockingPolicy != null) {
lockingPolicy.addLockValuesToTranslationRow(writeQuery);
// Do not lock an object that has previously been optimistically locked within the RWUoW
boolean existingOptimisticLock = false;
if (session instanceof RepeatableWriteUnitOfWork uow) {
if (uow.getOptimisticReadLockObjects().get(object) != null && uow.getCumulativeUOWChangeSet() != null
&& uow.getCumulativeUOWChangeSet().getObjectChangeSetForClone(object) != null) {
existingOptimisticLock = true;
}
}
if (!existingOptimisticLock) {
// update the row and object if shouldModifyVersionField is non null and has a value of true (a forced update),
// or if there is no forced update and modifyRow has modifications
if ((shouldModifyVersionField != null && shouldModifyVersionField) || !getModifyRow().isEmpty()) {
// Update the row with newer lock value.
lockingPolicy.updateRowAndObjectForUpdate(writeQuery, object);
} else if (!shouldModifyVersionField && (lockingPolicy instanceof VersionLockingPolicy policy)) {
// Add the existing write lock value to the for a "read" lock (requires something to update).
policy.writeLockValueIntoRow(writeQuery, object);
}
}
}
if (descriptor.hasSerializedObjectPolicy()) {
descriptor.getSerializedObjectPolicy().putObjectIntoRow(getModifyRow(), object, session);
}
// PERF: Avoid events if no listeners.
if (eventManager.hasAnyEventListeners()) {
DescriptorEvent event = new DescriptorEvent(DescriptorEventManager.AboutToUpdateEvent, writeQuery);
event.setRecord(getModifyRow());
eventManager.executeEvent(event);
}
if (QueryMonitor.shouldMonitor()) {
QueryMonitor.incrementUpdate(getWriteObjectQuery());
}
int rowCount = updateObject();
if (rowCount < 1) {
if (session.hasEventManager()) {
session.getEventManager().noRowsModified(writeQuery, object);
}
}
if (lockingPolicy != null) {
lockingPolicy.validateUpdate(rowCount, object, writeQuery);
}
}
commitManager.markPostModifyCommitInProgress(object);
// Verify if deep shallow modify is turned on
if (writeQuery.shouldCascadeParts()) {
queryManager.postUpdate(writeQuery);
}
if ((descriptor.getHistoryPolicy() != null) && descriptor.getHistoryPolicy().shouldHandleWrites()) {
descriptor.getHistoryPolicy().postUpdate(writeQuery);
}
// PERF: Avoid events if no listeners.
if (eventManager.hasAnyEventListeners()) {
eventManager.executeEvent(new DescriptorEvent(DescriptorEventManager.PostUpdateEvent, writeQuery));
}
}
/**
* Unprepare the call if required.
*/
public void unprepare() {
}
}