Search code examples
jsfjpaserializationview-scopeapache-tomee

JPA entities in JSF @ViewScoped backing bean always getting detached?


I'm currently trying to learn JSF and JPA. I know that the patterns I use are not recommended at all, but I want to understand what's going on because I think it'll help me in the future. I've just thrown together a prototype from various sources.

The problem that I encounter with the setup described below is that apparently the JPA entities are getting detached all the time, which in turn happens because the backing bean gets serialized over and over. In fact, if I remove the Serializable interface from the entity class, I get Exiting serializeView - Could not serialize state: com.sk.Message

Since the entities are detached, nothing gets committed to the database when I call EntityManager.commit(). If I manually merge all the entities (the commented out line in onCellEdit() below) with EntityManager.merge(), the modified entities are committed to the database.

I've already found from other SO posts that I could deal with this problem by adding

<context-param>
    <param-name>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</param-name>
    <param-value>false</param-value>
</context-param>

to my persistence.xml. But it was also pointed out somewhere that this would only be a workaround and not a solution.

So my questions are:

  1. Is it intended/expected that a @ViewScoped JSF backing bean gets serialized over and over again (while staying on the same view all the time), which makes it difficult to use JPA entities in it?
  2. Is it safe/reasonable to use the SERIALIZE_STATE_IN_SESSION parameter?
  3. As I found recommended many times, should I just forget about JSF managed beans altogether and go with CDI directly (e.g. @ConversationScope to achieve something similar)?

I'm using TomEE (MyFaces, OpenJPA) with PrimeFaces. The backing bean contains the following code:

@ViewScoped
@ManagedBean
public class MessageBean implements Serializable
{
  private List<Message> messages;

  public List<Message> getMessages()
  {
    return messages;
  }

  public void setMessages( List<Message> messages )
  {
    this.messages = messages;
  }

  @PostConstruct
  public void init()
  {
    messages = PersistenceManager.getInstance().queryMessages();
  }

  public void onCellEdit( CellEditEvent event )
  {
    // PersistenceManager.getInstance().mergeMessages( messages );
    PersistenceManager.getInstance().commitTransaction();
  }

  [...]

A Message is a JPA Entity, like this:

@Entity
@Table( name = "message" )
@NamedQuery( name = "Message.findAll", query = "SELECT a FROM Message a" )
public class Message implements Serializable
{
   private static final long serialVersionUID = 1L;

   @Id
   @Column( unique = true, nullable = false )
   private Integer dbid;

   @Column( nullable = false, length = 14 )
   private String no;

   [...]
}

The backing bean is referenced from a JSF page using a PrimeFaces DataTable:

<h:form id="navForm">
    <p:dataTable 
            id="messages" 
            value="#{messageBean.messages}"
            var="message" 
            editable="true" 
            editMode="cell">
        <f:facet name="header">MESSAGE</f:facet>

        <p:ajax 
                event="cellEdit" 
                listener="#{messageBean.onCellEdit}"
                update=":navForm:messages" />

        <p:column>
            <p:cellEditor>
                <f:facet name="output">
                    <h:outputText value="#{message.no}" />
                </f:facet>
                <f:facet name="input">
                    <p:inputText 
                            id="modelInput" 
                            value="#{message.no}" />
                </f:facet>
            </p:cellEditor>
            <f:facet name="header">Message number</f:facet>
        </p:column>

        [...]

I know I'm probably violating dozens of best practices here, but for prototyping I've created a singleton POJO, PersistenceManager, which deals with the JPA interface (and potentially other data sources). I use an application-managed, resource-local EntityManager. An excerpt looks like this:

public class PersistenceManager
{
  private static PersistenceManager INSTANCE;

  private EntityManagerFactory emf;
  private EntityManager em;
  private EntityTransaction entr;

  private PersistenceManager( PersistenceType persistenceType )
  {
    emf = Persistence.createEntityManagerFactory( "MessagePU" );
    em = emf.createEntityManager();
  }

  public List<Message> queryMessages()
  {
    TypedQuery<Message> query = em.createNamedQuery( "Message.findAll", Message.class );

    return query.getResultList();
  }

  public void commitTransaction()
  {
    if ( entr != null && entr.isActive() )
    {
      entr.commit();
    }
  }

  [...]

Solution

  • Before committing a transaction you have to start it (then close it at the end of the transaction). Where is the else statement in your commitTransaction method, in case the EntityTransaction object is not active and/or null ?

    Plus, I don't see any EJB in your code. The POJO approach is not the best option in an application managed, served, and hosted by a container.

    For me, the best approach to implement the persistence layer in JSF and JavaEE applications in general, is the Session Façade Pattern, you can search the web about it, there are plenty of references.

    In your case, something like this would do.

    A Message Facade, that manages transactions related to the Message entity.

    import javax.ejb.Stateless;
    import javax.persistence.EntityManager;
    import javax.persistence.PersistenceContext;
    
    @Stateless
    public class MessageFacade extends AbstractFacade<Message> {
        @PersistenceContext(unitName = "MessagePU")
        private EntityManager em;
    
        @Override
        protected EntityManager getEntityManager() {
            return em;
        }
    
        public MessageFacade() {
            super(Message.class);
        }        
    
       public List<Message> queryMessages()
       {
        TypedQuery<Message> query = em.createNamedQuery( "Message.findAll", Message.class );
    
        return query.getResultList();
       }
    }
    

    An abstract facade class implementing generic persistence functions on generic entities.

    public abstract class AbstractFacade<T> {
        private Class<T> entityClass;
    
        public AbstractFacade(Class<T> entityClass) {
            this.entityClass = entityClass;
        }
    
        protected abstract EntityManager getEntityManager();
    
        public void create(T entity) {
            getEntityManager().persist(entity);
        }
    
        public T edit(T entity) {
            return getEntityManager().merge(entity);
        }
    
        public void remove(T entity) {
            getEntityManager().remove(getEntityManager().merge(entity));
        }
    
        public T find(Object id) {
            return getEntityManager().find(entityClass, id);
        }
    
        public List<T> findAll() {
            javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
            cq.select(cq.from(entityClass));
            return getEntityManager().createQuery(cq).getResultList();
        }
    

    Your managed bean would then be something like :

    @ViewScoped
    @ManagedBean
    public class MessageBean implements Serializable
    {
      @EJB
      private MessageFacade messageFacade;
      private List<Message> messages;
    
      public List<Message> getMessages()
      {
        return messages;
      }
    
      public void setMessages( List<Message> messages )
      {
        this.messages = messages;
      }
    
      @PostConstruct
      public void init()
      {
        messages = messageFacade.findAll();
      }
    
      public void onCellEdit( CellEditEvent event )
      {
        messageFacade.edit(messages);
      }
    }