JSF @ViewScoped支持bean中的JPA实体总是脱离?

时间:2014-05-06 15:25:05

标签: jsf jpa serialization view-scope tomee

我目前正在尝试学习JSF和JPA。我知道我使用的模式根本不被推荐,但我想了解发生了什么,因为我认为它将来会帮助我。我只是把各种来源的原型拼凑在一起。

我在下面描述的设置中遇到的问题是,显然JPA实体一直在分离,这反过来发生,因为支持bean被反复序列化。实际上,如果我从实体类中删除Serializable接口,我会得到Exiting serializeView - Could not serialize state: com.sk.Message

由于实体是分离的,因此当我调用EntityManager.commit()时,没有任何内容会被提交到数据库。如果我手动将所有实体(下面的onCellEdit()中注释掉的行)与EntityManager.merge()合并,则修改后的实体将被提交到数据库。

我已经从其他SO帖子中找到了我可以通过添加

来解决这个问题
<context-param>
    <param-name>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</param-name>
    <param-value>false</param-value>
</context-param>

到我的persistence.xml。但也有人指出,这只是一种解决方法而不是解决方案。

所以我的问题是:

  1. 是否/期望@ViewScoped JSF支持bean反复序列化(同时始终保持相同的视图),这使得在其中使用JPA实体变得困难?
  2. 使用SERIALIZE_STATE_IN_SESSION参数是否安全/合理?
  3. 正如我多次推荐的那样,我是否应该完全忘记JSF托管bean并直接使用CDI(例如@ConversationScope来实现类似的东西)?
  4. 我在PrimeFaces中使用TomEE(MyFaces,OpenJPA)。辅助bean包含以下代码:

    @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();
      }
    
      [...]
    

    Message是JPA实体,如下所示:

    @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;
    
       [...]
    }
    

    使用PrimeFaces DataTable从页面引用支持bean:

    <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>
    
            [...]
    

    我知道我可能在这里违反了许多最佳做法,但是为了进行原型设计,我创建了一个单例POJO PersistenceManager,它处理JPA接口(以及可能的其他数据源) 。我使用应用程序管理的资源本地EntityManager。摘录如下:

    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();
        }
      }
    
      [...]
    

1 个答案:

答案 0 :(得分:2)

在提交交易之前,您必须启动它(然后在交易结束时关闭它)。如果else对象未激活和/或commitTransactionEntityTransaction方法中的null语句在哪里?

另外,我在代码中看不到任何 EJB POJO 方法不是由容器管理,提供和托管的应用程序中的最佳选择。

对我来说,在 JSF JavaEE 应用程序中实现持久层的最佳方法是会话外观模式,你可以在网上搜索它,有很多参考资料。

在你的情况下,这样的事情就可以了。

消息Facade,用于管理与Message实体相关的事务。

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();
   }
}

在通用实体上实现通用持久性函数的抽象外观类。

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();
    }

您的托管bean将类似于:

@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);
  }
}