org.eclipse.persistence.internal.queries.DatabaseQueryMechanism 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 346465e
/*
* Copyright (c) 1998, 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
// 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 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;
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;
/**
* 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 = (DatabaseField)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) {
((DatabaseException)exception).setCall(call);
if(((DatabaseException)exception).getAccessor() == null) {
((DatabaseException)exception).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)) {
// Add the existing write lock value to the for a "read" lock (requires something to update).
((VersionLockingPolicy)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 (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) {
RepeatableWriteUnitOfWork uow = (RepeatableWriteUnitOfWork)session;
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)) {
// Add the existing write lock value to the for a "read" lock (requires something to update).
((VersionLockingPolicy)lockingPolicy).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() {
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy