Avoid unnecessary updates when cascading the deletes

Description

Consider this mapping:

<class name="Category" table="category">
<id name="id"><generator class="native"/></id>
<version name="version"/>
<property name="description" not-null="true" column="description"/>
<many-to-one name="parent" column="parent_id" cascade="persist"/>
<set name="children" inverse="true" cascade="all">
<key column="parent_id"/>
<one-to-many class="Category"/>
</set>
</class>

Now suppose we have a root category with some children
and try to delete one of them:

Category root = ... ;

Category child = root.children().iterator().next();
root.remove(child);
session.delete(child);
session.flush();

Hibernate issues these SQL statements:

  • an update for the root entity incrementing the version
    that's ok because the set of children has changed

  • an unnecessary update for the child entity to be deleted
    this update changes no fields (even version) and IMO is unnecessary

  • the deletes for the chilren of deleted child

That unnecessary update is executed even when deleted child has no children.
Its children collection is changed from empty set to null
and that is detected as property change, hence the update.

A better strategy would be not to update entities that are going to be
deleted during the flush.

Environment

None

Activity

Show:
Christian Bauer
November 24, 2005, 10:39 AM

Go to the forum.

tHE DPR
November 30, 2005, 4:30 PM

Been there (http://forum.hibernate.org/viewtopic.php?t=950554) but nobody seems to care.
Maybe it's my mistake somewhere, but I can definitely reproduce it, and the test case is not big.
So if product quality does matter to you please consider reevaluating this issue.

Frederic Leitenberger
September 8, 2006, 12:38 PM

I had the same problem with a much more important problem than an additional sql-statement.
We implemented our own advanced property- and state-transition-validation using hibernate-interceptors.

Property-Validation is done in onFlushDirty and onSave.

State-Transition-Validation ist done in onPreSave, onPreUpdate and onPreDelete.

Short description of the problem:

  • save entity and attach childs to it

  • flush/commit

  • delete and/or detach all childs and delete the entity

  • flush/commit

During the last flush/commit the entity is deleted and afterwards updated/flushed before final deletion ...
This produces problems during validation since the current state during flush is almost empty and causes validation to fail.
The state-transition validation fails too, because a delete causes the object to change its state into a wrong state first before finally deleted.

Here are some code nipps and stacks:

All in same session with same entities.

Transaction tx = session.beginTransaction();
E1 entity1 = new E1();
session.save(entity1);
at de.iccs.api.validation.HibernateInterceptor.onSave(HibernateInterceptor.java:31)
at org.hibernate.event.def.AbstractSaveEventListener.substituteValuesIfNecessary(AbstractSaveEventListener.java:348)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:247)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:167)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:114)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:186)
at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:33)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:175)
at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:27)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:530)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:518)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:514)

tx.commit(); // insert for entity1 - ok
at de.iccs.api.validation.HibernateInterceptor.onPreInsert(HibernateInterceptor.java:148)
at org.hibernate.action.EntityInsertAction.preInsert(EntityInsertAction.java:138)
at org.hibernate.action.EntityInsertAction.execute(EntityInsertAction.java:44)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:248)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:232)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:139)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:297)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:985)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:333)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
(...)

// new Transaction ...
Transaction tx = session.beginTransaction();
E2 entity2 = new E2();
// ManyToOne [E2.e1_id -> E1.id]
entity2.setE1(entity1);
entity1.getE2s().add(entity2); // no effective change to entity1 (relation owned by E2)
tx.commit(); // flush for entity1 ?? why? (maybe version inc) ok
at de.iccs.api.validation.HibernateInterceptor.onFlushDirty(HibernateInterceptor.java:25)
at org.hibernate.event.def.DefaultFlushEntityEventListener.invokeInterceptor(DefaultFlushEntityEventListener.java:318)
at org.hibernate.event.def.DefaultFlushEntityEventListener.handleInterception(DefaultFlushEntityEventListener.java:294)
at org.hibernate.event.def.DefaultFlushEntityEventListener.scheduleUpdate(DefaultFlushEntityEventListener.java:234)
at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:114)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:195)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:76)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:985)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:333)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at de.iccs.hibernate.SessionContainer.commitTransaction(SessionContainer.java:63)
(...)

