Is there an EntityListener equivalent for when assocaitions
benc Apr 10, 2008 4:59 PMI have a scenario where I need to know when an association between two entities is changed in the same fashion as @PostUpdate and @PostPersist in EntityListener. My original assumption was that EntityListener would do what I wanted; but, it seems to only be called for fields that are stored directly in the backing table of an entity... So when I change collections defined by @OneToMany or @ManyToMany with @JoinTable the EntityListener is not called.
I can force the EntityListener to be called by setting another field in the Entity (labeled dirty in the attached code example) but I can't help but think there is a better solution than this.
I've created as simple an ear as I could to demonstrate this behavior. EntityA contains a list of EntityB objects. A servlet has two methods it can call. The first creates an instance of EntityA with 3 instances of EntityB, persists them, then adds another instance of EntityB and persists them. The second method called by the servlet does the same thing except it also sets the dirty field on EntityA when it adds the fourth EntityB instance.
Output from non-dirty servlet run
15:39:30,077 INFO [STDOUT] Entity Listener Called for EntityA : 1 15:39:30,077 INFO [STDOUT] Dirty : false 15:39:30,077 INFO [STDOUT] EntityB list : [1, 2, 3]
Output from dirty servlet run
15:39:47,241 INFO [STDOUT] Entity Listener Called for EntityA : 2 15:39:47,241 INFO [STDOUT] Dirty : false 15:39:47,241 INFO [STDOUT] EntityB list : [5, 6, 7] 15:39:47,255 INFO [STDOUT] Entity Listener Called for EntityA : 2 15:39:47,255 INFO [STDOUT] Dirty : true 15:39:47,256 INFO [STDOUT] EntityB list : [5, 6, 7, 8]
Source Code:
EntityA.java
package test; import java.util.Collection; import javax.persistence.Entity; import javax.persistence.EntityListeners; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.OneToMany; @Entity @EntityListeners(AssociationTestEntityListener.class) public class EntityA { protected int id; protected boolean dirty; protected Collection<EntityB> b; @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public boolean isDirty() { return dirty; } public void setDirty(boolean dirty) { this.dirty = dirty; } @OneToMany @JoinTable( name = "a_b", joinColumns = {@JoinColumn(name = "b_id")}, inverseJoinColumns = @JoinColumn(name = "a_id") ) public Collection<EntityB> getB() { return b; } public void setB(Collection<EntityB> b) { this.b = b; } }
EntityB.java
package test; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class EntityB { protected int id; @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } }
AssociationTestEntityListener.java
package test; import javax.persistence.PostPersist; import javax.persistence.PostUpdate; public class AssociationTestEntityListener { @PostPersist @PostUpdate public void postEvent(EntityA a) { StringBuffer bstr = new StringBuffer("EntityB list : ["); for( EntityB b: a.getB()) { bstr.append(b.getId()); bstr.append(", "); } bstr.replace(bstr.lastIndexOf(", "), bstr.length(), "]"); System.out.println(); System.out.println("Entity Listener Called for EntityA : " + a.getId()); System.out.println("Dirty : " + a.isDirty()); System.out.println(bstr.toString()); System.out.println(); a.setDirty(false); } }
TestListenerStatelessSession.java
package test; import java.util.ArrayList; import java.util.Collection; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.jboss.annotation.ejb.LocalBinding; @Stateless (name="TestListener") @LocalBinding(jndiBinding="AT/TestListener") public class TestListenerStatelessSesssion implements TestListenerSession { @PersistenceContext(unitName="defaultPU") EntityManager em; /* (non-Javadoc) * @see test.TestListenerSession#doStuff() */ public void testWithDirty() { Collection<EntityB> bList = new ArrayList<EntityB>(3); bList.add(getNewB()); bList.add(getNewB()); bList.add(getNewB()); EntityA a = new EntityA(); a.setB(bList); em.persist(a); a.setDirty(true); bList = a.getB(); bList.add(getNewB()); a.setB(bList); em.merge(a); } /* (non-Javadoc) * @see test.TestListenerSession#doStuff() */ public void testWithoutDirty() { Collection<EntityB> bList = new ArrayList<EntityB>(3); bList.add(getNewB()); bList.add(getNewB()); bList.add(getNewB()); EntityA a = new EntityA(); a.setB(bList); em.persist(a); bList = a.getB(); bList.add(getNewB()); a.setB(bList); em.merge(a); } private EntityB getNewB() { EntityB rc = new EntityB(); em.persist(rc); return rc; } }
TestListenerSession.java
package test; import javax.ejb.Local; @Local public interface TestListenerSession { public abstract void testWithDirty(); public abstract void testWithoutDirty(); }
AT.jsp
<%@ page import="test.*, javax.naming.*, java.text.*"%> <%! private TestListenerSession tls = null; public void jspInit () { try { InitialContext ctx = new InitialContext(); tls = (TestListenerSession) ctx.lookup("AT/TestListener"); } catch (Exception e) { e.printStackTrace (); } } %> <% try { if (request.getParameter("TestWithoutDirty") != null) { tls.testWithoutDirty(); } else if (request.getParameter("TestWithDirty") != null) { tls.testWithDirty(); } } catch (Exception e) { e.printStackTrace (); } %> <html> <body> <p>Test Listener<br /> <form action="AT.jsp" method="POST"> <input type="submit" value="TestWithoutDirty" name="TestWithoutDirty"> <input type="submit" value="TestWithDirty" name="TestWithDirty"> </form> </p> </body> </html>
Application.xml
<?xml version="1.0" encoding="UTF-8"?> <application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:application="http://java.sun.com/xml/ns/javaee/application_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd" id="Application_ID" version="5"> <module> <web> <web-uri>ROOT.war</web-uri> <context-root>/</context-root> </web> </module> <module> <ejb>AssociationTest.jar</ejb> </module> </application>