Spring to Java EE Migration, Part 3
By David Heffelfinger
CTO and ardent Java EE fan David Heffelfinger demonstrates how easy it is to develop the data layer of an application using Java EE, JPA, and the NetBeans IDE instead of the Spring Framework.
Downloads:
Introduction
In Part One and Part Two of this series, we generated a complete Java EE application by using JavaServer Faces (JSF) 2.0, Enterprise JavaBeans (EJB) 3.1, and Java Persistence API (JPA) 2.0 from Spring’s Pet Clinic MySQL schema. With only a few clicks, we were able to develop an application whose functionality is equivalent to that provided by this Spring sample application.
We also analyzed the generated code, noting that it employs some nice JSF features such as Facelets templating, data models, and converters, as well as some advanced JPA features such as annotation attributes that can be used to regenerate tables, while retaining information such as the maximum allowed length and whether the field is nullable. It also includes bean validation support.
In this article, we will tweak the application to make it more user friendly. The generated application displays primary keys on some of the pages, and these keys are surrogate primary keys—meaning that they have no business value and are used strictly as a unique identifier—so there is no reason why they should be visible to the user. In addition, we will modify some of the generated labels to make them more user-friendly.
Tweaking the User Interface
The following screen shot illustrates how the generated application looks.
There are some minor improvements we can make. For starters, the page has a generic title we should change.
Looking at the generated index.xhtml file, you can see the following markup:
<h:head>
<title>Facelet Title</title>
<h:outputStylesheet name="css/jsfcrud.css"/>
</h:head>
The JSF 2.0 <h:head>
tag is analogous to the standard HTML <head>
tag. We can change the page title by simply modifying the text inside the <title>
tag as follows:
<title>Pet Clinic</title>
While we are modifying this page, we may as well make a few more simple modifications: add an <h1>
tag with the page title, plus modify each link text with more user-friendly wording.
After these simple changes, our application now looks like this:
Navigating through the application, you can see some labels that were obviously generated from the corresponding properties in the JPA entities. For instance, clicking the Display All Owners link takes us to a page that displays all the pet owners in the database.
If we examine the markup for the page generating the above table, we can see that these labels (and most other labels, for that matter) are taken from a resource bundle named “bundle.”
Note that the first name and last name headers are FirstName and LastName. This happens because these labels were derived from the corresponding attributes in the JPA Owner entity.
Listing 1. Resource Bundle for Labels
<h:dataTable value="#{ownerController.items}" var="item" border="0"
cellpadding="2" cellspacing="0"
rowClasses="jsfcrud_odd_row,jsfcrud_even_row"
rules="all" style="border:solid 1px">
<h:column>
<f:facet name="header">
<h:outputText value="#{bundle.ListOwnerTitle_id}"/>
</f:facet>
<h:outputText value="#{item.id}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{bundle.ListOwnerTitle_firstName}"/>
</f:facet>
<h:outputText value="#{item.firstName}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{bundle.ListOwnerTitle_lastName}"/>
</f:facet>
<h:outputText value="#{item.lastName}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{bundle.ListOwnerTitle_address}"/>
</f:facet>
<h:outputText value="#{item.address}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{bundle.ListOwnerTitle_city}"/>
</f:facet>
<h:outputText value="#{item.city}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{bundle.ListOwnerTitle_telephone}"/>
</f:facet>
<h:outputText value="#{item.telephone}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value=" "/>
</f:facet>
<h:commandLink action="#{ownerController.prepareView}"
value="#{bundle.ListOwnerViewLink}"/>
<h:outputText value=" "/>
<h:commandLink action="#{ownerController.prepareEdit}"
value="#{bundle.ListOwnerEditLink}"/>
<h:outputText value=" "/>
<h:commandLink action="#{ownerController.destroy}"
value="#{bundle.ListOwnerDestroyLink}"/>
</h:column>
</h:dataTable>
Inspecting the faces-config.xml
file for our application reveals that “bundle” is mapped to a property file named Bundle.properties.
Listing 2. Modifying the Labels
<faces-config version="2.0"
xmlns=""
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://bit.ly/17U4sqz">
<application>
<resource-bundle>
<base-name>/Bundle</base-name>
<var>bundle</var>
</resource-bundle>
</application>
</faces-config>
By modifying the labels in Bundle.properties
(not shown, because it is long—it does show up as part of the code download for this article), we can make the application more user-friendly. After some simple replacements in this file, the Owners page now looks like this:
The page title and the header are now Owners, as opposed to the previous generic value List. We also changed the first name and last name headers to read First Name and Last Name and changed the link for deleting an owner from Destroy to Delete.
Now that we have modified the labels, we should remove the Id column from the table, because the primary keys don’t provide any useful information to the user. We can do this by simply removing the first <h:column>
tag from the JSF data table.
Listing 3. Removing Id Column from Table
<h:dataTable value="#{ownerController.items}" var="item"
border="0" cellpadding="2" cellspacing="0"
rowClasses="jsfcrud_odd_row,jsfcrud_even_row"
rules="all" style="border:solid 1px">
<h:column>
<f:facet name="header">
<h:outputText
value="#{bundle.ListOwnerTitle_id}"/>
</f:facet>
<h:outputText
value="#{item.id}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText
value="#{bundle.ListOwnerTitle_firstName}"/>
</f:facet>
<h:outputText value="#{item.firstName}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText
value="#{bundle.ListOwnerTitle_lastName}"/>
</f:facet>
<h:outputText value="#{item.lastName}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText
value="#{bundle.ListOwnerTitle_address}"/>
</f:facet>
<h:outputText value="#{item.address}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText
value="#{bundle.ListOwnerTitle_city}"/>
</f:facet>
<h:outputText value="#{item.city}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText
value="#{bundle.ListOwnerTitle_telephone}"/>
</f:facet>
<h:outputText value="#{item.telephone}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value=" "/>
</f:facet>
<h:commandLink
action="#{ownerController.prepareView}"
value="#{bundle.ListOwnerViewLink}"/>
<h:outputText value=" "/>
<h:commandLink
action="#{ownerController.prepareEdit}"
value="#{bundle.ListOwnerEditLink}"/>
<h:outputText value=" "/>
<h:commandLink
action="#{ownerController.destroy}"
value="#{bundle.ListOwnerDestroyLink}"/>
</h:column>
</h:dataTable>
After this simple change, our Owners list now looks like this:
We have removed the Id column from the table, but an Id field is visible when an owner is being created or edited.
Listing 4: Removing Id Field from Create Page
<h:panelGrid columns="2">
<h:outputLabel value="#{bundle.CreateOwnerLabel_id}" for="id" />
<h:inputText id="id" value="#{ownerController.selected.id}"
title="#{bundle.CreateOwnerTitle_id}" required="true"
requiredMessage="#{bundle.CreateOwnerRequiredMessage_id}"/>
<h:outputLabel value="#{bundle.CreateOwnerLabel_firstName}"
for="firstName" />
<h:inputText id="firstName" value="#{ownerController.selected.firstName}"
title="#{bundle.CreateOwnerTitle_firstName}" />
<h:outputLabel value="#{bundle.CreateOwnerLabel_lastName}" for="lastName" />
<h:inputText id="lastName" value="#{ownerController.selected.lastName}"
title="#{bundle.CreateOwnerTitle_lastName}" />
<h:outputLabel value="#{bundle.CreateOwnerLabel_address}" for="address" />
<h:inputText id="address" value="#{ownerController.selected.address}"
title="#{bundle.CreateOwnerTitle_address}" />
<h:outputLabel value="#{bundle.CreateOwnerLabel_city}" for="city" />
<h:inputText id="city" value="#{ownerController.selected.city}"
title="#{bundle.CreateOwnerTitle_city}" />
<h:outputLabel value="#{bundle.CreateOwnerLabel_telephone}"
for="telephone" />
<h:inputText id="telephone" value="#{ownerController.selected.telephone}"
title="#{bundle.CreateOwnerTitle_telephone}" />
</h:panelGrid>
After removal of the highlighted lines above, our create page now looks like this:
We can’t successfully create a new owner anymore, however, because the Id
field in the JPA Owner Entity is decorated with the @NotNull
annotation.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@NotNull
@Column(name = "id", nullable = false)
private Integer id;
Fixing this is as simple as it sounds: simply remove the @NotNull
annotation from the Id field. At this point, we can create a new pet owner without having to explicitly enter a primary key.
After the newly created owner’s entry is saved, it is listed in the Owners’ table.
You can also see it in the database by using NetBeans’ built-in database tool.
The JPA primary key generator automatically added a value to the Id
field.
At this point, we have increased the usability of the generated application by modifying automatically generated labels as well as removing unnecessary input fields hiding user information that is irrelevant to the user.
Many of the tables in our application have a one-to-many relationship. When viewing information on the “one” side of the relationship, you can see some data about the “many” side of the relationship—for example, there is a one-to-many relationship between pets and owners (a pet can have only one owner, but an owner can have many pets). Let’s take a look at the page that lists all the pets in the system.
Note that the values for the Type and Owner columns are numbers corresponding to the primary keys of the other end of the relationship. We should modify this page so that a textual description appears instead.
The markup for the page that generates the pet list shows how these values are obtained.
Listing 5. Obtaining Values for Type and Owner Columns
<h:dataTable value="#{petController.items}" var="item" border="0"
cellpadding="2" cellspacing="0"
rowClasses="jsfcrud_odd_row,jsfcrud_even_row"
rules="all" style="border:solid 1px">
<h:column>
<f:facet name="header">
<h:outputText value="#{bundle.ListPetTitle_name}"/>
</f:facet>
<h:outputText value="#{item.name}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{bundle.ListPetTitle_birthDate}"/>
</f:facet>
<h:outputText value="#{item.birthDate}">
<f:convertDateTime pattern="MM/dd/yyyy" />
</h:outputText>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{bundle.ListPetTitle_type}"/>
</f:facet>
<h:outputText value="#{item.type}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{bundle.ListPetTitle_owner}"/>
</f:facet>
<h:outputText value="#{item.owner}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value=" "/>
</f:facet>
<h:commandLink action="#{petController.prepareView}"
value="#{bundle.ListPetViewLink}"/>
<h:outputText value=" "/>
<h:commandLink action="#{petController.prepareEdit}"
value="#{bundle.ListPetEditLink}"/>
<h:outputText value=" "/>
<h:commandLink action="#{petController.destroy}"
value="#{bundle.ListPetDestroyLink}"/>
</h:column>
</h:dataTable>
The column is displaying a string representation of the Type
and Owner
properties of the Pet
class. Logically, we would think that the toString()
method of these objects would return their Id. Let’s take a look at the toString()
implementation for Owner.
@Override
public String toString() {
return "com.ensode.petclinicjavaee.entity.Owner[ id=" + id + " ]";
}
As you can see, toString()
does not return the ID, so what’s going on here? The reason the ID is displayed on the page is that the NetBeans wizard automatically generated JSF converters for all JPA entities. The converter for Owner is defined as an inner class of OwnerController.java
.
Listing 6. Definition of Converter for Owner
@FacesConverter(forClass = Owner.class)
public static class OwnerControllerConverter implements Converter {
public Object getAsObject(FacesContext facesContext,
UIComponent component, String value) {
if (value == null || value.length() == 0) {
return null;
}
OwnerController controller =
(OwnerController) facesContext.getApplication().
getELResolver().
getValue(facesContext.getELContext(), null,
"ownerController");
return controller.ejbFacade.find(getKey(value));
}
java.lang.Integer getKey(String value) {
java.lang.Integer key;
key = Integer.valueOf(value);
return key;
}
String getStringKey(java.lang.Integer value) {
StringBuffer sb = new StringBuffer();
sb.append(value);
return sb.toString();
}
public String getAsString(FacesContext facesContext,
UIComponent component, Object object) {
if (object == null) {
return null;
}
if (object instanceof Owner) {
Owner o = (Owner) object;
return getStringKey(o.getId());
} else {
throw new IllegalArgumentException("object " + object +
" is of type " + object.getClass().getName() +
"; expected type: " + OwnerController.class.getName());
}
}
}
Of special interest here is the getAsString()
method, which invokes a method called getStringKey()
, which simply converts the Id property of the Owner object to a string and then returns the string representation of the owner ID. We can replace this with code for returning the owner’s first name and last name, as follows:
Listing 7. Returning Owner’s First and Last Name
public String getAsString(FacesContext facesContext,
UIComponent component, Object object) {
if (object == null) {
return null;
}
if (object instanceof Owner) {
Owner o = (Owner) object;
return new StringBuilder(o.getFirstName()).append(
" ").append(o.getLastName()).toString();
} else {
throw new IllegalArgumentException("object " + object +
" is of type " + object.getClass().getName() +
"; expected type: " + OwnerController.class.getName());
}
}
After this change plus a similar change for all generated converters, our pet list page now looks like this:
We are now almost done tweaking the application, but there is one more simple change we need to make. When an entity that has a one-to-many relationship with another entity is being modified or created, a drop-down appears for selecting the “many” part of the relationship—for example, the Edit Pet page has drop-downs for Type and Owner.
Note that the values displayed in the drop-downs aren’t exactly user-friendly. What it is showing is the return value of the generated toString()
method of each corresponding entity. We can make the drop-downs more user-friendly by going through all the generated toString()
methods and modifying them to return an appropriate description of the object.
After simple modifications to the appropriate toString() methods, our Edit Pet page now looks like this:
At this point, we have a fully functional, user-friendly version of the Pet Clinic application. It isn’t a straight port of the Spring version, but it offers equivalent functionality. Our version enables us to manage veterinarians and veterinary specialties, whereas the Spring version does not. On the other hand, the Spring version shows a single page displaying owner, pet, and visit information, whereas in our version, these entities are managed and displayed separately. Nevertheless, the applications are more or less equivalent.
Comparing the Spring and Java EE Versions of Pet Clinic
Now that we have finished tweaking our version of the Pet Clinic application, we should go ahead and compare it with the Spring version.
File Size and Required Libraries
First let’s consider the size of the generated application. The Java EE version of the application is just under 2 megabytes in size.
In contrast, the Spring version is 17 megabytes.
The Spring version of the application is more than nine times the size of the Java EE version. The main reason is that Spring applications are usually deployed to a Servlet container such as Tomcat and need to include several libraries that implement transactions, object-relational mapping functionality, and so on. These services are provided out of the box if you are using a Java EE-compliant application server.
Let’s take a look at the WEB-INF/lib
directory for the Spring version of the application.
The Spring version of the application contains 34 libraries that need to be bundled with the application.
Now, let’s take a look at the WEB-INF directory for the Java EE version of the application.
The Java EE version of the application doesn’t even have a lib
directory under WEB-INF—everything we need is provided by the Java EE application server, so no external libraries are needed.
To be completely fair, it’s worth noting that the Spring version of the application supports three ways of accessing the database (using Hibernate, JPA, or JDBC) whereas the Java EE version of the application supports only JPA, but the point stands that Spring applications generally tend to have a much higher number of dependencies than their Java EE counterparts.
Configuration
Now that we have gone through the number of libraries required for developing each version of the application, let’s focus on the number of configuration files needed.
Looking through the Spring version of the application, we find the following XML configuration files.
File | Line Count |
---|---|
context.xml
|
7 |
web.xml
|
141 |
applicationContext-jpa.xml
|
101 |
Geronimo-web.xml
|
5 |
applicationContext-hibernate.xml
|
89 |
petclinic-servlet.xml
|
68 |
petclinic.hbm.xml
|
74 |
applicationContext-jdbc.xml
|
81 |
Total | 566 |
Now let’s take a look at the configuration files for the Java EE version of the application.
File | Line Count |
---|---|
web.xml
|
24 |
faces-config.xml
|
16 |
persistence.xml
|
8 |
Total | 48 |
The Spring version of the application has almost 12 times as many configuration lines. Again, to be fair, some of those files are needed only for choosing a specific API for data access or for deploying to a specific application server. Assuming that we are deploying to Tomcat and using Hibernate for data access, we can remove geronimo-web.xml
, applicationContext-jpa.xml,
and applicationContext-jdbc.xml
, bringing the total line count to 462 for the Spring version of the application, which is still almost 10 times as much configuration as the Java EE version of the application requires.
Code and Markup
Now that we have compared file sizes, dependencies, and the amount of configuration, let’s compare the actual amount of code in each version of the application.
The total line count of JSP, Cascading Style Sheets (CSS), and Java code for the Spring version of the application is 2,664 lines, and the total line count for XHTML, CSS, and Java code for the Java EE version of the application is 3,768.
Although the total line count of the Spring version of the application is a lot less, there are a couple of things we should keep in mind:
The Java EE version of the application is not a straight port of the Spring version. For example, the Java EE version enables us to create, update, and delete veterinarians as well as veterinary specialties, whereas the Spring version of the application enables us only to view veterinarians and specialties. Additionally, the Spring version has a single page for managing/viewing owners, pets, and visits, whereas the Java EE version of the application has separate pages for each of these entities.
The other thing we should keep in mind is that we didn’t actually write a lot of the code and markup for the Java EE version of the application, because the bulk of it was generated by the NetBeans wizard.
This concludes part 3 of this article series. The next installment will compare the Java EE and Spring APIs.
See Also
About the Author
David Heffelfinger is the Chief Technology Officer of Ensode Technology, LLC, a software consulting firm based in the greater Washington D.C. area. He has been architecting, designing, and developing software professionally since 1995 and has been using Java as his primary programming language since 1996. He has worked on many large-scale projects for several clients including the U.S. Department of Homeland Security, Freddie Mac, Fannie Mae, and the U.S. Department of Defense. He has a masters degree in software engineering from Southern Methodist University.