0 Replies Latest reply on Nov 28, 2007 6:25 PM by Chris Fraser

    EntityHome throws 'EntityManager is closed' when app marked

    Chris Fraser Newbie

      I'm working on an application that makes extensive use of Seam's EntityHome base class to manage entity bean instances. This works great in a non-clustered environment, but is causing "java.lang.IllegalStateException: EntityManager is closed" exceptions to be thrown when the web application is clustered by marking it distributable in web.xml.

      Stepping through the EntityHome code, I've found that if an EntityHome's entityManager is closed but not nulled at the end of a request, subsequent requests to the same EntityHome instance will result in "java.lang.IllegalStateException: EntityManager is closed".

      We've circumvented this issue by implementing a "@PrePassivate" method on our EntityHomes that takes care of nulling out the entityManager prior to passivation. This does the job in terms of getting rid of the "EntityManager is closed" problem, but another problem is introduced in its place. When managing an entity bean instance in one request, and attempting to update/persist it in a subsequent request results in "org.hibernate.PersistentObjectException: detached entity passed to persist".

      We've circumvented this 2nd issue by merging the EntityHome's associated entity bean instance into the current request's entityManager instance and re-setting the merged entity bean instance as the EntityHome's associated entity bean instance.

      Once we've gotten around both of these issues, we're able to deploy our app in a clustered environment and have it failover without any problems. However, given that the Seam-managed persistence context is touted as being able to

      "...supports transparent failover of extended persisence contexts, without the need to replicate any persistence context state between nodes..." (Chapter 8 in the Seam Manual)
      , I'm left scratching my head, wondering why we had to jump through all of these hoops to get our app to work in a clustered environment.

      So, just to prove that there was nothing specific to our app that was causing the problem, I used seam-gen to create a new project and generate entities from a very simple (one table, 2 columns) existing database schema. The app built, deployed and otherwise worked just as expected without me having to change a thing. Then I went in and added distributable to web.xml, and the same problems that I described above began surfacing.

      I was able to get the seam-genned app to work with by implementing a @PrePassivate method and an "attachInstanceToEntityManager()" method that is called by overridden isManaged(), persist(), remove() and update() on my one and only EntityHome. Code from this class is pasted in below, but does anyone have any deeper insight on Seam-managed persistence contexts used in EntityHomes in an app that is distributable? I'm hoping that either a) we're taking the wrong approach when clustering our app, or b) this is something that will be cleaned up in an upcoming version of Seam. It's easy enough to get around in simple cases, but could get pretty tedious when working with entity bean instances that are made up of complex object graphs.
      package com.mydomain.widget;
      
      import javax.ejb.PrePassivate;
      
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Transactional;
      import org.jboss.seam.framework.EntityHome;
      
      @Name("widgetHome")
      public class WidgetHome extends EntityHome<Widget> {
      
       public void setWidgetWidgetId(Integer id) {
       setId(id);
       }
      
       public Integer getWidgetWidgetId() {
       return (Integer) getId();
       }
      
       @Override
       protected Widget createInstance() {
       Widget widget = new Widget();
       return widget;
       }
      
       public void wire() {
       }
      
       public boolean isWired() {
       return true;
       }
      
       public Widget getDefinedInstance() {
       return isIdDefined() ? getInstance() : null;
       }
      
       ////
       // code below here was not auto-genned by seam-gen, but everything above was
       ////
      
       /**
       * Need to null out entityManagers prior to passivation so that they'll get
       * reinitialized upon subsequent reqests. Failing to do this in a clustered
       * environment will result in "EntityManager is closed" error messages.
       */
       @PrePassivate
       public void prePassivate() {
       setEntityManager(null);
       }
      
       /**
       * if super.isManaged() returns false, but getInstance() has an Id, then
       * instance needs to be re-attached to the entityManager in order to
       * prevent "detached entity passed to persist" exceptions.
       */
       private void attachInstanceToEntityManager() {
       if (!super.isManaged() && getInstance().getWidgetId() != null) {
       setInstance(getEntityManager().merge(getInstance()));
       }
       }
      
       @Override
       @Transactional
       public boolean isManaged() {
       attachInstanceToEntityManager();
       return super.isManaged();
       }
      
       @Override
       @Transactional
       public String persist() {
       attachInstanceToEntityManager();
       return super.persist();
       }
      
       @Override
       @Transactional
       public String remove() {
       attachInstanceToEntityManager();
       return super.remove();
       }
      
       @Override
       @Transactional
       public String update() {
       attachInstanceToEntityManager();
       return super.update();
       }
      }