-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 posts ] 
Author Message
 Post subject: My flush during cascade is NOT dangerous!
PostPosted: Mon Mar 15, 2004 6:11 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
http://forum.hibernate.org/viewtopic.php?t=928838 is a posting by me from last week describing how I am trying to use Lifecycle in (I think) a straightforward way, but am getting "flush during cascade is dangerous". I've gotten no responses yet, which is very atypical for this forum, especially on issues of substance.

I contend that my use of flush during cascade in this case is NOT dangerous, and that in fact Hibernate's throwing that exception is breaking my ability to use Lifecycle effectively.

I hope Gavin or Christian or one of the other core Hibernators can make some comment, as right now I don't see that Lifecycle is very useful. I would love to be proven wrong!!!!!

Cheers,
Rob


Top
 Profile  
 
 Post subject:
PostPosted: Tue Mar 16, 2004 12:26 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
Well, I can tell you the cause of this behaviour. Take a look at the block of code that actually performs the deletions / cascades / Lifecycle.onDelete fucntionality. It is in SessionImpl in 2.1.x and earlier, and in the new AbstractDeleteCommand (specifically lines 134 - 202) in the new v22branch. That logic goes like:
Code:

      List deletionsByOnDelete = null;
      HashSet nullifiablesAfterOnDelete = null;

      if ( persister.implementsLifecycle() ) {
         HashSet oldNullifiables = ( HashSet ) getSession().nullifiables.clone();
         ArrayList oldDeletions = ( ArrayList ) getSession().deletions.clone();
         getSession().nullifiables.add(key); //the deletion of the parent is actually executed BEFORE any deletion from onDelete()
         try {
            getLogger().debug( "calling onDelete()" );
            if ( ( ( Lifecycle ) object ).onDelete( getSession() ) ) {
               //rollback deletion
               entry.status = SessionImpl.LOADED;
               entry.deletedState = null;
               getSession().nullifiables=oldNullifiables;
               getLogger().debug("deletion vetoed by onDelete()");
               return; //don't let it cascade
            }
         }
         catch (CallbackException ce) {
            //rollback deletion
            entry.status = SessionImpl.LOADED;
            entry.deletedState = null;
            getSession().nullifiables=oldNullifiables;
            throw ce;
         }
         //note, the following assumes that onDelete() didn't cause the session
         //to be flushed! TODO: add a better check that it doesn't
         if ( oldDeletions.size() > getSession().deletions.size() ) {
            throw new HibernateException("session was flushed during onDelete()");
         }
         deletionsByOnDelete = getSession().deletions.subList(
               oldDeletions.size(),
               getSession().deletions.size()
         );
         getSession().deletions = oldDeletions;
         nullifiablesAfterOnDelete = getSession().nullifiables;
         getSession().nullifiables = oldNullifiables;
      }

      getSession().incrementCascadeLevel();
      try {
         // cascade-delete to collections BEFORE the collection owner is deleted
         Cascades.cascade(
               getSession(),
               persister,
               object,
               Cascades.ACTION_DELETE,
               Cascades.CASCADE_AFTER_INSERT_BEFORE_DELETE
         );
      }
      finally {
         getSession().decrementCascadeLevel();
      }

      getSession().nullifyTransientReferences( entry.deletedState, propTypes, false, object );
      checkNullability( entry.deletedState, persister, true );
      getSession().nullifiables.add(key);

        // Ensures that containing deletions happen before sub-deletions
      getSession().deletions.add(
            new ScheduledDeletion( entry.id, version, object, persister, getSession() )
      );

      if ( persister.implementsLifecycle() ) {
         // after nullify, because we don't want to nullify references to subdeletions
         getSession().nullifiables.addAll(nullifiablesAfterOnDelete);
         // after deletions.add(), to respect foreign key constraints
         getSession().deletions.addAll(deletionsByOnDelete);
      }



Essentially, before Lifecycle.onDelete is called, a snapshot is taken of all deletes currently scheduled. After Lifecycle.onDelete returns, that snapshot is used to determine any deletions requested by the Lifecycle.onDelete() method (by populating the deletionsByOnDelete list). After Lifecycle.onDelete, but before continuing, the original snapshot is "reinstated" to "postpone" any deletes requested by the Lifecycle.onDelete method until after the requested delete and any actual collection cascades are performed.

I would need to think through this some more to give you a better answer as to the reason and a solution.

FMI, is there a particular reason you cannot map the association between Root and Node and then define the cascade between Root and Node declaratively (using all-delete-orphan or delete-orphan)? That approach would work. I actually do it that way, even mapping collections I never access just to get the cascading delete capability.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Mar 16, 2004 2:23 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
Wow, Hibernate team comes through again :-D

I think the big question is why are the Lifecycle.onDelete deletions deliberately postponed until after the cascade deletions? It seems that that decision is as likely to be wrong as right -- for example, in my specific case, it *is* wrong. I *want* the Lifecycle.onDelete deletions to be done *first*. Perhaps some way to indicate that? special return value from onDelete, or something?

In fact I'm wondering when you *want* the onDeletes to be done last... seems like that would only be useful if the cascaded objects were the parents. Really what you probably want is something like onDeleteBefore and onDeleteAfter to let you go either way.

You're correct that in this particular example I can just set up a separate association from the Root to all Nodes in its contained Node graph. That is in fact what I've done in our app itself. But that doesn't work for another case in our application. Let me briefly explain that case:

We have Version objects that are immutable. We have Asset objects that contain a list of Versions. (Think content management system.) Multiple Assets may reference a single Version (why not?).

We want to garbage collect Versions when they are no longer referenced by any Asset. Ideally we would want to implement this through an Asset.onDelete method, so that if we cascade the deletion of a bunch of Assets (there *is* a strict ownership relation from Campaign to Asset), the Versions get cleaned up as well.

In this case we probably should back off and just implement a separate garbage collection method for Versions, rather than trying to push it into Lifecycle.

Still, the general question here remains: why was this do-the-Lifecycle-onDeletes-last policy decision made, and is it possible to give the Lifecycle user more control over the deletion sequencing?

Cheers!
Rob


Top
 Profile  
 
 Post subject:
PostPosted: Tue Mar 16, 2004 5:38 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 3:00 pm
Posts: 1816
Location: Austin, TX
Quote:
that decision is as likely to be wrong as right

As for why the deletes are "postponed", what you have to realize is that you have not told hibernate anything about this relationship. Thus when you try to cascade these relations manually, it tries to make a guess. Sure, its 50/50, but that's the best you can ever get in a single-choice, two-possibility situation.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Mar 16, 2004 5:44 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
I understand that, but my point above was that there is no way for me *to tell* Hibernate anything about these relationships! That's really the basic issue -- Lifecycle provides no hook to control whether or when this should happen, and there is no other construct in mappings or elsewhere to provide this additional information.

Or am I missing something? Was there something more I could or should have done above?

Cheers,
Rob


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.