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