// new Transaction ...
Transaction tx = session.beginTransaction();
// no change to entity1
session.createQuery("from E3 where ...").uniqueResult(); // flush for entity1 ??? why?
at de.iccs.api.validation.HibernateInterceptor.onFlushDirty(HibernateInterceptor.java:25)
at org.hibernate.event.def.DefaultFlushEntityEventListener.invokeInterceptor(DefaultFlushEntityEventListener.java:318)
at org.hibernate.event.def.DefaultFlushEntityEventListener.handleInterception(DefaultFlushEntityEventListener.java:294)
at org.hibernate.event.def.DefaultFlushEntityEventListener.scheduleUpdate(DefaultFlushEntityEventListener.java:234)
at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:114)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:195)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:76)
at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:35)
at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:954)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1099)
at org.hibernate.impl.QueryImpl.list(QueryImpl.java:79)
at org.hibernate.impl.AbstractQueryImpl.uniqueResult(AbstractQueryImpl.java:749)
(...)

// no change to entity1
session.createQuery("from E3 where ...").uniqueResult(); // flush for entity1 ??? why?
at de.iccs.api.validation.HibernateInterceptor.onFlushDirty(HibernateInterceptor.java:25)
at org.hibernate.event.def.DefaultFlushEntityEventListener.invokeInterceptor(DefaultFlushEntityEventListener.java:318)
at org.hibernate.event.def.DefaultFlushEntityEventListener.handleInterception(DefaultFlushEntityEventListener.java:294)
at org.hibernate.event.def.DefaultFlushEntityEventListener.scheduleUpdate(DefaultFlushEntityEventListener.java:234)
at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:114)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:195)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:76)
at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:35)
at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:954)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1099)
at org.hibernate.impl.QueryImpl.list(QueryImpl.java:79)
at org.hibernate.impl.AbstractQueryImpl.uniqueResult(AbstractQueryImpl.java:749)
(...)

for(E2 e2 : entity1.getE2s()) {
if (ex) {
e2.setE1(null); // detach from entity1
session.update(e2);
} else {
session.delete(e2);
}
}
entity1.getE2s().clear();

session.delete(entity1);
at de.iccs.api.validation.HibernateInterceptor.onDelete(HibernateInterceptor.java:38)
at org.hibernate.event.def.DefaultDeleteEventListener.deleteEntity(DefaultDeleteEventListener.java:157)
at org.hibernate.event.def.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:110)
at org.hibernate.impl.SessionImpl.fireDelete(SessionImpl.java:761)
at org.hibernate.impl.SessionImpl.delete(SessionImpl.java:739)
(...)

tx.commit(); // flush for entity1 ??? why?
at de.iccs.api.validation.HibernateInterceptor.onFlushDirty(HibernateInterceptor.java:25)
at org.hibernate.event.def.DefaultFlushEntityEventListener.invokeInterceptor(DefaultFlushEntityEventListener.java:318)
at org.hibernate.event.def.DefaultFlushEntityEventListener.handleInterception(DefaultFlushEntityEventListener.java:294)
at org.hibernate.event.def.DefaultFlushEntityEventListener.scheduleUpdate(DefaultFlushEntityEventListener.java:234)
at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:114)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:195)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:76)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:985)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:333)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
(...)

// update for entity1 ??? why?
at de.iccs.api.validation.HibernateInterceptor.onPreUpdate(HibernateInterceptor.java:169)
at org.hibernate.action.EntityUpdateAction.preUpdate(EntityUpdateAction.java:216)
at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:64)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:248)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:232)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:140)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:297)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:985)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:333)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
(...)

// delete for entity1 --> that's what i expected
at de.iccs.api.validation.HibernateInterceptor.onPreDelete(HibernateInterceptor.java:192)
at org.hibernate.action.EntityDeleteAction.preDelete(EntityDeleteAction.java:108)
at org.hibernate.action.EntityDeleteAction.execute(EntityDeleteAction.java:47)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:248)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:232)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:144)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:297)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:985)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:333)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
(...)

I have an unlucky workaround for now.
I set the version onDelete to null and skip the validation when the version is null (defaults to -1).

Please review/reopen this issue and fix it if possible!!

Steve Ebersole
March 21, 2011, 7:04 PM

Bulk closing stale resolved issues

Cannot Reproduce

Assignee

Unassigned

Reporter

tHE DPR

Fix versions

None

Labels

None

backPortable

None

Suitable for new contributors

None

Requires Release Note

None

Pull Request

None

backportDecision

None

Components

Affects versions

Priority

Major
Configure