-->
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.  [ 2 posts ] 
Author Message
 Post subject: removing entity from all collections on delete: for review
PostPosted: Tue Mar 24, 2009 2:28 am 
Newbie

Joined: Tue Mar 24, 2009 1:46 am
Posts: 1
I am trying to work out a robust way to remove a child entity from all collections on entity delete to ensure all related collection cache nodes in 2nd level cache are properly evicted. In doing so I came up with a solution below, which I hope someone can validate (please see specific questions at the bottom). But first, let me motivate the problem with a use case:

Let's say we have an entity that belongs to two collections:

Code:
@Entity
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
class Mom
{
   @CacheConcurrencyStrategy.TRANSACTIONAL
   @CascadeType.ALL, DELETE_ORPHAN
   @OneToMany
   public Set<Child> getChildren()
   {
      return this.children;
   }   
}

@Entity
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
class Dad
{
   @CacheConcurrencyStrategy.TRANSACTIONAL
   @CascadeType.ALL
   @OneToMany
   public Set<Child> getChildren()
   {
      return this.children;
   }

   public void removeChild(Child c)
   {
      children.remove(c);
      s.setDad(null);
   }   
}

@Entity
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
class Child
{
   @ManyToOne
   @NotNull
   private Mom mom;

   @ManyToOne
   @NotNull
   private Dad dad;
}


Let's say the same Child 'c' belongs to a Mom and a Dad. On Session.delete(mom), Child c will be deleted by cascade (as orphan). 2nd level Collection node Mom.children will be evicted, but Dad.children will NOT be. To make sure Dad.children collection is evicted, one has to call dad.removeChild(c) during the same Session as session.delete(mom). Since Mom may also be deleted via cascade (say from another parent class GrandMa), it becomes cumbersome to keep track of places where one has to explicitly dissociate a Child from a Mom and a Dad. So I wanted to define how to delete a Child in one place, and ensure that method is invoked whenever a Child is being deleted - regardless of whether delete is explicit or by cascade:

Code:
public interface MultiparentEntity {

    /**
     * called *before* implementing entity is to be deleted
     * (either via Session.delete(entity), or via Cascade)
     */
    void onDelete();
}


Now define how to delete a Child:

Code:
@Entity
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
class Child implements MultiparentEntity
{
   @ManyToOne
   @NotNull
   private Mom mom;

   @ManyToOne
   @NotNull
   private Dad dad;

   /**
    * before deletion, remove this Child from all collections to ensure those Collections are evicted
   */
   public void onDelete()
   {
      Mom m = this.mom;
      if(m != null)
      {
         m.removeChild(this);
      }

      Dad d = this.dad;
      if(d != null)
      {
         d.removeChild(this);
      }
   }
}


To ensure MultiparentEntity.onDelete() is called whenever such entity is about to be deleted, I extend DefaultDeleteEventListener:

Code:
public class MultiparentEntityDeleteEventListener extends DefaultDeleteEventListener {

    @Override
    public void onDelete(DeleteEvent evt, Set transientEntities) throws HibernateException {

        if (evt.getObject() instanceof MultiparentEntity) {
            MultiparentEntity multiparentEntity = (MultiparentEntity) evt.getObject();
            multiparentEntity.onDelete();
        }

        super.onDelete(evt, transientEntities);
    }
}


Questions
That's it. However, having implemented the above in several entities on our domain model I am seeing some errors - constraint violation attempts and possible failures to cascade (if, for example, 'mom' is being deleted by cascade) that make me wonder whether the above approach is sound. Is it ok to access and modify a (previously uninstantiated) collection from within a DeleteEventListener? In the above case we would end up instantiating and modifying Dad.children. Also, since 'dad' is being modified inside onDelete() an update Cascade from Dad to children should fire. WIll it? Finally, in above scenario, DELETE would cascade from Mom to Child, but UPDATE would cascade from Dad to the same child (latter as result of onDelete() call)> Would both cascade actions be fired, or will UPDATE be somehow discarded, given that there is a DELETE. Seems like UPDATE cascade could cause a Validator failure (on non-null) if fired before DELETE.

If the above is a misuse of the Event System, how would you solve the original problem of deleting a Child cleanly (including via Cascade)? thanks!

-nikita

Stack:
Hibernate 3.3.1.GA
Hibernate Annotations 3.4.0.GA
JBC3 as 2nd level cache


Top
 Profile  
 
 Post subject:
PostPosted: Tue Mar 24, 2009 1:45 pm 
Newbie

Joined: Wed Feb 04, 2009 1:44 pm
Posts: 7
Seems like the above is either an anti-pattern or a valid pattern triggering a bug: if Child.onDelete() removes a different entity from another collection, that collection's cascade does not execute.

Say, for example Dad has another collection of Photo's and a method to find photos by child:

Code:
 
   @CacheConcurrencyStrategy.TRANSACTIONAL
   @CascadeType.ALL, DELETE_ORPHAN
   @OneToMany
   public Set<Photo> getPhotos()
   {
      return this.photos;
   }   

   public Set<Photo> getPhotosOfChild(Child c)



and we'd like to ensure that all photos of child are deleted when child is deleted (yes, sounds morbid, sorry). So, we'd amend Child.onDelete():

Code:
   /**
    * before deletion, remove this Child from all collections to ensure those Collections are evicted
   */
   public void onDelete()
   {
      Mom m = this.mom;
      if(m != null)
      {
         m.removeChild(this);
      }

      Dad d = this.dad;
      if(d != null)
      {
         d.removeChild(this);
      }

      //remove photos of child
      for(Iterator<Photo> iter = dad.getPhotosOfChild(this); iter.hasNext();)
     {
           Photo p = iter.next();
           d.removePhoto(p); //expect photo to be deleted by orphan cascade but that does NOT happen
            //so have to delete explicitly
           session.delete(p);
      }

   }
}


Dad.photos cascade DELETE_ORPHAN isn't honored when onDelete is called from DeleteEventListener (described above). If explicit delete(p) isn't added, I see a Nullability exception complaining about Photo.dad being null. Why is that?


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 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.