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