【问题标题】:JPA entities in JSF @ViewScoped backing bean always getting detached?JSF @ViewScoped 支持 bean 中的 JPA 实体总是被分离?
【发布时间】:2014-05-06 15:25:05
【问题描述】:

我目前正在尝试学习 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 来实现类似的目标)?

我将 TomEE(MyFaces、OpenJPA)与 PrimeFaces 结合使用。支持 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 数据表从 JSF 页面引用支持 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();
    }
  }

  [...]

【问题讨论】:

    标签: jsf jpa serialization view-scope apache-tomee


    【解决方案1】:

    在提交事务之前,您必须启动它(然后在事务结束时关闭它)。 commitTransaction 方法中的 else 语句在哪里,以防 EntityTransaction 对象不活动和/或 null

    另外,我在您的代码中没有看到任何 EJBPOJO 方法不是由容器管理、服务和托管的应用程序的最佳选择。

    对我来说,在 JSFJavaEE 应用程序中实现持久层的最佳方法是 Session Façade Pattern,你可以在网上搜索一下,有很多参考资料。

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

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

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

    【讨论】:

    • 你说得对,就像我说的我的实现只是原型的尝试。我实际上忘了复制交易开始的那一行(这是一个精简的摘录)。您对会话外观模式的建议对我很有帮助,我会尝试使用它。但是,我的问题本质上是更原则性的:当人们想要在具有钝化能力范围的 JSF 支持 bean 中使用 JPA 实体时,是否真的不可避免地使用这种“解决方法”?我发现在某些用例中这是必需的,但对于一个简单的用例来说,这对我来说就像是样板代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-10
    • 1970-01-01
    • 2014-06-21
    • 2013-09-02
    • 2011-05-09
    • 1970-01-01
    相关资源
    最近更新 更多