Conversational CRUD in Java EE 6
This tutorial will demonstrate a pattern for creating CRUD applications in JSF and Java EE 6. While this is not the only way of implementing this mechanism, it does promote re-use and can give you essentially zero code CRUD pages requiring just the view code. The goal is to provide a single structure that provides the particular feature of being both stateless or conversational where we might want a conversational edit page and a stateless view page. This pattern is based on the EntityHome
pattern that was used in JBoss Seam and carries over well to Java EE 6 with CDI. This is something I use all the time to make view/edit pages really quickly and unlike most of the automatic scaffolding in other frameworks, doesn’t need re-writing to go into production.
Running in a Servlet Container
If you are running this in a servlet container or embedded Jetty, you will need to make the following changes :
- Use the
jee6-servlet-sandbox
archetype which is only available in Knappsack 1.0.5 upwards. - Remove the
@Stateless
annotations from theEntityManagerDao
- Add manual transactions to the entity manager method calls
The servlet version of the source code for this project can be downloaded from here (Crud App Maven Project Zip). Just unzip and type mvn jetty:run
CRUD is an initialism for Create, Retrieve, Update, Delete and is applied typically to entity objects or pieces of data in our application. CRUD probably accounts for about 80% of functions in most systems where users enter, update, view and sometimes delete data.
An entity object starts out by being created and then saved to the database. At a later date, the user may want to view the data and then modify and update the data. When the entity is no longer needed, the data will be deleted. This is the long term lifecycle of our data. When an entity is saved, it is saved with a key, or some reference value used to refer to that entity uniquely. Typically, this is an ID field that is often a number. The ID is assigned to the entity when it is saved and used to identify the entity when we want to retrieve, update or delete the entity.
Create our Application
We’ll start by creating a new application using the Knappsack Maven Archetypes. We’ll use the jee6-sandbox-archetype
so we have some data to play with. Because the Knappsack archetypes are fairly new, you might have problems finding them in your IDEs depending on when you updated from Maven Central, so here is the command line to create the project :
mvn archetype:generate -DarchetypeGroupId=org.fluttercode.knappsack -DarchetypeArtifactId=jee6-sandbox-archetype -DinteractiveMode=false -DarchetypeVersion=1.0.4 -DgroupId=org.fluttercode.demo -DartifactId=crudapp -Dpackage=org.fluttercode.demo.crudapp
You can create the app and then import it into your IDE as needed. We are going to start by creating a very simple DAO style bean that will be used to load, save and refresh objects using the JPA entity manager. We will make it a stateless session bean so that it has transactional capabilities. If you are using the servlet container version, see the sidebar above for necessary changes. The downloadable source code uses the servlet container version so you can run it without an application server.
- Create a new class called
EntityManagerDao
- Put the following code in the bean
package org.fluttercode.demo.crudapp.bean; import javax.ejb.Stateless; import javax.inject.Inject; import javax.persistence.EntityManager; import java.io.Serializable; import org.fluttercode.demo.crudapp.qualifier.DataRepository; @Stateless public class EntityManagerDao implements Serializable { @Inject @DataRepository private EntityManager entityManager; public Object updateObject(Object object) { return entityManager.merge(object); } public void createObject(Object object) { entityManager.persist(object); } public void refresh(Object object) { entityManager.refresh(object); } public <T> T find(Class<T> clazz, Long id) { return entityManager.find(clazz, id); } public void deleteObject(Object object) { entityManager.remove(object); } }
We inject a conversational entity manager into the bean and create methods for create, finding, updating, and removing entities. The
DataRepository
annotation is a Qualifier that is defined by the archetype and used to access the application’s default data source.
All this bean really does is provide transaction support on our method calls to the entity manager. It is not a part of the key pattern, just a mechanism we can use to persist objects transactionally. While this Dao will work for any JPA entity, it is common practice to create Dao objects specific to individual types where you can add helper and additional methods specific to that type.
Our home bean
Now we have our support code set up, on to the main feature! We will create a generic home bean that will take an Id and when requested, load the entity bean for that Id. If no Id is available, then we assume a new entity is to be created. Once loaded or created, the entity will be accessible from our JSF pages where we can display or edit the information.
- Create a new class called
org.fluttercode.demo.crudapp.bean.HomeBean
. This will be our generic home bean that we use to load and hold our entities. - Add the following code to make the bean generic and give it an id and entity attribute.
package org.fluttercode.demo.crudapp.bean; import java.io.Serializable; import javax.inject.Inject; import org.fluttercode.demo.crudapp.model.BaseEntity; public class HomeBean<T extends BaseEntity> implements Serializable { @Inject private EntityManagerDao entityManagerDao; private Long id; private T instance; }
Our bean will use the injected
EntityManagerDao
we just created. TheBaseEntity
class is a base entity class implemented in the archetype and has the commonId
attribute. - Now we want to add the getter method for the instance. What we want to do here is lazy load or create the bean when it is needed. The assumption is that you won’t request the bean until the home bean has been set up i.e. The id is properly set. The second assumption is that if you request the instance without the Id being set, you are actually creating a new instance.
- We’ll implement this in the getter that calls two other methods, one to create and return a new entity and another to load it. We’ll also add the getters and setters for the Id.
public T getInstance() { if (instance == null) { if (id != null) { instance = loadInstance(); } else { instance = createInstance(); } } return instance; } public Long getId() { return id; } public void setId(Long id) { this.id = id; }
- The create or load methods are fairly simply but rely on some reflection magic to get the generic parameter type :
public T loadInstance() { return entityManagerDao.find(getClassType(), getId()); } public T createInstance() { try { return getClassType().newInstance(); } catch (Exception e) { e.printStackTrace(); } return null; } private Class<T> getClassType() { ParameterizedType parameterizedType = (ParameterizedType) getClass() .getGenericSuperclass(); return (Class<T>) parameterizedType.getActualTypeArguments()[0]; }
- When we edit our entity, we will want to either save the changes, or undo them. We will add two methods to do this, save and cancel and a third utility method called
isManaged()
.public boolean isManaged() { return getInstance().getId() != null; } public String save() { if (isManaged()) { entityManagerDao.updateObject(getInstance()); } else { entityManagerDao.createObject(getInstance()); } conversation.end(); return "saved"; } public String cancel() { conversation.end(); return "cancelled"; }
We end the conversation when we are done with the data which is when we either save or cancel the changes. Once the conversation is ended, it will be destroyed at the end of the request.
- One last method we want to add is called
initConversation
that checks to see if we are in a long running conversation, and starts one if we are not. In theory we could put this method anywhere, but here is a good a place as any. We also need to add aConversation
attribute into which we will inject the current conversation.@Inject private Conversation conversation; ... ... ... public void initConversation() { if (conversation.isTransient()) { conversation.begin(); } }
This wraps up the coding of our HomeBean
that we will use as the backing bean for CRUD pages. Now let’s try out our new bean by implementing it for our Student
entity class and create a page to view and edit our students.
- Start by creating a new class called
org.fluttercode.demo.crudapp.view.StudentHome
. We add two class level annotations, one to name the bean so JSF can access it through an EL expression and another to give it a scope.package org.fluttercode.demo.crudapp.view; import javax.enterprise.context.ConversationScoped; import javax.inject.Named; import org.fluttercode.demo.crudapp.bean.HomeBean; import org.fluttercode.demo.crudapp.model.Student; @Named @ConversationScoped public class StudentHome extends HomeBean<Student>{ }
We subclass the
HomeBean
and give it ourStudent
class type to tell it what kind of entity we want it to handle. We have given this bean a conversation scope for reasons we’ll see later. - For our view, create a new page called
studentView.xhtml
with the following content :<?xml version="1.0" encoding="UTF-8"?> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:fn="http://java.sun.com/jsp/jstl/functions" template="/WEB-INF/templates/template.xhtml"> <ui:define name="content"> <f:metadata> <f:viewParam name="id" value="#{studentHome.id}" /> </f:metadata> <h:form> <h:outputText value="ID" styleClass="caption" /> <h:outputText value="#{studentHome.instance.id}" styleClass="value" /> <h:outputText value="First Name" styleClass="caption" /> <h:outputText value="#{studentHome.instance.firstName}" styleClass="value" /> <h:outputText value="Last Name" styleClass="caption" /> <h:outputText value="#{studentHome.instance.lastName}" styleClass="value" /> <h:outputText value="GPA" styleClass="caption" /> <h:outputText value="#{studentHome.instance.gpa}" styleClass="value" /> <h:link outcome="studentEdit.jsf" includeViewParams="true" value="Edit #{studentHome.instance.name}" style="margin-right : 24px" /> <h:link outcome="studentEdit.jsf" value="Create Student" /> </h:form> </ui:define> </ui:composition>
Everything up to the
f:metadata
tag is boilerplate which includes pulling the template into the page. Thef:metadata
tag tells JSF to take a parameter called id and put it in thestudentHome.id
attribute. This is done before the page is rendered as part of our initial setup for the bean so we know which entity to load.In the view code we bind our text output components to attributes on the instance in the student home bean. We also styled the components so we can alter the display using CSS styles.
At the bottom we have added a couple of links using the new JSF 2.0h:link
component to an as yet unwritten JSF page calledstudentEdit.xhtml
. In the first link, we get JSF to pass in the view parameters automatically which in this case is the student Id value. Our second link is to create a new student which means we call the samestudentEdit.xhtml
page without passing the id as a parameter which we do by leavinging theincludeViewParams
attribute set to false.Note that the
h:form
tag isn’t necessary, but will make the next step easier and allow us to add form controls to this form should we want to. - If you are running JBoss Developer Tools or Netbeans, you should be able to see the
Student
attributes in the auto-completion (very cool). If you open this page in a browser with an id (http://localhost:8080/crudapp/studentView.jsf?id=3) you will see the following person displayed :We did change two of the archetype CSS styles in the file
screen.css
to the following :.caption { float: left; width: 100px; padding-top : 2px; } .value { display : block; margin-bottom : 8px; margin-right: 8px; }
As you can see, we can display the student details for the given id. The page for editing this person is very similar, so similar in fact that we are going to start by copying the student view page and making 3 line changes and adding a page event.
- Copy the
studentView.xhtml
file and paste it in the same folder with the namestudentEdit.xhtml
. Open it up and make the following changes (highlighted in the source) :- Change the
outputText
components bound to attributes toinputText
components. - Add the
preRenderView
event the thef:metadata
tag. - Set the
rendered
attribute on the ID label and value since new entities don’t have a value to display
<ui:define name="content"> <f:metadata> <f:viewParam name="id" value="#{studentHome.id}" /> <f:event type="preRenderView" listener="#{studentHome.initConversation}" /> </f:metadata> <h:form> <h:outputText value="ID" styleClass="caption" rendered="#{studentHome.managed}"/> <h:outputText value="#{studentHome.instance.id}" styleClass="value" rendered="#{studentHome.managed}"/> <h:outputText value="First Name" styleClass="caption" /> <h:inputText value="#{studentHome.instance.firstName}" styleClass="value" /> <h:outputText value="Last Name" styleClass="caption" /> <h:inputText value="#{studentHome.instance.lastName}" styleClass="value" /> <h:outputText value="GPA" styleClass="caption" /> <h:inputText value="#{studentHome.instance.gpa}" styleClass="value" /> <h:commandButton value="Save" action="#{studentHome.save}" style="margin-right : 8px" /> <h:commandButton value="Cancel" action="#{studentHome.cancel}" immediate="true" style="margin-right : 8px" /> </h:form> </ui:define>
If you launch this page now (http://localhost:8080/crudapp/studentEdit.jsf?id=3), you will see that we have an editor filled with the attribute values that can be changed.
- Change the
That was pretty easy! The reason being is that we re-used the StudentHome
bean in our edit page because it performs the same role in providing the requested entity based on the bean’s id value. We don’t need to tell the home bean that it is being used somewhere else because the page pulls the bean into the page.
Conversational or Not?
It’s important to notice that in the view page we do not call the method to initialize the conversation. Conversational beans that are not in a long running conversation are reduced to request scoped because the conversation context is destroyed at the end of the request if it is not marked as long running. This means that our view page is fairly stateless since the StudentHome
bean used in this page is effectively request scoped. This is actually a good thing since when we press the refresh button, the page is regenerated from scratch instead of using an older version of the data held in the conversation.
On the other hand, the edit page does call the initConverstion
method which starts a conversation. This means that the edit page is conversational which is good because it carries the state from one request to the next without relying on our page to support that state or having to reload it each time. Without the conversation we would have to manually propagate the id value from one request to the next, and possibly other values that are a part of the entity but not edited on the page. For example, if we had an addedDate
attribute, we might want to display it, but not edit it, but without conversations we have to resort to either putting it in a hidden field in the form, or re-loading the object to get the value on postback. Otherwise how can we have the right value when we save the edited entity to the database?
This dual nature of the conversation scope is a very useful feature of CDI (and Seam before it) because it allows one bean to act in two different scopes depending on whether the conversation is long running or not which is ideal for view and edit pages.
This is a simple design that can be re-used anywhere you need to edit an entity with little or no code changes.
You can download the servlet version of the demo project from here (Crud App Maven Project Zip). Just unzip it and run mvn jetty:run
Additional Points
Here are some addition points that are not a core part of the demonstration but flesh things out a bit more. All these changes are included in the maven project that can be downloaded.
Do we need an Entity Manager DAO?
Here we used a simple DAO to wrap calls to the EntityManager. There’s no reason you couldn’t make your Entity Home bean an EJB, inject the entity manager and persist the entities directly. With a separate entity manager DAO it makes writing code for servlet containers cleaner since the explicit transactions are bundled in the DAO. Also, it is possible to write one DAO bean for Java EE 6 servers and another for Servlet containers and switch between them using @Alternative
.
Deleting is a part of CRUD
I left deleting out primarily because it is fairly simple and the focus was really on the retrieve and update pieces which is often trickier. To implement delete, we add a method to the HomeBean
to end the conversation and delete the item.
public String delete() { entityManagerDao.deleteObject(getInstance()); conversation.end(); return "deleted"; }
Now we just need to add a button to call the delete method from the edit page.
<h:commandButton value="Delete" action="#{studentHome.delete}"/>
When we click the button, we delete the entity, and return the action value “deleted” for which, in the next section, we’ll add navigation to the home page.
Finding your way around
We haven’t included any navigation here so when you save or cancel changes, you stay on the same page. So far I’ve left it out because it isn’t central to the CRUDness of the tutorial. However, we can easily add some navigation in faces-config.xml
.
<navigation-rule> <from-view-id>/studentEdit.xhtml</from-view-id> <navigation-case> <from-outcome>saved</from-outcome> <to-view-id>/studentView.xhtml</to-view-id> <redirect include-view-params="true" /> </navigation-case> <navigation-case> <from-outcome>cancelled</from-outcome> <if>#{studentHome.managed}</if> <to-view-id>/studentView.xhtml</to-view-id> <redirect include-view-params="true" /> </navigation-case> <navigation-case> <from-outcome>cancelled</from-outcome> <if>#{!studentHome.managed}</if> <to-view-id>/home.xhtml</to-view-id> <redirect include-view-params="true" /> </navigation-case> <navigation-case> <from-outcome>deleted</from-outcome> <to-view-id>/home.xhtml</to-view-id> </navigation-case> </navigation-rule>
Notice that when we cancel the changes, we go to either the view page or the home page depending on whether the entity exists in the database.
Better Homes
Let’s also add on a better home page so you can see and select different students. Add the following method to the DataFactory.java
class that comes with the archetype.
@Produces @Named("students") public List<Student> getStudents() { List<Student> students = entityManager.createQuery( "select s from Student s").setMaxResults(20) .getResultList(); return students; }
Open up the the home.xhtml
page and change the content to :
<?xml version="1.0" encoding="UTF-8"?> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:fn="http://java.sun.com/jsp/jstl/functions" template="/WEB-INF/templates/template.xhtml"> <ui:define name="content"> <h1>Courses</h1> <h:form> <h:dataTable value="#{students}" var="v_student" styleClass="dataTable" rowClasses="odd,even"> <h:column> <f:facet name="header">Id </f:facet> #{v_student.id} </h:column> <h:column> <f:facet name="header">Name </f:facet> <h:link outcome="studentView.jsf?id=#{v_student.id}" value="#{v_student.name}" /> </h:column> </h:dataTable> </h:form> <h:link outcome="studentEdit.jsf" value="Create Student" /> </ui:define> </ui:composition>
This will list all the students and let you edit them or add new ones. The project source that can be downloaded (Crud App Maven Project Zip) includes these changes plus validation error messages on controls.
7 thoughts on “Conversational CRUD in Java EE 6”
Comments are closed.
Very nice tutorial.
Congratulations !!
Is the best cdi, jsf, crud tutorial I saw.
Thank you.
Thanks for your posts and articles, they have been very helpful in learning javaee, I also like your critical and contextual approach. I have some comments and questions below, generally in increasing scope order.
em.merge()
———-
Is the dao.updateObject() -> em.merge() call needed ? It seems to be a no-op and that it suffices to commit the TX because that particular entity instance is already managed in the conversation-scoped PC. In fact, it was confusing because it would actually be important with a request-scoped PC.
session serialization
———————
It throws an exception if serialized in midst of a conversation:
view -> edit >>> serialize session -> exception …
This is probably a bug in weld (after making the entities serializable) which prevents session serialization / replication.
WARNING: Cannot serialize session attribute org.jboss.weld.context.SessionContext#org.jboss.weld.bean-weld-ManagedBean-class org.jboss.weld.conversation.ServletConversationManager for session 2332CFF85495E593B2A6CD629788ED2F
java.io.NotSerializableException: org.jboss.weld.conversation.ManagedConversation
unbounded session size
———————-
Reloading the edit page creates a new conversation each time and keeps increasing the session (two new objects per reload) until the conversation/session timeout. Is there a way to limit the number of conversations per session ? What is the appropriate level to do so (web, cdi, …) ?
comparison to request-scoped PC
——————————-
Yes, this is the perpetual stateful vs. stateless issue 🙂 How does the conversation-scoped PC compares with a request-scoped PC ? The em.merge() is a no-op for a conversation-scoped PC, while it would hit the db for a request-scoped PC. What if we add second-level caching ? Then, the two approaches start to look similar (session ~ second-level cache). What if we add clustering ?
Hey George,
Thanks for the detailed response.
The merge is there because this is a fairly generic dao and it’s possible that this isn’t executed in an active conversation. The code was also meant to run under both Java EE 6 and servlet containers. It may need a bit of tweaking depending on the environment, I had some trouble keeping the entity managed initially (although I can’t remember specifics).
The serialization looks like a Weld issue depending on what piece was not serializable.
Regarding session sizes, yes, continual reloading the page will create new conversations. I don’t know of anything to limit the conversations per session. If this becomes an issue I think we’ll probably see something in Weld Extensions, with an eye to including it in the next version of Java EE.
Conversation scoped PC is useful for a few reasons, it lets you have all your state in one place and gives the application a feeling of being like a thick client server app (even though the state is remote). Lazy loading isn’t an issue, and you can pretty much navigate the whole object graph. However, this is probably best implemented in a controlled manner. Obviously, don’t load #{customer.orders} just to locate the order with id 123! It lets you capture the state of the database for the duration of the conversation, a bit like the transaction type that allows repeatable queries (except we aren’t repeating the query, we already have the data there).
If conversation scope gets too heavy for a particular activity or process, you can always re-design it to be stateless. OTOH, without conversation scope, all you have is the stateless scope so you always have to deal with those issues. Conversation scope is almost like a second level cache specific to the user. Obviously it will result in more memory use since each user has their own copy, but again, you get benefits. These are all fairly high level aspects that depend on the type of application you have, the number of users and probably other factors.
For clustered environments, there probably should be a recommended practice, but I don’t know what that is!
Cheers,
Andy Gibson
When I try to run the project on GlassFish 3.0.1 I get the following:
SEVERE: Error Rendering View[/home.xhtml]
org.jboss.weld.exceptions.WeldException: WELD-001000 Error resolving property demoCourseList against base null
at org.jboss.weld.el.AbstractWeldELResolver.getValue(AbstractWeldELResolver.java:133)
at javax.el.CompositeELResolver.getValue(CompositeELResolver.java:175)
Ryan, that’s a fairly common error that usually indicates something else is off. Did you start the javaDB database? Sometimes you might need to restart everything in order to get it all to click just right so the DB is up, the app is installed and creates the DB items, and everything runs smoothly.
If all else fails, check the full stack trace and Email it to me. Since you are running with 3.0.1, depending on the build, it could be a Glassfish issue since they had varying problems (3.1 build 25 is doing very nicely for me right now)
Cheers,
Andy Gibson
Instead of using the code from maven I tried the zip. The code in the zip works, but I get a few errors on startup:
…
SEVERE: SLF4J: The requested version 1.5.10 by your slf4j binding is not compatible with [1.5.5, 1.5.6]
SEVERE: SLF4J: See http://www.slf4j.org/codes.html#version_mismatch for further details.
…
and
…
INFO: Hibernate EntityManager 3.5.0.Beta1
SEVERE: URL: file:/C:/Users/ryans/Documents/NetBeansProjects/crudapp/target/crudapp/WEB-INF/classes/
META-INF/orm.xml
INFO: Binding entity from annotated class: org.fluttercode.demo.crudapp.model.Person
…