org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* Copyright (c) 1998, 2019 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
// Gordon Yorke - VM managed entity detachment
// 07/16/2009-2.0 Guy Pelletier
// - 277039: JPA 2.0 Cache Usage Settings
// 07/15/2011-2.2.1 Guy Pelletier
// - 349424: persists during an preCalculateUnitOfWorkChangeSet event are lost
package org.eclipse.persistence.internal.sessions;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.eclipse.persistence.config.FlushClearCache;
import org.eclipse.persistence.config.ReferenceMode;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.changetracking.AttributeChangeTrackingPolicy;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.OptimisticLockException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.descriptors.ObjectBuilder;
import org.eclipse.persistence.internal.helper.IdentityHashSet;
import org.eclipse.persistence.internal.localization.ExceptionLocalization;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
import org.eclipse.persistence.queries.ReadObjectQuery;
import org.eclipse.persistence.sessions.IdentityMapAccessor;
public class RepeatableWriteUnitOfWork extends UnitOfWorkImpl {
/** Used to store the final UnitOfWorkChangeSet for merge into the shared cache */
protected UnitOfWorkChangeSet cumulativeUOWChangeSet;
/**
* Used to determine if UnitOfWork should commit and rollback transactions.
* This is used when an EntityTransaction is controlling the transaction.
*/
protected boolean shouldTerminateTransaction;
/**
* Used to determine if we should bypass any merge into the cache. This is
* a JPA flag and is true when the cacheStoreMode property is set to BYPASS.
* Otherwise, EclipseLink behaves as it usually would.
*/
protected boolean shouldStoreBypassCache;
/**
* The FlashClearCache mode to be used.
* Initialized by setUnitOfWorkChangeSet method in case it's null;
* commitAndResume sets this attribute back to null.
* Relevant only in case call to flush method followed by call to clear method.
* @see org.eclipse.persistence.config.FlushClearCache
*/
protected transient String flushClearCache;
/**
* Track whether we are already in a flush().
*/
protected boolean isWithinFlush;
/** Contains classes that should be invalidated in the shared cache on commit.
* Used only in case fushClearCache == FlushClearCache.DropInvalidate:
* clear method copies contents of updatedObjectsClasses to this set,
* adding classes of deleted objects, too;
* on commit the classes contained here are invalidated in the shared cache
* and the set is cleared.
* Relevant only in case call to flush method followed by call to clear method.
* Works together with flushClearCache.
*/
protected transient Set classesToBeInvalidated;
/**
* Alters the behaviour of the RWUOW commit to function like the UOW with respect to Entity lifecycle
*/
protected boolean discoverUnregisteredNewObjectsWithoutPersist;
public RepeatableWriteUnitOfWork() {
}
public RepeatableWriteUnitOfWork(org.eclipse.persistence.internal.sessions.AbstractSession parentSession, ReferenceMode referenceMode){
super(parentSession, referenceMode);
this.shouldTerminateTransaction = true;
this.shouldNewObjectsBeCached = true;
this.isWithinFlush = false;
this.discoverUnregisteredNewObjectsWithoutPersist = false;
}
/**
* @return the discoverUnregisteredNewObjectsWithoutPersist
*/
public boolean shouldDiscoverUnregisteredNewObjectsWithoutPersist() {
return discoverUnregisteredNewObjectsWithoutPersist;
}
/**
* @param discoverUnregisteredNewObjectsWithoutPersist the discoverUnregisteredNewObjectsWithoutPersist to set
*/
public void setDiscoverUnregisteredNewObjectsWithoutPersist(boolean discoverUnregisteredNewObjectsWithoutPersist) {
this.discoverUnregisteredNewObjectsWithoutPersist = discoverUnregisteredNewObjectsWithoutPersist;
}
/**
* INTERNAL:
* This method will clear all registered objects from this UnitOfWork.
* If parameter value is 'true' then the cache(s) are cleared, too.
*/
@Override
public void clear(boolean shouldClearCache) {
super.clear(shouldClearCache);
if (this.cumulativeUOWChangeSet != null) {
if (this.flushClearCache == FlushClearCache.Drop) {
this.cumulativeUOWChangeSet = null;
this.unregisteredDeletedObjectsCloneToBackupAndOriginal = null;
} else if (this.flushClearCache == FlushClearCache.DropInvalidate) {
// classes of the updated objects should be invalidated in the shared cache on commit.
Set updatedObjectsClasses = this.cumulativeUOWChangeSet.findUpdatedObjectsClasses();
if (updatedObjectsClasses != null) {
if (this.classesToBeInvalidated == null) {
this.classesToBeInvalidated = updatedObjectsClasses;
} else {
this.classesToBeInvalidated.addAll(updatedObjectsClasses);
}
}
if ((this.unregisteredDeletedObjectsCloneToBackupAndOriginal != null) && !this.unregisteredDeletedObjectsCloneToBackupAndOriginal.isEmpty()) {
if (this.classesToBeInvalidated == null) {
this.classesToBeInvalidated = new HashSet<>();
}
Iterator enumDeleted = this.unregisteredDeletedObjectsCloneToBackupAndOriginal.keySet().iterator();
// classes of the deleted objects should be invalidated in the shared cache
while (enumDeleted.hasNext()) {
this.classesToBeInvalidated.add(getDescriptor(enumDeleted.next().getClass()));
}
}
this.cumulativeUOWChangeSet = null;
this.unregisteredDeletedObjectsCloneToBackupAndOriginal = null;
}
}
}
/**
* INTERNAL:
* Call this method if the uow will no longer used for committing transactions:
* all the changes sets will be dereferenced, and (optionally) the cache cleared.
* If the uow is not released, but rather kept around for ValueHolders, then identity maps shouldn't be cleared:
* the parameter value should be 'false'. The lifecycle set to Birth so that uow ValueHolder still could be used.
* Alternatively, if called from release method then everything should go and therefore parameter value should be 'true'.
* In this case lifecycle won't change - uow.release (optionally) calls this method when it (uow) is already dead.
* The reason for calling this method from release is to free maximum memory right away:
* the uow might still be referenced by objects using UOWValueHolders (though they shouldn't be around
* they still might).
*/
@Override
public void clearForClose(boolean shouldClearCache){
this.cumulativeUOWChangeSet = null;
this.unregisteredDeletedObjectsCloneToBackupAndOriginal = null;
super.clearForClose(shouldClearCache);
}
/**
* INTERNAL:
* Return classes that should be invalidated in the shared cache on commit.
* Used only in case fushClearCache == FlushClearCache.DropInvalidate:
* clear method copies contents of updatedObjectsClasses to this set,
* adding classes of deleted objects, too;
* on commit the classes contained here are invalidated in the shared cache
* and the set is cleared.
* Relevant only in case call to flush method followed by call to clear method.
* Works together with flushClearCache.
*/
public Set getClassesToBeInvalidated(){
return classesToBeInvalidated;
}
/**
* INTERNAL:
* Get the final UnitOfWorkChangeSet for merge into the shared cache.
*/
public UnitOfWorkChangeSet getCumulativeUOWChangeSet() {
return cumulativeUOWChangeSet;
}
/**
* INTERNAL:
* Set the final UnitOfWorkChangeSet for merge into the shared cache.
*/
public void setCumulativeUOWChangeSet(UnitOfWorkChangeSet cumulativeUOWChangeSet) {
this.cumulativeUOWChangeSet = cumulativeUOWChangeSet;
}
/**
* INTERNAL:
* Calculate whether we should read directly from the database to the UOW.
* This will be necessary, if a flush and a clear have been called on this unit of work
* In that case, there will be changes in the database that are not in the shared cache,
* so a read in this UOW should get info directly form the DB
*/
@Override
public boolean shouldForceReadFromDB(ObjectBuildingQuery query, Object primaryKey){
if (this.wasTransactionBegunPrematurely() && query.getDescriptor() != null){
// if the saved change set for this UOW contains any changes to the class that is being queried for,
// we should build from the DB
if (this.getFlushClearCache().equals(FlushClearCache.Merge) && this.getCumulativeUOWChangeSet() != null){
Map changeSetMap = this.getCumulativeUOWChangeSet().getObjectChanges().get(query.getDescriptor().getJavaClass());
Object lookupPrimaryKey = null;
if (primaryKey == null && query.isReadObjectQuery()){
lookupPrimaryKey = ((ReadObjectQuery)query).getSelectionId();
}
if (changeSetMap != null ){
if (lookupPrimaryKey == null){
return true;
} else {
// this change set is simply used to do a lookup in the map. The hashcode method just needs the key and the descriptor
ObjectChangeSet lookupChangeSet = new ObjectChangeSet(lookupPrimaryKey, query.getDescriptor(), null, null, false);
if (changeSetMap.get(lookupChangeSet) != null){
return true;
}
}
}
// if the invalidation list for this UOW contains any changes to the class being queried for
// we should build directly from the DB
} else if (this.getFlushClearCache().equals(FlushClearCache.DropInvalidate) && this.getClassesToBeInvalidated() != null){
if (this.getClassesToBeInvalidated().contains(query.getDescriptor())){
return true;
}
}
}
return false;
}
/**
* INTERNAL:
* Indicates whether clearForClose method should be called by release method.
*/
@Override
public boolean shouldClearForCloseOnRelease() {
return true;
}
/**
* INTERNAL:
* Returns true if the UOW should bypass any updated to the shared cache
* during the merge.
*/
@Override
public boolean shouldStoreBypassCache() {
return shouldStoreBypassCache;
}
/**
* Check to see if the descriptor of a superclass can be used to describe this class
*
* By default, in JPA, classes must have specific descriptors to be considered entities
* In this implementation, we check whether the inheritance policy has been configured to allow
* superclass descriptors to describe subclasses that do not have a descriptor themselves
*
* @param theClass
* @return ClassDescriptor
*/
@Override
protected ClassDescriptor checkHierarchyForDescriptor(Class theClass){
ClassDescriptor descriptor = getDescriptor(theClass.getSuperclass());
if (descriptor != null && descriptor.hasInheritance() && descriptor.getInheritancePolicy().getDescribesNonPersistentSubclasses()){
return descriptor;
}
return null;
}
/**
* INTERNAL:
* Commit the changes to any objects to the parent.
*/
@Override
public void commitRootUnitOfWork() throws DatabaseException, OptimisticLockException {
commitToDatabaseWithChangeSet(false);
// unit of work has been committed so it's ok to set the cumulative into the UOW for merge
if (this.cumulativeUOWChangeSet != null) {
this.cumulativeUOWChangeSet.mergeUnitOfWorkChangeSet((UnitOfWorkChangeSet)this.getUnitOfWorkChangeSet(), this, true);
setUnitOfWorkChangeSet(this.cumulativeUOWChangeSet);
}
commitTransactionAfterWriteChanges(); // this method will commit the
// transaction
// and set the transaction
// flags appropriately
// Merge after commit
mergeChangesIntoParent();
}
/**
* INTERNAL:
* Traverse the object to find references to objects not registered in this unit of work.
* Any unregistered new objects found will be persisted or an error will be thrown depending on the mapping's cascade persist.
* References to deleted objects will also currently cause them to be undeleted.
*/
@Override
public void discoverUnregisteredNewObjects(Map clones, Map newObjects, Map unregisteredExistingObjects, Map visitedObjects) {
if (this.discoverUnregisteredNewObjectsWithoutPersist){
super.discoverUnregisteredNewObjects(clones, newObjects, unregisteredExistingObjects, visitedObjects);
}else{
//Bug#438193 : Replace HashSet with IdentityHashSet below for cascadePersistErrors so that the comparison will be by reference and
//not by equals() which invokes hashCode()
Set