[hibernate-issues] [Hibernate-JIRA] Commented: (HHH-1196) Avoid unnecessary updates when cascading the deletes

Frederic Leitenberger (JIRA) noreply at atlassian.com
Fri Sep 8 06:39:25 EDT 2006


    [ http://opensource.atlassian.com/projects/hibernate/browse/HHH-1196?page=comments#action_24370 ] 

Frederic Leitenberger commented on HHH-1196:
--------------------------------------------

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!!

> Avoid unnecessary updates when cascading the deletes
> ----------------------------------------------------
>
>          Key: HHH-1196
>          URL: http://opensource.atlassian.com/projects/hibernate/browse/HHH-1196
>      Project: Hibernate3
>         Type: Improvement

>   Components: core
>     Versions: 3.1 rc3
>     Reporter: tHE DPR

>
>
> 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.

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators:
   http://opensource.atlassian.com/projects/hibernate/secure/Administrators.jspa
-
For more information on JIRA, see:
   http://www.atlassian.com/software/jira




More information about the hibernate-issues mailing list