Support associations delete-orphan on saveOrUpdate()
Description
Attachments
- 12 Dec 2013, 10:30 AM
- 12 Dec 2013, 10:22 AM
Activity
Stephan van Hugten December 12, 2013 at 10:21 AM
I might have a possible solution which passes the unit test in HHH-5267. It looks up the database state and puts the child association value back onto the loaded state. This will trigger a delete action in the method and an entity update action when flushing. See below and in the attached code.
Version 4.2.7.SP1, starting on line 262
Cascade.java
// orphaned if the association was nulled (child == null) or receives a new value while the
// entity is managed (without first nulling and manually flushing).
if ( child == null || ( loadedValue != null && child != loadedValue ) ) {
// Begin patch HHH-3795
if (loadedValue == null) {
loadedValue = entry.getDatabaseValue((SessionImplementor) eventSource, propertyName, parent);
}
final EntityEntry valueEntry = eventSource.getPersistenceContext().getEntry( loadedValue );
// Need to check this in case the context has
// already been flushed. See HHH-7829.
if ( valueEntry != null ) {
final String entityName = valueEntry.getPersister().getEntityName();
if ( LOG.isTraceEnabled() ) {
final Serializable id = valueEntry.getPersister().getIdentifier( loadedValue, eventSource );
final String description = MessageHelper.infoString( entityName, id );
LOG.tracev( "Deleting orphaned entity instance: {0}", description );
}
if (type.isAssociationType() && ((AssociationType)type).getForeignKeyDirection().equals(
ForeignKeyDirection.FOREIGN_KEY_TO_PARENT )) {
// If FK direction is to-parent, we must remove the orphan *before* the queued update(s)
// occur. Otherwise, replacing the association on a managed entity, without manually
// nulling and flushing, causes FK constraint violations.
eventSource.removeOrphanBeforeUpdates( entityName, loadedValue );
}
else {
// Else, we must delete after the updates.
eventSource.delete( entityName, loadedValue, isCascadeDeleteEnabled, new HashSet() );
}
}
// End patch
}
Version 4.2.7.SP1, starting on line 296
Cascade.java
// Begin patch HHH-3795
public Object getDatabaseValue(SessionImplementor session, String propertyName, Object owner) {
int propertyIndex = ( (UniqueKeyLoadable) persister ).getPropertyIndex(propertyName);
Object[] databaseState = getPersister().getDatabaseSnapshot(id, session);
if (databaseState == null) {
return null;
}
Object result = getPersister().getPropertyType(propertyName).assemble(
(Serializable) databaseState[propertyIndex], session, owner);
// Place it back on the loaded state to force an update of the owner during flush
loadedState[propertyIndex] = result;
return result;
}
// End patch
Stephan van Hugten November 2, 2012 at 10:49 AM
How is this expected to work when having a bi-directional relation? If you only delete the orphan you will trigger the foreign key constraint.
Steve Ebersole November 1, 2012 at 5:00 PM
With reattachment the only valid solution is to generally (re)obtain the loaded state. This might happen in a number of ways:
The enhancement option works if we have the entity track its state. This is already true in the new enhancement code I am working on. The next piece there would be to have that state become part of the entity's serialized state as well (currently I have all the enhanced additions set to be transient to not muck with the managed class's serial signature)
(re)load the loaded state. Part of that could be to peek into the second level cache as a first option, before physically querying the database. Personally, I'd like to see select-before-update control this; or maybe we default select-before-update to true for entities which contain one-to-one associations with orphan-removal enabled. But even then, currently select-before-update is too late in being applied to help here, so we'd then also need to add the ability for that loading to be triggered here.
Brett Meyer October 31, 2012 at 7:37 PM
As of 4.1.x, this is still true. saveOrUpdate currently has no way of checking the detatched orphan for deletion. The fix will involve either entity bytecode enhancement or querying the DB for the orphan.
I think saveOrUpdate() behaves somewhat unexpected for associations with delete-orphan cascading style:
Prerequisite: Association is set to cascade="all,delete-orphan" and nullable=true
1) Within a single session (i.e. a transient object):
Load parent
Delete child
saveOrUpdate()
Result: Child is deleted from DB, a DELETE statement was issued
2) Across multiple session (i.e. a detached object):
Have parent with valid ID, but empty list/set of children
saveOrUpdate()
Result: Child is not deleted from DB, an UPDATE statement is issued which sets FK of child to 'null'
3) like 2) but nullable is FALSE
Result: Nothing happens.
With merge() everything works as expected for detached and transient instances. I would expect saveOrUpdate() behave similar to merge().