JSF, JPA, and EJB are three important aspects that can be put together to create a robust and reliable enterprise application under the JEE framework. Interestingly, these three performer fit into the three-level architecture of the MVC (Model-View-Controller) model. Grossly, the role played in the arena is that JPA can be used to define a persistence model for the back-end relational database, transforming relational schema into an object. EJB reigns the business logic for data transformation, validation, and so forth, and JSF provides the eye candy, a user-friendly interface. Integrating these three pieces is sufficient in most cases to create an enterprise business application. In this article, I shall try to give a bird’s eye view of these three technologies and a small application code to get your hands dirty.
Citation: To depict the relevant idea, pictures are taken randomly from a Google search. Watermarks on the images for copyright information are kept intact wherever found. |
JPA (Java Persistence Annotation)
Enterprise applications are infamous for their need to collect, process, transform, and report on a huge amount of data. Although the Java platform is excellent at handling RDBMS, it is quite inconvenient to move data back and forth between a relational model and an object model through plain old JDBC programming. Not only Java, this problem is faced by every object-oriented programming language when they want to communicate with relational databases that are inherently non-object oriented. Both Java and RDBMS are unrelated technologies, even though they’re reputed to be quite stable down the years. Their architectural background does not blend well and it is unwise to disturb the stability through architectural re-modification. So, to find a way where Java will talk about objects and classes but the RDBMS will understand SQL or vice-versa, JPA emerged. It is as if JPA creates a MoU between two bloated egos and makes the Java programmers happy in an interesting manner. Thus, Java provided a standard solution to answer the problem with the help of Java Persistence APIs.
Java | JPA + Persistence Provider | RDBMS |
“I’ll not speak any other way except object oriented.” |
“You don’t have to; I’ll handle it. Talk to me.” |
“You wanna piece of me! Learn SQL and be procedural.” |
JSF (JavaServer Faces)
JavaServer Faces initially may seem to be a simple HTML-based rendering of HTML-based web applications. This, however, is a gross underestimation. JSF is rich with its extensible component model where we can customize basic HTML to give a new look and flexibility. This power of extensibility provided a fertile ground for several third-party component suites, such as IceFaces, RichFaces, PrimeFaces, and so on. PrimeFaces is quite popular among the Java EE web development community. This lightweight JSF component suite comes with a single JAR file with no dependency requirement. As a result, configuration is simple and hassle free; one needs only to add the artifact for the library and start using its features in XHTML pages of the web application.
EJB (Enterpirse Javabeans)
A technology was needed to unify RMI, JTA, and CORBA under a common banner of a standard component development model. From inception, EJB emerged from it ruin every time, encompassing numerous features while judiciously rejecting others. I am pretty sure you would not have liked it the way is was programmed when this technology toddled in during the late 90s. But now, it has matured into a robust and standard framework for deploying and executing business components in a distributed environment. Though EJB’s playground is distributed, multi-tiered applications, it is equally suitable for a single-user application in view of making it multi-user in the near future. Interestingly, in the MVC model, EJB is uninfluenced by both the front-end view layer and back-end model. So, any minimal changes made in any of these layers leaves the other unaffected. This is a very welcome feature of the MVC model where EJB plays the role of delivering actual business logic of the application.
Getting Your Hands Dirty
The project will follow the following principles.
- Models (JPA): The Model classes will be solely responsible for object persistent, as we have discussed, an object representation of the database table. EJB will talk with the database with the help of this objects. There is a Persistence provider (here, EclipseLink) that provides a manager (EntityManager) to mediate object persistence between the database and the EJB.
- Business Logic (EJB): Always strictly adherent to business, EJB will provide the internal logic of what to communicate from the application.
- Controllers (JSF): The Controller handshakes with EJB to create the ground for the views.
- Views (Primefaces): Comprises all eye-candy visual components rendered in the browser through XHTML
Note: EclipseLink is the open source Eclipse Persistence Services Project from the Eclipse Foundation. The software provides an extensible framework that allows Java developers to interact with various data services, including databases, web services, Object XML mapping (OXM), and Enterprise Information Systems (EIS): excerpt from Wikipedia. |
It can be pictorially depicted as shown in the following illustrations.
PrimeFaces | JSF Controller | EJB | JPA + Database |
UI components with extensible theme | Controller grooming for usability | EJB at work | JPA stuffing data into the back-end database |
View | Controller+Business logic | Model |
Model Class
package org.app.model;
//...import statements
@Entity
@Table(name = "PERSON")
@NamedQueries({
@NamedQuery(name = "Person.findAll",
query = "SELECT p FROM Person p"),
@NamedQuery(name = "Person.findByPersonid",
query = "SELECT p FROM Person p
WHERE p.personid = :personid"),
@NamedQuery(name = "Person.findByFirstname",
query = "SELECT p FROM Person p
WHERE p.firstname = :firstname"),
@NamedQuery(name = "Person.findByLastname",
query = "SELECT p FROM })
publicclass Person implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private Integer personid;
private String firstname;
private String lastname;
@Temporal(TemporalType.DATE)
private Date birthdate;
private String address1;
private String address2;
private String email;
private String phone;
//... constructors, getters, and setters
}
EJB Class
@Stateless public abstract class PersonEJB { @PersistenceContext(unitName = "JSF_EJB_JPA_DemoPU") protected abstract EntityManager getEntityManager(); public void create(Person entity) { getEntityManager().persist(entity); } public void edit(Person entity) { getEntityManager().merge(entity); } public void remove(Person entity) { getEntityManager().remove(getEntityManager().merge(entity)); } public Person find(Object id) { return getEntityManager().find(Person.class, id); } public List<Person> findAll() { CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery(); cq.select(cq.from(Person.class)); return getEntityManager().createQuery(cq).getResultList(); } public List<Person> findRange(int[] range) { CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery(); cq.select(cq.from(Person.class)); javax.persistence.Query q = getEntityManager().createQuery(cq); q.setMaxResults(range[1] - range[0] + 1); q.setFirstResult(range[0]); return q.getResultList(); } public int count() { CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery(); Root<Person> rt = cq.from(Person.class); cq.select(getEntityManager().getCriteriaBuilder().count(rt)); javax.persistence.Query q = getEntityManager().createQuery(cq); return ((Long) q.getSingleResult()).intValue(); } }
Controller Class
@ManagedBean(name = "personController") @SessionScoped public class PersonController implements Serializable { @EJB PersonEJB ejb; private List<Person> items = null; private Person selected; public static enum PersistAction { CREATE, DELETE, UPDATE } public Person getSelected() { return selected; } public void setSelected(Person selected) { this.selected = selected; } private PersonEJB getEJB() { return ejb; } public Person prepareCreate() { selected = new Person(); return selected; } public void create() { persist(PersistAction.CREATE, "Person Created successfully."); if (!FacesContext.getCurrentInstance().isValidationFailed()) { items = null; } } public void update() { persist(PersistAction.UPDATE,"Person updated successfully."); } public void destroy() { persist(PersistAction.DELETE,"Person deleted successfully."); if (!FacesContext.getCurrentInstance().isValidationFailed()) { selected = null; items = null; } } public List<Person> getItems() { if (items == null) items = getEJB().findAll(); return items; } private void persist(PersistAction persistAction, String msg) { if (selected != null) { try { if (persistAction != PersistAction.DELETE) { getEJB().edit(selected); } else { getEJB().remove(selected); } addSuccessMessage(msg); } catch (EJBException ex) { String msg = ""; Throwable cause = ex.getCause(); if (cause != null) { msg = cause.getLocalizedMessage(); } if (msg.length() > 0) { addErrorMessage(msg); } else { addErrorMessage("A persistence error occurred."); } } catch (Exception ex) { addErrorMessage("A persistence error occurred."); } } } public List<Person> getItemsAvailableSelectMany() { return getEJB().findAll(); } public List<Person> getItemsAvailableSelectOne() { return getEJB().findAll(); } @FacesConverter(forClass = Person.class) public static class PersonControllerConverter implements Converter { @Override public Object getAsObject(FacesContext fc, UIComponent c, String v) { if (v == null || v.length() == 0) { return null; } PersonController controller = (PersonController) fc.getApplication().getELResolver(). getValue(fc.getELContext(), null, "personController"); return controller.getEJB().find(getKey(v)); } Integer getKey(String value) { Integer key; key = Integer.valueOf(value); return key; } String getStringKey(Integer value) { StringBuilder sb = new StringBuilder(); sb.append(value); return sb.toString(); } @Override public String getAsString(FacesContext fc, UIComponent c, Object object) { if (object == null) { return null; } if (object instanceof Person) { Person o = (Person) object; return getStringKey(o.getPersonid()); } else { return null; } } } public void addSuccessMessage(String msg) { FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_INFO, msg, msg); FacesContext.getCurrentInstance().addMessage("successInfo", facesMsg); } public void addErrorMessage(String msg) { FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg); FacesContext.getCurrentInstance().addMessage(null, facesMsg); } }
XHTML Views
CreateEditPerson.xhtml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html ...> <ui:composition> <p:dialog id="PersonCreateDlg" widgetVar="PersonCreateDialog" modal="true" resizable="false" appendTo="@(body)" header="Create Person"> <h:form id="PersonCreateForm"> <h:panelGroup id="display"> <p:panelGrid columns="2" rendered="#{personController.selected != null}"> <p:outputLabel value="PersonId" for="personid" /> <p:inputText id="personid" value="#{personController.selected.personid}" title="PersonId" required="true" requiredMessage="ID cannot be empty"/> <p:outputLabel value="First Name" for="firstname" /> <p:inputText id="firstname" value="#{personController.selected.firstname}" title="First Name" required="true" requiredMessage="First Name cannot be empty"/> <p:outputLabel value="Last Name" for="lastname" /> <p:inputText id="lastname" value="#{personController.selected.lastname}" title="Last Name" required="true" requiredMessage="Last Name cannot be empty"/> <p:outputLabel value="Birth Date" for="birthdate" /> <p:calendar id="birthdate" pattern="dd/MM/yyyy" value="#{personController.selected.birthdate}" title="Birth Date" required="true" requiredMessage="Birthdate canot be empty" showOn="button"/> <p:outputLabel value="Address 1" for="address1" /> <p:inputText id="address1" value="#{personController.selected.address1}" title="Address 1" required="true" requiredMessage="Address1 cannot be empty"/> <p:outputLabel value="Address 2" for="address2" /> <p:inputText id="address2" value="#{personController.selected.address2}" title="Address 2" required="true" requiredMessage=""Address2 cannot be empty""/> <p:outputLabel value="Email" for="email" /> <p:inputText id="email" value="#{personController.selected.email}" title="Email" required="true" requiredMessage="Email cannot be empty"/> <p:outputLabel value="Phone" for="phone" /> <p:inputText id="phone" value="#{personController.selected.phone}" title="Phone" required="true" requiredMessage="Phone cannot be empty"/> </p:panelGrid> <p:commandButton actionListener="#{personController.create}" value="Save" update="display,:PersonListForm:datalist,:growl" oncomplete="handleSubmit(args,'PersonCreateDialog');"/> <p:commandButton value="Cancel" onclick="PersonCreateDialog.hide()"/> </h:panelGroup> </h:form> </p:dialog> </ui:composition> </html>
ViewPerson.xhtml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE ...> <ui:composition> <p:dialog id="PersonViewDlg" widgetVar="PersonViewDialog" modal="true" resizable="false" appendTo="@(body)" header="#{bundle.ViewPersonTitle}"> <h:form id="PersonViewForm"> <h:panelGroup id="display"> <p:panelGrid columns="2" rendered="#{personController.selected != null}"> <h:outputText value="personId"/> <h:outputText value="#{personController.selected.personid}" title="personId"/> <h:outputText value="First Name"/> <h:outputText value="#{personController.selected.firstname}" title="First Name"/> <h:outputText value="Last Name"/> <h:outputText value="#{personController.selected.lastname}" title="Last Name"/> <h:outputText value="Birth Date"/> <h:outputText value="#{personController.selected.birthdate}" title="Birth Date"> <f:convertDateTime pattern="MM/dd/yyyy" /> </h:outputText> <h:outputText value="Address 1"/> <h:outputText value="#{personController.selected.address1}" title="Address 1"/> <h:outputText value="#Address 2"/> <h:outputText value="#{personController.selected.address2}" title="Address 2"/> <h:outputText value="#Email"/> <h:outputText value="#{personController.selected.email}" title="Email"/> <h:outputText value="#Phone"/> <h:outputText value="#{personController.selected.phone}" title="Phone"/> </p:panelGrid> <p:commandButton value="Close" onclick="PersonViewDialog.hide()"/> </h:panelGroup> </h:form> </p:dialog> </ui:composition> </html>
ListPerson.xhtml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE ...> <ui:composition template="/template.xhtml"> <ui:define name="title"> <h:outputText value="List of Person"></h:outputText> </ui:define> <ui:define name="body"> <h:form id="PersonListForm"> <p:panel header="List of Person"> <p:dataTable id="datalist" value="#{personController.items}" var="item" selectionMode="single" selection="#{personController.selected}" paginator="true" rowKey="#{item.personid}" rows="10" rowsPerPageTemplate="10,20,30,40,50"> <p:ajax event="rowSelect" update="createButton viewButton editButton deleteButton"/> <p:ajax event="rowUnselect" update="createButton viewButton editButton deleteButton"/> <p:column> <f:facet name="header"><h:outputText value="personId"/></f:facet> <h:outputText value="#{item.personid}"/> </p:column> <p:column> <f:facet name="header"> <h:outputText value="First NAme"/> </f:facet> <h:outputText value="#{item.firstname}"/> </p:column> <p:column><f:facet name="header"> <h:outputText value="Last Name"/></f:facet> <h:outputText value="#{item.lastname}"/> </p:column> <p:column> <f:facet name="header"> <h:outputText value="Birth Date"/> </f:facet> <h:outputText value="#{item.birthdate}"> <f:convertDateTime pattern="MM/dd/yyyy" /> </h:outputText> </p:column> <p:column> <f:facet name="header"> <h:outputText value="Address 1"/> </f:facet> <h:outputText value="#{item.address1}"/> </p:column> <p:column> <f:facet name="header"> <h:outputText value="Address 2"/> </f:facet> <h:outputText value="#{item.address2}"/> </p:column> <p:column> <f:facet name="header"> <h:outputText value="Email"/> </f:facet> <h:outputText value="#{item.email}"/> </p:column> <p:column> <f:facet name="header"> <h:outputText value="Phone"/> </f:facet> <h:outputText value="#{item.phone}"/> </p:column> <f:facet name="footer"> <p:commandButton id="createButton" icon="ui-icon-plus" value="Create" actionListener="#{personController.prepareCreate}" update=":PersonCreateForm" oncomplete="PF('PersonCreateDialog').show()"/> <p:commandButton id="viewButton" icon="ui-icon-search" value="View" update=":PersonViewForm" oncomplete="PF('PersonViewDialog').show()" disabled="#{empty personController.selected}"/> <p:commandButton id="editButton" icon="ui-icon-pencil" value="Edit" update=":PersonEditForm" oncomplete="PF('PersonEditDialog').show()" disabled="#{empty personController.selected}"/> <p:commandButton id="deleteButton" icon="ui-icon-trash" value="Delete" actionListener="#{personController.destroy}" update=":growl,datalist" disabled="#{empty personController.selected}"/> </f:facet> </p:dataTable> </p:panel> </h:form> <ui:include src="CreateEditPerson.xhtml"/> <ui:include src="CreateEditPerson.xhtml"/> <ui:include src="ViewPerson.xhtml"/> </ui:define> </ui:composition> </html>
Output
Figure 1: Output of the HTML files
Conclusion
The best part of integrating EJB, JSF, and JPA is that Netbeans IDE can auto-generate most of the preceding code. JSF, EJB, and JPA are quite a mature framework. Developers who work with struts may find JSF lacking quite a few features, yet web development with these trio is quite enjoyable and they integrate pretty well. If you have been planning for a small- to medium-scale web application, this trio will deliver most of your needs.