All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.mappings.UnidirectionalOneToManyMapping Maven / Gradle / Ivy

There is a newer version: 5.0.0-B03
Show newest version
/*
 * 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:
//     ailitchev - Uni-directional OneToMany
//     07/19/2011-2.2.1 Guy Pelletier
//       - 338812: ManyToMany mapping in aggregate object violate integrity constraint on deletion
//     09/12/2018 - Will Dazey
//       - 391279: Add support for Unidirectional OneToMany mappings with non-nullable values
package org.eclipse.persistence.mappings;

import java.util.Iterator;
import java.util.Vector;

import org.eclipse.persistence.config.SystemProperties;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.OptimisticLockException;
import org.eclipse.persistence.internal.descriptors.CascadeLockingPolicy;
import org.eclipse.persistence.internal.helper.ConversionManager;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.ChangeRecord;
import org.eclipse.persistence.internal.sessions.CollectionChangeRecord;
import org.eclipse.persistence.internal.sessions.ObjectChangeSet;
import org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.queries.DeleteObjectQuery;
import org.eclipse.persistence.queries.ObjectLevelModifyQuery;
import org.eclipse.persistence.queries.ObjectLevelReadQuery;
import org.eclipse.persistence.queries.ReadAllQuery;
import org.eclipse.persistence.queries.ReadQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;

/**
 * 

Purpose: UnidirectionalOneToManyMapping doesn't have 1:1 back reference mapping. * * @author Andrei Ilitchev * @since Eclipselink 1.1 */ public class UnidirectionalOneToManyMapping extends OneToManyMapping { /** * Indicates whether target's optimistic locking value should be incremented on * target being added to / removed from a source. **/ protected boolean shouldIncrementTargetLockValueOnAddOrRemoveTarget; /** * Indicates whether target's optimistic locking value should be incremented on * the source deletion. * Note that if the flag is set to true then the indirection will be triggered on * source delete - in order to verify all targets' versions. **/ protected boolean shouldIncrementTargetLockValueOnDeleteSource; /** * PUBLIC: * Default constructor. */ public UnidirectionalOneToManyMapping() { super(); this.shouldIncrementTargetLockValueOnAddOrRemoveTarget = true; this.shouldIncrementTargetLockValueOnDeleteSource = true; } /** * INTERNAL: * Build a row containing the keys for use in the query that updates the row for the * target object during an insert or update */ @Override protected AbstractRecord buildKeyRowForTargetUpdate(ObjectLevelModifyQuery query){ AbstractRecord keyRow = new DatabaseRecord(); // Extract primary key and value from the source. int size = sourceKeyFields.size(); for (int index = 0; index < size; index++) { DatabaseField sourceKey = sourceKeyFields.get(index); DatabaseField targetForeignKey = targetForeignKeyFields.get(index); Object sourceKeyValue = query.getTranslationRow().get(sourceKey); keyRow.put(targetForeignKey, sourceKeyValue); } return keyRow; } /** * INTERNAL: * This method is used to create a change record from comparing two collections * @return org.eclipse.persistence.internal.sessions.ChangeRecord */ @Override public ChangeRecord compareForChange(Object clone, Object backUp, ObjectChangeSet owner, AbstractSession uow) { ChangeRecord record = super.compareForChange(clone, backUp, owner, uow); if(record != null && getReferenceDescriptor().getOptimisticLockingPolicy() != null) { postCalculateChanges(record, (UnitOfWorkImpl)uow); } return record; } /** * INTERNAL: * Extract the source primary key value from the target row. * Used for batch reading, most following same order and fields as in the mapping. */ protected Vector extractSourceKeyFromRow(AbstractRecord row, AbstractSession session) { int size = sourceKeyFields.size(); Vector key = new Vector(size); ConversionManager conversionManager = session.getDatasourcePlatform().getConversionManager(); for (int index = 0; index < size; index++) { DatabaseField targetField = targetForeignKeyFields.get(index); DatabaseField sourceField = sourceKeyFields.get(index); Object value = row.get(targetField); // Must ensure the classification gets a cache hit. try { value = conversionManager.convertObject(value, sourceField.getType()); } catch (ConversionException e) { throw ConversionException.couldNotBeConverted(this, getDescriptor(), e); } key.addElement(value); } return key; } /** * INTERNAL: */ @Override public boolean isOwned(){ return true; } /** * INTERNAL: */ @Override public boolean isUnidirectionalOneToManyMapping() { return true; } /** * INTERNAL: * Initialize the mapping. */ @Override public void initialize(AbstractSession session) throws DescriptorException { super.initialize(session); if (getReferenceDescriptor().getOptimisticLockingPolicy() != null) { if (this.shouldIncrementTargetLockValueOnAddOrRemoveTarget) { this.descriptor.addMappingsPostCalculateChanges(this); } if (this.shouldIncrementTargetLockValueOnDeleteSource && !this.isPrivateOwned) { this.descriptor.addMappingsPostCalculateChangesOnDeleted(this); } } } /** * Initialize the type of the target foreign key, as it will be null as it is not mapped in the target. */ @Override public void postInitialize(AbstractSession session) { super.postInitialize(session); Iterator targetForeignKeys = getTargetForeignKeyFields().iterator(); Iterator sourceKeys = getSourceKeyFields().iterator(); while (targetForeignKeys.hasNext()) { DatabaseField targetForeignKey = targetForeignKeys.next(); DatabaseField sourcePrimaryKey = sourceKeys.next(); if (targetForeignKey.getType() == null) { DatabaseMapping mapping = getDescriptor().getObjectBuilder().getMappingForField(sourcePrimaryKey); // If we have a mapping, set the type, otherwise at this point // there is not much more we can do. This case will likely hit // when we have a UnidirectionalOneToManyMapping on an aggregate // outside of JPA. Within JPA, in most cases, the metadata // processing should set the type on the targetForeignKey for us. // Bug 278263 has been entered to revisit this code. if (mapping != null) { targetForeignKey.setType(mapping.getFieldClassification(sourcePrimaryKey)); } } } } /** * INTERNAL: */ @Override protected AbstractRecord createModifyRowForAddTargetQuery() { AbstractRecord modifyRow = super.createModifyRowForAddTargetQuery(); int size = targetForeignKeyFields.size(); for (int index = 0; index < size; index++) { DatabaseField targetForeignKey = targetForeignKeyFields.get(index); modifyRow.put(targetForeignKey, null); } return modifyRow; } /** * INTERNAL: * Delete the reference objects. */ @Override public void preDelete(DeleteObjectQuery query) throws DatabaseException, OptimisticLockException { if (shouldObjectModifyCascadeToParts(query)) { super.preDelete(query); } else { updateTargetRowPreDeleteSource(query); } } /** * Prepare a cascade locking policy. */ @Override public void prepareCascadeLockingPolicy() { CascadeLockingPolicy policy = new CascadeLockingPolicy(getDescriptor(), getReferenceDescriptor()); policy.setQueryKeyFields(getSourceKeysToTargetForeignKeys()); policy.setShouldHandleUnmappedFields(true); getReferenceDescriptor().addCascadeLockingPolicy(policy); } /** * INTERNAL: * Overridden by mappings that require additional processing of the change record after the record has been calculated. */ @Override public void postCalculateChanges(org.eclipse.persistence.sessions.changesets.ChangeRecord changeRecord, UnitOfWorkImpl uow) { // targets are added to and/or removed to/from the source. CollectionChangeRecord collectionChangeRecord = (CollectionChangeRecord)changeRecord; Iterator it = collectionChangeRecord.getAddObjectList().values().iterator(); while(it.hasNext()) { ObjectChangeSet change = it.next(); if(!change.hasChanges()) { change.setShouldModifyVersionField(Boolean.TRUE); ((org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet)change.getUOWChangeSet()).addObjectChangeSet(change, uow, false); } } // in the mapping is privately owned then the target will be deleted - no need to modify target version. it = collectionChangeRecord.getRemoveObjectList().values().iterator(); while(it.hasNext()) { ObjectChangeSet change = it.next(); if (!isPrivateOwned()){ if(!change.hasChanges()) { change.setShouldModifyVersionField(Boolean.TRUE); ((org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet)change.getUOWChangeSet()).addObjectChangeSet(change, uow, false); } }else{ containerPolicy.postCalculateChanges(change, referenceDescriptor, this, uow); } } } /** * INTERNAL: * Overridden by mappings that require objects to be deleted contribute to change set creation. */ @Override public void postCalculateChangesOnDeleted(Object deletedObject, UnitOfWorkChangeSet uowChangeSet, UnitOfWorkImpl uow) { // the source is deleted: // trigger the indirection - we have to get optimistic lock exception // in case another thread has updated one of the targets: // triggered indirection caches the target with the old version, // then the version update waits until the other thread (which is locking the version field) commits, // then the version update is executed and it throws optimistic lock exception. Object col = getRealCollectionAttributeValueFromObject(deletedObject, uow); if (col != null) { Object iterator = this.containerPolicy.iteratorFor(col); while (this.containerPolicy.hasNext(iterator)) { Object target = this.containerPolicy.next(iterator, uow); ObjectChangeSet change = this.referenceDescriptor.getObjectBuilder().createObjectChangeSet(target, uowChangeSet, uow); if (!change.hasChanges()) { change.setShouldModifyVersionField(Boolean.TRUE); ((org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet)change.getUOWChangeSet()).addObjectChangeSet(change, uow, false); } } } } /** * INTERNAL: * Add additional fields */ @Override protected void postPrepareNestedBatchQuery(ReadQuery batchQuery, ObjectLevelReadQuery query) { super.postPrepareNestedBatchQuery(batchQuery, query); ReadAllQuery mappingBatchQuery = (ReadAllQuery)batchQuery; int size = this.targetForeignKeyFields.size(); for (int i=0; i < size; i++) { mappingBatchQuery.addAdditionalField(this.targetForeignKeyFields.get(i)); } } /** * INTERNAL: * The translation row may require additional fields than the primary key if the mapping in not on the primary key. */ @Override protected void prepareTranslationRow(AbstractRecord translationRow, Object object, ClassDescriptor descriptor, AbstractSession session) { // Make sure that each source key field is in the translation row. int size = sourceKeyFields.size(); for(int i=0; i < size; i++) { DatabaseField sourceKey = sourceKeyFields.get(i); if (!translationRow.containsKey(sourceKey)) { Object value = descriptor.getObjectBuilder().extractValueFromObjectForField(object, sourceKey, session); translationRow.put(sourceKey, value); } } } /** * INTERNAL: * Overridden by mappings that require additional processing of the change record after the record has been calculated. */ @Override public void recordPrivateOwnedRemovals(Object object, UnitOfWorkImpl uow) { //need private owned check for this mapping as this method is called for any mapping // that also registers a postCalculateChanges() method. Most mappings only register the // postCalculateChanges if they are privately owned. This Mapping is a special case an // always registers a postCalculateChanges mapping when the target has OPT locking. if (isPrivateOwned){ super.recordPrivateOwnedRemovals(object, uow); } } /** * INTERNAL: * UnidirectionalOneToManyMapping performs some events after INSERT/UPDATE to maintain the keys */ @Override public boolean requiresDataModificationEvents(){ return true; } /** * PUBLIC: * Set value that indicates whether target's optimistic locking value should be incremented on * target being added to / removed from a source (default value is true). **/ public void setShouldIncrementTargetLockValueOnAddOrRemoveTarget(boolean shouldIncrementTargetLockValueOnAddOrRemoveTarget) { this.shouldIncrementTargetLockValueOnAddOrRemoveTarget = shouldIncrementTargetLockValueOnAddOrRemoveTarget; } /** * PUBLIC: * Set value that indicates whether target's optimistic locking value should be incremented on * the source deletion (default value is true). **/ public void setShouldIncrementTargetLockValueOnDeleteSource(boolean shouldIncrementTargetLockValueOnDeleteSource) { this.shouldIncrementTargetLockValueOnDeleteSource = shouldIncrementTargetLockValueOnDeleteSource; } /** * PUBLIC: * Indicates whether target's optimistic locking value should be incremented on * target being added to / removed from a source (default value is true). **/ public boolean shouldIncrementTargetLockValueOnAddOrRemoveTarget() { return shouldIncrementTargetLockValueOnAddOrRemoveTarget; } /** * PUBLIC: * Indicates whether target's optimistic locking value should be incremented on * the source deletion (default value is true). **/ public boolean shouldIncrementTargetLockValueOnDeleteSource() { return shouldIncrementTargetLockValueOnDeleteSource; } /** * INTERNAL * Target foreign key of the removed object should be modified (set to null). */ @Override protected boolean shouldRemoveTargetQueryModifyTargetForeignKey() { return true; } @Override public boolean shouldDeferInsert() { if (shouldDeferInserts == null) { String property = PrivilegedAccessHelper.getSystemProperty(SystemProperties.ONETOMANY_DEFER_INSERTS); shouldDeferInserts = true; if (property != null) { shouldDeferInserts = "true".equalsIgnoreCase(property); } else { for (DatabaseField f : targetForeignKeyFields) { if (!f.isNullable()) { shouldDeferInserts = false; break; } } } } return shouldDeferInserts; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy