Introduction
This article has been extracted from the book Building Back-End Web Apps with Java, JPA and JSF, which is available as an open access online book. It shows how to build a Java back-end app with minimal effort, using Java Server Faces (JSF) as the user interface technology, the Java Persistence API (JPA) for object-to-storage mapping, and a MySQL database.
If you first want to see how it works and how it looks like, you can run the minimal app discussed in this article from our server.
A distributed web app is composed of at least two parts: a front-end part, which, at least, renders the user interface (UI) pages, and a back-end part, which, at least, takes care of persistent data storage. A back-end web app is a distributed web app where essentially all work is performed by the back-end component, including data validation and UI page creation, while the front-end only consists of a web browser's rendering of HTML-forms-based UI pages. Normally, a distributed web app can be accessed by multiple users, possibly at the same time, over HTTP connections.
In the case of a Java/JPA/JSF back-end app, the back-end part of the app can be executed by a server machine that runs a web server supporting the Java EE specifications Java Servlets, Java Expression Language (EL), JPA and JSF, such as the open source server Tomcat/TomEE.
The minimal version of a Java back-end data management app discussed in this tutorial only includes a minimum of the overall functionality required for a complete app. It takes care of only one object type (Book
) and supports the four standard data management operations (Create/Read/Update/Delete), but it needs to be enhanced by styling the user interface with CSS rules, and by adding further important parts of the app's overall functionality:
-
Part 2: Taking care of constraint validation
-
Part 3: Dealing with enumerations
-
Part 4: Managing unidirectional associations assigning authors and publishers to books
-
Part 5: Managing bidirectional associations also assigning books to authors and to publishers
-
Part 6: Handling subtype (inheritance) relationships in a class hierarchy
Background
Compared to JavaScript, what is different in Java?
-
No program without a class: Any Java program must include at least one class.
-
No object without a class: For creating an object, a class has to be used (or defined) for
-
No global variables, no global methods: In Java, all variables and methods must be defined in the context of a class, which provides their name space.
-
Classes, properties and methods are defined with a visibility level: public
, protected
or private
.
-
Java is strongly typed: Properties, parameters and variables must be declared to be of some type.
-
Type parameters: Classes and complex data structures (such as lists) can be defined with the help of type parameters. See, for instance, this tutorial.
-
Arrays are static: Arrays have a fixed size, which cannot be changed at run-time. See also the section on arrays below.
-
Java programs must be compiled before they can be executed.
-
Speed: Java is about twice as fast as optimized JavaScript.
It is important to understand that in Java, visibility level are used to define the possible levels of access:
| Class | Package | Subclass | World |
public | y | y | y | y |
protected | y | y | y | n |
no modifier | y | y | n | n |
private | y | n | n | n |
Normally, properties are defined as private, with public getters and setters, so they are only directly accessible at the level of the class defining them.
JavaBean Classes and Entity Classes
A JavaBean class (or, simply, bean class) is a Java class with a default constructor where all properties are serializable and have a get
and set
method (also called "getter" and "setter"). A Java bean is an object created with the help of a bean class.
A JPA entity class (or, simply, entity class) is a JavaBean class with an @Entity
annotation, implying that a JavaEE runtime environment (such as provided by the TomEE PLUS web server) will take care of the persistent storage of its instances.
Building a Minimal Java Web App in Seven Steps
In this chapter, we build a simple Java web application with the Java Persistence API (JPA) for object-to-storage mapping and Java Server Faces (JSF) as the user interface technology. Such an application requires a web server (as a back-end) environment executing the Java code, but also consists of HTML, CSS and possibly some auxiliary JavaScript front-end code that is executed on the user's computer. Since essentially all data processing (including constraint validation) is performed on the back-end, and the front-end only renders the user interface, we can classify Java/JPA/JSF web applications as back-end web apps.
JPA is a Java API for the management of persistent data in Java applications. It uses the Java Persistence Query Language (JPQL), a platform-independent object-oriented query language, which is heavily inspired by SQL, and its query expressions are quite similar to SQL query expressions, but they are executed in the context of JPA entity objects.
JSF is a Java specification for building component-based user interfaces for web applications. Its current version, JSF 2, by default, uses Facelets as its template technology. In contrast, JSF 1 has used JavaServer Pages (JSP) as its default template technology.
In this tutorial, we show how to develop, deploy and run a simple example app using the TomEE web server, which provides an execution environment for Java/JPA/JSF web apps. We assume that you have already installed the TomEE PLUS web server, the MySQL DBMS, and the ANT build tool on your computer.
The purpose of our example app is to manage information about books. For simplicity, in this chapter we deal with a single object type Book
, as depicted in Figure 1.1.
The following is a sample data population for the model class Book
:
Table 1.1. Sample data for Book
ISBN | Title | Year |
006251587X | Weaving the Web | 2000 |
0465026567 | Gödel, Escher, Bach | 1999 |
0465030793 | I Am A Strange Loop | 2008 |
What do we need for a data management app? There are four standard use cases, which have to be supported by the app:
-
Create a new book record by allowing the user to enter the data of a book that is to be added to the collection of stored book records.
-
Retrieve (or read) all books from the data store and show them in the form of a list.
-
Update the data of a book record.
-
Delete a book record.
These four standard use cases, and the corresponding data management operations, are often summarized with the acronym CRUD.
For entering data with the help of the keyboard and the screen of our computer, we use HTML forms, which provide the user interface technology for web applications.
For any data management app, we need a technology that allows to store data in persistent records on a secondary storage device, such as a hard-disk or a solid state disk. JPA allows using a great number of different data storage technologies, including many SQL database management systems (DBMS) such as Oracle, MySQL and PostgreSQL. We don't have to change much in the application code for switching from one storage technology to another. Adding the right driver implementation to our Java runtime environment, properly setting up the DBMS and changing the database access configuration is all we need to do. Below, in step 3, we explain how to set up the JPA configuration for MySQL.
1. Step 1 - Set up the Folder Structure
In the first step, we set up our folder structure for the application program code. The application name in this chapter will be "Public Library", and we will use a corresponding name for the application folder, "publicLibrary". Then we create the application structure. There are many ways to do this, like for example to use the Eclipse development environment (and create and configure a Dynamic Web Project). In this tutorial we show how to do it manually, so there is no need to use special tools, except ANT for being able to compile and deploy the application more easily. For your convenience, we provide an ANT script (available for download at the end of this tutorial), which is able to create the folder structure of a web application, compile it to a Web application Archive (WAR) file and then deploy it to a TomEE web server for execution. The application structure (which is compatible with the Dynamic Web Project structure of Eclipse, so it can be imported in Eclipse) is the following:
publicLibrary
src
pl
model
ctrl
META-INF
persistence.xml
WebContent
views
books
WEB-INF
templates
faces-config.xml
web.xml
This folder structure has the following parts:
-
The src
folder contains the app code folder pl
, defining the Java package name for the app (as a shortcut of 'public library'), and the folder META-INF
with configuration files:
-
the app code folder pl
contains the model and controller code in its subfolders model
and ctrl
, while the view/UI code is contained in the WebContent
folder;
-
the most important configuration file (and the only one we need for this app) is the persistence.xml
file. It contains the configuration for the database connection. The content of this file is discussed in Section 3.2.
-
The WebContent
folder contains various web resources, including template files and custom view files:
-
the views
stores our custom view files for the application, so it represents the View part of the MVC paradigm. Please note that it is not strictly required to name it views, but it makes a lot of sense to do it so, since this is what this folder represents. We will discuss in details about its content, later in this tutorial.
-
the WEB-INF
folder contains the used libraries (jar
files) of the project (as part of the lib
subfolder), the JSF template files for your pages (as part of the templates
subfolder), the faces-config.xml
file, which stores the facelets configuration data and the web.xml
configuration file, specific to the Tomcat (TomEE) environment server used to run our application.
2. Step 2 - Write the Model Code
In the second step, we create the model classes for our app, using a separate Java source code file (with extension .java
) for each model class. In the information design model shown in Figure 1.1 above, there is only one class, representing the object type Book
. So, we create a file Book.java
in the folder src/pl/model
with the following code:
@Entity @Table( name="books")
public class Book {
@Id private String isbn;
private String title;
private int year;
public Book() {}
public Book( String isbn, String title, int year) {
this.setIsbn( isbn);
this.setTitle( title);
this.setYear( year);
}
public String getIsbn() {return isbn;}
public void setIsbn( String isbn) {this.isbn = isbn;}
public String getTitle() {return title;}
public void setTitle( String title) {this.title = title;}
public int getYear() {return year;}
public void setYear( int year) {this.year = year;}
public static void add(...) {...}
public static List<Book> retrieveAll(...) {...}
public static Book retrieve(...) {...}
public static void update(...) {...}
public static void destroy(...) {...}
public static void clearData(...) {...}
public static void createTestData(...) {...}
}
Notice that the model class Book
is encoded as a JPA entity class, which is a JavaBean class enriched with the following JPA annotations:
-
The annotation @Entity
designates a class as an entity class implying that the instances of this class will be stored persistently.
-
The annotation @Table( name="books")
specifies the name of the database table to be used for storing the Book
entities. This annotation is optional and defaults to a table name being the same as the class name but in lower case (that is, it would be book
in our case).
-
The @Id
annotation marks the standard identifier attribute, implying that the corresponding column of the underlying SQL database table is designated as the PRIMARY KEY. In our example, isbn
is used as the standard identifier attribute, and the corresponding isbn
column of the books
table stores the primary key values.
In the entity class Book
, we also define the following static (class-level) methods:
-
Book.add
for creating a new Book
instance.
-
Book.retrieveAll
for retrieving all Book
instances from the persistent data store.
-
Book.retrieve
for retrieving a specific Book
instance from the persistent data store by means of its standard identifier.
-
Book.update
for updating an existing Book
instance.
-
Book.destroy
for deleting a Book
instance.
-
Book.createTestData
for creating a few example book records to be used as test data.
-
Book.clearData
for clearing the book database table.
These methods are discussed in the following sections.
The JPA architecture for data management and object-to-storage mapping is based on the concept of an entity manager, which provides the data management methods persist
for saving a newly created entity, find
for retrieving an entity, and remove
for deleting an entity.
Since the database access operations of an entity manager are executed in the context of a transaction, our data management methods have a parameter ut
of type UserTransaction
. Before the entity manager can invoke the database write method persist
, a transaction needs to be started with ut.begin()
. After all write (and state change) operations have been performed, the transaction is completed (and all changes are committed) with ut.commit()
.
2.1. Storing Book
objects in a database table books
The instances of our entity class Book
are special Java objects representing "entities" (or business objects), which can be serialized, or, in other words, turned into database records, or rows of a database table. Consequently, they can be shown as a table like in Table 1.2.
Table 1.2. Book objects represented as a table
ISBN | Title | Year |
006251587X | Weaving the Web | 2000 |
0465026567 | Gödel, Escher, Bach | 1999 |
0465030793 | I Am A Strange Loop | 2008 |
The data storage technology used in our example app is MySQL, and the (My)SQL code used to create the schema for the database table books
is the following:
CREATE TABLE IF NOT EXISTS books (
isbn VARCHAR(10) NOT NULL PRIMARY KEY,
title VARCHAR(128),
year SMALLINT
);
While it is also possible to create the database schema manually (with the help of CREATE TABLE statements such as the one above), we'll show later in this tutorial how the database schema can be automatically generated by JPA. In both cases, the database setup, including a user account and the associated rights (create, update, etc), must be done manually before the JPA application can connect to it.
2.2. Creating a new Book
instance and storing it
The Book.add
method takes care of creating a new Book
instance and saving it to a database with the help of an 'entity manager':
public static void add( EntityManager em, UserTransaction ut,
String isbn, String title, int year) throws Exception {
ut.begin();
Book book = new Book( isbn, title, year);
em.persist( book);
ut.commit();
}
For storing the new object, the persist
method of the given 'entity manager' is invoked. It is responsible for creating the corresponding SQL INSERT statement and executing it.
2.3. Retrieving all Book
instances
The instances of an entity class, such as Book
, are retrieved from the database with the help of a corresponding query expressed in the Java Persistence Query Language (JPQL). These queries are similar to SQL queries. They use class names Instead of table names, property names instead of column names, and object variables instead of row variables.
In the Book.retrieveAll
method, first a query
asking for all Book
instances is created, and then this query is executed with query.getResultList()
assigning its answer set to the list variable books
:
public static List<Book> retrieveAll( EntityManager em) {
Query query = em.createQuery( "SELECT b FROM Book b", Book.class);
List<Book> books = query.getResultList();
return books;
}
2.4. Updating a Book
instance
For updating an existing Book
instance we first retrieve it from the database with em.find
, and then set those attributes the value of which has changed:
public static void update( EntityManager em,
UserTransaction ut, String isbn, String title,
int year) throws Exception {
ut.begin();
Book book = em.find( Book.class, isbn);
if (!title.equals( book.getTitle())) book.setTitle( title);
if (year != book.getYear()) book.setYear( year);
ut.commit();
}
Notice that, when invoking the find
method for retrieving an entity, the first argument must be a reference to the entity class concerned (here: Book.class
), so the JPA runtime environment can identify the database table from which to retrieve the entity's data. The second argument must be the value of the entity's primary key.
Notice that in the update case, we do not have to use persist
for saving the changes. Saving is automatically managed by the JPA runtime environment when we complete the transaction with ut.commit()
.
2.5. Deleting a Book
instance
A book entity can be deleted from the database as shown in the following example code:
public static void destroy( EntityManager em,
UserTransaction ut, String isbn) throws Exception {
ut.begin();
Book book = em.find( Book.class, isbn);
em.remove( book);
ut.commit();
}
To delete an entity from the database, we first need to retrieve it with the help of the find
method as in the update case. Then, the remove
method has to be invoked by the 'entity manager', and finally the transaction is completed with ut.commit()
.
For being able to test our code, we may create some test data and save it in our database. We can use the following procedure for this:
public static void createTestData( EntityManager em,
UserTransaction ut) throws Exception {
Book book = null;
Book.clearData( em, ut);
ut.begin();
book = new Book("006251587X","Weaving the Web", 2000);
em.persist( book);
book = new Book("0465026567","Gödel, Escher, Bach", 1999);
em.persist( book);
book = new Book("0465030793","I Am A Strange Loop", 2008);
em.persist( book);
ut.commit();
}
After clearing the database, we successively create 3 instances of the Book
entity class and save them with the help of persist
.
The following procedure clears our database by deleting all rows:
public static void clearData( EntityManager em,
UserTransaction ut) throws Exception {
ut.begin();
Query deleteStatement = em.createQuery( "DELETE FROM Book");
deleteStatement.executeUpdate();
ut.commit();
}
JPA does not provide a direct method to drop the entire population of a specific class from the database. However, this can be easily obtained by using a JPQL statement as shown in the above code. The JPQL code can be read as: delete all rows from the database table associated with the entity class Book
.
3. Step 3 - Configure the App
In this section we show how to
-
configure an app to connect with a database in a controller class,
-
obtain the EntityManager
and UserTransaction
instances required for performing database operations,
-
wrap an app in a WAR file and deploy it to a web server for execution.
3.1. Create the EntityManager and UserTransaction objects
A controller class contains the code that glues the views to the model, as well as all methods that do no neither belong to the model nor to the view, like getting a connection with the database server. In our example app, this class is pl.ctrl.BookController
in the src/pl/ctrl
folder.
JPA requires an EntityManager
object for executing JPQL queries (with SELECT
) and data manipulation statements (with INSERT
, UPDATE
and DELETE
). Also, in order to perform database write operations, a UserTransaction
object is required for starting and completing transactions. In a standalone application, the programmer has to create an 'entity manager' and a transaction manually, using a factory pattern as shown in the following code fragment:
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("MinimalApp");
EntityManager em = emf.createEntityManager();
EntityTransaction et = em.getTransaction();
A JPA-enabled Java web application normally runs in an environment called "container" (in our case this is TomEE), which takes care of creating an EntityManager
and a UserTransaction
object if the right annotations are used. The code responsible for this is part of the controller class ( e.g., pl.ctrl.BookController
) since the controller is responsible for managing the database connections.
public class BookController {
@PersistenceContext( unitName="MinimalApp")
private EntityManager em;
@Resource() UserTransaction ut;
public List<Book> getBooks() {...}
public void refreshObject( Book book) {...}
public String add( String isbn, String title,
int year) {...}
public String update( String isbn,
String title, int year) {...}
public String destroy( String isbn) {...}
}
A closer look at this code shows that it is sufficient to use the @PersistenceContext
annotation and provide a unitName
(see the next section) for obtaining an EntityManager
instance at runtime. Also, obtaining a UserTransaction
instance at runtime is as simple as using the @Resource
annotation for the user transaction reference property ut
. Not only that the required code is short and simple, but if the database type is changed (e.g. when we switch from MySQL to an Oracle database), this code remains the same.
3.2. Configure the JPA database connection
In the previous section, discussing the BookController
class, we have shown how to obtain the EntityManager
and UserTransaction
objects required for performing database operations. The @PersistenceContext
annotation of the EntityManager
reference property requires a unitName
, which is just a name used for identifying the storage management configuration defined in the src/META-INF/persistence.xml
file. In our example app this file has the following content:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<persistence-unit name="MinimalApp">
<class>pl.model.Book</class>
<properties>
<!-- Request auto-generation of the database schema -->
<property name="javax.persistence.schema-generation.database.action"
value="create"/>
<!-- Use the JPA annotations for creating the database schema -->
<property name="javax.persistence.schema-generation.create-source"
value="metadata"/>
</properties>
<jta-data-source>jdbc/MinimalApp</jta-data-source>
</persistence-unit>
</persistence>
The configuration name ("MinimalApp") is defined by the name
attribute of the persistence-unit
element. This is the value we have to use for the unitName
property of the @PersistenceContext
annotation.
The persistence-unit
element has three content parts:
-
One or more class
elements, each one containing the full qualified name of an entity class of the app (like pl.model.Book
in our example app).
-
A set of configuration property
elements used for providing further configuration settings.
-
A jta-data-source
element for specifying the configuration block in the config/TomEE.xml
web server configuration file in the web server installation folder.
In our persistence.xml
file, two configuration properties have been set:
-
javax.persistence.schema-generation.database.action
, with the possible values: none
(default), create
, drop-and-create
and drop
. It specifies if the database schema is to be automatically created and additionally allows to drop the existing tables before creating the new ones (with drop
or drop-and-create
). .
-
javax.persistence.schema-generation.create-source
, with the possible values metadata
(default), script
, metadata-then-script
and script-then-metadata
. It specifies the source of information used to create the database schema. The value metadata
enforces using the JPA annotations while the value script
allows using an external DDL script for defining the schema.
The jta-data-source
element of our persistence.xml
file refers to the Resource
element with id
value "MinimalApp" in the config/TomEE.xml
file, which has the following content:
<?xml version="1.0" encoding="UTF-8"?>
<TomEE>
<Resource id="MinimalApp" type="DataSource">
JdbcDriver com.mysql.jdbc.Driver
JdbcUrl jdbc:mysql:
UserName minimalapp
Password minimalapp
JtaManaged true
</Resource>
</TomEE>
The Resource
element contains the information required to connect with the database (i.e. username, password, access URL and the Java class name of the connection driver).
3.3. Create the main template
The main template, called page.xhtml
, is shown below. It has two sub-templates:
-
header.xhtml
defines the general header information items (such as the application name)
-
footer.xhtml
defines the general footer information items (such as a copyrights notice)
Both sub-templates are included in the main template with the help of a ui:include
element. We add all three template files to the WebContent/WEB-INF/templates
folder.
The content of our HTML5-compliant main template page.xhtml
is the following:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title><ui:insert name="title">Public Library</ui:insert></title>
<link href="#{facesContext.externalContext.requestContextPath}/resources/css/style.css"
rel="stylesheet" type="text/css" />
</h:head>
<body>
<div id="header">
<ui:insert name="header">
<ui:include src="/WEB-INF/templates/header.xhtml"/>
</ui:insert>
</div>
<div id="main">
<ui:insert name="main"/>
</div>
<div id="footer">
<ui:insert name="footer">
<ui:include src="/WEB-INF/templates/footer.xhtml"/>
</ui:insert>
</div>
</body>
</html>
In the code, one can see that some HTML elements are used (e.g., title
, link
or div
) while others like h:head
and ui:insert
are not HTML elements, but have been defined by JSF in different namespaces. JSF is using its own head element h:head
because it allows injecting special HTML code such as script
elements needed for XHR (or "AJAX") messaging.
Notice that in the main template, we have a first example of an expression using JSF's Expression Language (EL). where an expression starts with # and is encapsulated between curly brackets, like #{expression}
. Such an expression allows reading the value of a property of, or invoking a method on, a Java bean or a context object. In any case, the value of the expression will be inserted into the HTML generated from the template. The example in our main template is the expression #{facesContext.externalContext.requestContextPath}
, which retrieves the value of the requestContextPath
property of the context object facesContext.externalContext
.
Our main template defines three content regions: header, main and footer. The header and footer regions are defined by sub-templates included with the help of the ui:include
element.
The header.xhtml
sub-template contains the following:
<div><h2>Public Library</h2></div>
The footer.xhtml
sub-template contains the following::
<div>Copyright 2014-2015, Gerd Wagner and Mircea Diaconescu</div>
The main region is dynamic, and will be replaced with the content generated by a facelet as shown below. Notice that the file extension of template files and of facelet files is xhtml
.
JSF is using the following namespaces:
-
xmlns:ui="http://java.sun.com/jsf/facelets"
for the JSF Facelets Tag Library providing templating elements (like ui:define
for specifying the region of a template where to inject the facelet content).
-
xmlns:h="http://java.sun.com/jsf/html"
for the JSF HTML Tag Library providing JSF versions of HTML elements, which are then mapped to HTML elements. For example h:inputText
, which is mapped to an HTML input
element.
-
xmlns:f="http://java.sun.com/jsf/core"
for the JSF Core Tag Library providing custom actions or elements that are independent of any particular render kit. For example, f:actionListener
can be used to define a Java method which is executed when the user clicks a button.
-
xmlns:p="http://xmlns.jcp.org/jsf/passthrough"
for using HTML attributes in JSF HTML elements and passing them through to the generated HTML. For example, with p:type
in, <h:inputText p:type="number">
an HTML5 input
type attribute can be created in the generated HTML: <input type="number">
.
-
xmlns:c="http://java.sun.com/jsp/jstl/core"
for the JSTL Core Tag Library providing all kinds of features, like dealing with loops and defining variables. For example, we can use <c:set var="isbn" value="#{book.isbn}"/>
to create a variable named isbn
which can then be used in the view code for conditional expressions.
-
xmlns:fn="http://java.sun.com/jsp/jstl/functions"
for the JSTL Functions Tag Library providing various utility functions, such as string converters. For example, we can use fn:toUpperCase
to convert a string to its uppercase representation.
3.4. Define the managed beans needed in facelets
JavaBean classes, including entity classes, can be used for creating 'managed beans' with the help of the @ManagedBean
annotation, which allows defining the name of a variable for accessing the created bean in the view code, typically in an EL expression. In our example app, we want to access a Book
bean as well as a BookController
bean, therefore both classes have to be annotated as follows:
@Entity @Table( name="books")
@RequestScoped @ManagedBean( name="book")
public class Book { ... }
@SessionScoped @ManagedBean( name="bookCtrl")
public class BookController { ... }
Notice how a lifetime scope can be specified for a managed bean with a scope annotation. In our example the book
bean is @RequestScoped
, this means the instance exists as long as the HTTP request and the associated response are being processed. The bookCtrl
bean is @SessionScoped
, which means it is created when the session starts, and destroyed when the session is closed. Other scopes are available, but we only need these two scopes in this tutorial.
3.5. Build the WAR file and deploy it to TomEE
In this tutorial we show how to use an ANT script for generating the structure of a Java web app, and then compile the code, build the WAR file and deploy it to a TomEE web server. One may also use Eclipse (or NetBeans or other IDEs) for doing this, but for keeping it simple we use ANT. Our ANT script generates a folder structure, which is compatible with Eclipse, so in case you want to use Eclipse, you may simply create an Eclipse project from the existing application code.
The purpose of this section is only to show you how to use our ANT script for making your life easier. It is not intended to be an ANT tutorial, so we don't get into specific ANT details. The following ANT tasks are defined in the script:
create app -Dappname=yourAppName -Dpkgname=yourAppPackageName
-
allows creating the folder structure. Instead of yourAppName
and yourAppPackageName
you have to provide your app's name and its package name. In our example app, we invoke the task with ant create app -Dappname=publicLibrary -Dpkgname=pl
.
The script creates the folder structure, as well as the required files src/META-INF/persistence.xml
, WEB-INF/faces-config.xml
and WEB-INF/web.xml
. The parameter yourAppPackageName
is used to create the Java top level packages. If omitted, yourAppName
is used as Java top package name instead. For the next tasks/commands you have to be sure that the ANT script file is located in the same folder as your web application folder (and not one level deeper in the web application folder). This way, one can use the same ANT script for building multiple web applications.
Using the optional parameter, -Dforce=true
will overwrite an existing application by first deleting the existing application folder.
Hint: a JPA/JSF application requires a set of libraries to run. The ANT script looks for the jar
files in a folder named lib
located on the same folder as the script itself. The location of the jar files can be modified by editing the ANT script and setting the lib.folder
parameter to the right folder on your computer. You can download the dependency JAR files with the link provided at the end of this tutorial.
build war -Dappname=yourAppName
-
allows building the WAR file by using yourAppName
as file name. The resulting WAR file will be in the same folder as the ANT script file. For our example app we use the following command:
ant war -Dappname=publicLibrary
Hint: before being able to use this command, you have to edit the ANT script and modify the value of the server.folder
parameter so it points to your TomEE installation folder. In case that you get compilation errors, try to copy the mysql-connector-java-xxxx-bin.jar
file to the lib
folder of your TomEE installation folder. This file and some other dependency files are provided in a ZIP archive that can be downloaded with the link provided at the end of this tutorial.
deploy -Dappname=yourAppName
-
allows deploying the WAR file associated with yourAppName
to the TomEE web server. It automatically executes the build war -Dappname=yourAppName
command, which means the WAR file is built before the deploy. The location of the deploy folder is detected by using the server.folder
property, by appending the webapps
folder name. For our example app we invoke the following command: ant deploy -Dappname=publicLibrary
.
Hint: we do not recommend using spaces in folder names, but if for any reason, the application name needs to contain spaces, then it has to be enclosed in double quotes, e.g. create app -Dappname="Hellow World"
.
4. Step 4 - Implement the Create Use Case
The Create use case involves creating a new object in main memory and then saving it to persistent storage with the help of the add
method.
The corresponding add
action method code from the src/pl/ctrl/BookController.java
is shown below:
public class BookController {
...
public String add( String isbn, String title, int year) {
try {
Book.add( em, ut, isbn, title, year);
FacesContext fContext = FacesContext.getCurrentInstance();
fContext.getExternalContext().getRequestMap().remove("book");
} catch ( Exception e) {
e.printStackTrace();
}
return "create";
}
}
The BookController::add
action method invokes the Book.add
model class method for creating and saving a Book
instance. It returns the name of the view file found in the same folder as the view that triggered the action. This file (create.xhtml
in our case) will be displayed after executing the action. In lines 5 and 6 above, using a FacesContext
object, the form is cleared after creating a Book
instance. The code of the add
method in src/pl/model/Book.java
is the following:
public class Book {
...
public static void add( EntityManager em, UserTransaction ut,
String isbn, String title, int year) throws Exception {
ut.begin();
Book book = new Book( isbn, title, year);
em.persist( book);
ut.commit();
}
}
Now we need to create the facelet view file for the Create use case, WebContent/views/books/create.xhtml
. Such a facelet file essentially defines a HTML form with data binding and action binding.
Data binding refers to the binding of model class properties to form (input or output) fields. For instance, in the following facelet code fragment, the entity property book.isbn
is bound to the form input field "isbn":
<h:outputLabel for="isbn" value="ISBN: " />
<h:inputText id="isbn" value="#{book.isbn}" />
Action binding refers to the binding of method invocation expressions to actionable UI elements, where the invoked methods typically are controller action methods, and the actionable UI element typically are form buttons. For instance, in the following facelet code fragment, the method invocation expression bookCtrl.add(...)
is bound to the form's submit button:
<h:commandButton value="Create"
action="#{bookCtrl.add( book.isbn, book.title, book.year)}"/>
After discussing data binding and action binding, it's time to look at the complete code of the facelet:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="..."
xmlns:h="..." xmlns:p="...">
<ui:composition template="/WEB-INF/templates/page.xhtml">
<ui:define name="main">
<h:form id="createBookForm">
<h:panelGrid columns="2">
<h:outputLabel for="isbn" value="ISBN: " />
<h:inputText id="isbn" value="#{book.isbn}" />
<h:outputLabel for="title" value="Title: " />
<h:inputText id="title" value="#{book.title}" />
<h:outputLabel for="year" value="Year: " />
<h:inputText id="year" p:type="number" value="#{book.year}" />
</h:panelGrid>
<h:commandButton value="Create"
action="#{bookCtrl.add( book.isbn, book.title, book.year)}"/>
</h:form>
<h:button value="Main menu" outcome="index" />
</ui:define>
</ui:composition>
</html>
This facelet replaces the main
region of the template defined in page.xhtml
, because the name
attribute of the ui:define
element has been set to "main".
h:outputLabel
elements can be used for creating form field labels, while h:inputText
elements are used for creating HTML input elements. It is possible to specify a HTML5 type of an input
element by using a special namespace prefix (xmlns:p="http://xmlns.jcp.org/jsf/passthrough"
) for the type
attribute, enforcing it to be 'passed through'. In this way the year
input field can be defined with type number
, so it's rendered by the corresponding number widget in the browser.
The h:commandButton
element allows creating submit buttons rendered as a input
elements with type="submit"
, and binding them to an action to be performed when the button is clicked. The value of the action
attribute is a method invocation expression. In our Create use case we want that, when the button is clicked, a Book
instance with the property values provided by corresponding form fields is created and saved.
5. Step 5 - Implement the Retrieve/List All Use Case
This use case corresponds to the "Retrieve/Read" from the four basic data management use cases Create-Retrieve-Update-Delete (CRUD).
First of all, for the list objects use case we have to add a method in the controller class (src/pl/ctrl/BookController.java
file), which reads all the Book
records from the books
database table and then delivers this information to the view. The controller action method code is shown below:
public class BookController {
...
public List<Book> getBooks() {
return Book.retrieveAll( em);
}
...
}
The getBooks
method returns a list of Book
instances which are obtained by calling the static retrieveAll
method of the Book
model class. The code of the Book.retrieveAll
method is:
public class Book {
...
public static List<Book> retrieveAll( EntityManager em) {
Query query = em.createQuery( "SELECT b FROM Book b");
List<Book> books = query.getResultList();
return books;
}
...
}
The code is simple, and as already discussed in Section 2.3, it uses a JPQL statement to retrieve the Book
records from the books
table and create the corresponding Book
instances. The EntityManager
object required for being able to perform the JPQL query is passed to Book.retrieveAll
from the BookController
object as discussed in Section 3.1 section.
Now, it is the time to define the facelet view which is used to display a table with all the records found in the database. The view files corresponding to our application are located under WebContent/views/books
folder. For the Retrieve/list all use case, we create a file named listAll.xhtml
with the following content:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="..."
xmlns:h="..." xmlns:f="...">
<ui:composition template="/WEB-INF/templates/page.xhtml">
<ui:define name="main">
<h:dataTable value="#{bookCtrl.books}" var="b">
<h:column>
<f:facet name="header">ISBN: </f:facet>
#{b.isbn}
</h:column>
<h:column>
<f:facet name="header">Title: </f:facet>
#{b.title}
</h:column>
<h:column>
<f:facet name="header">Year: </f:facet>
#{b.year}
</h:column>
</h:dataTable>
<h:button value="Main menu" outcome="index" />
</ui:define>
</ui:composition>
</html>
The ui:composition
element specifies which template is applied (i.e. template="/WEB-INF/templates/page.xhtml"
) and which view block (<ui:define name="main">
) is replaced by this facelet at render time.
The h:dataTable
element defines a table view for a set of records, which is then rendered as an HTML table. Its value
attribute defines a data binding to a record collection, while the var
attribute defines a variable name for iteratively accessing records from this collection. The expression provided in the value
attribute normally specifies a collection-valued property (here: books
) which is accessed via a corresponding getter (here: getBooks
) as defined in the controller class BookController
. In this particular case it is just sufficient to define the getBooks
method since there is no need of a books
property in the controller class. In any case, value
does not allow to invoke a method, so we cannot call getBooks
directly. Instead we have to use the (possibly virtual) property books
, which internally evaluates to a call of getBooks
without checking if a books
property really exists.
The h:button
JSF element allows to create redirect buttons. The value of the outcome
attribute specifies a name of a JSF view file by omitting the .xhtml
extension (i.e. the view file name is index.xhtml
).
6. Step 6 - Implement the Update Use Case
This use case corresponds to the "Update" from the four basic data management use cases Create-Read-Update-Delete (CRUD).
As for the create object use case, a controller action method is defined in the BookController
class (src/pl/ctrl/BookController.java
file) with the following code:
public String update( String isbn, String title, int year) {
try {
Book.update( em, ut, isbn, title, year);
} catch ( Exception e) {
e.printStackTrace();
}
return "update";
}
The Book.update
takes care of updating and making persistent changes for the book entry identified by the provided isbn
value as shown below:
public static void update( EntityManager em, UserTransaction ut,
String isbn, String title, int year) throws Exception {
ut.begin();
Book book = em.find( Book.class, isbn);
book.setTitle( title);
book.setYear( year);
ut.commit();
}
Now, we create the view where a Book
can be selected so the user can edit the title
and year
properties, and then save the changes. The code for this view is stored in the WebContent/views/books/update.xhtml
file which has the following content:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="..."
xmlns:h="..." xmlns:f="..." xmlns:p="...">
<ui:composition template="/WEB-INF/templates/page.xhtml">
<ui:define name="main">
<h:form id="updateBookForm">
<h:panelGrid columns="2">
<h:outputLabel for="selectBook" value="Select book: " />
<h:selectOneMenu id="selectBook" value="#{book.isbn}">
<f:selectItem itemValue="" itemLabel="---" />
<f:selectItems value="#{bookCtrl.books}" var="b"
itemValue="#{b.isbn}" itemLabel="#{b.title}" />
<f:ajax listener="#{bookCtrl.refreshObject( book)}"
render="isbn title year"/>
</h:selectOneMenu>
<h:outputLabel for="isbn" value="ISBN: " />
<h:outputText id="isbn" value="#{book.isbn}" />
<h:outputLabel for="title" value="Title: " />
<h:inputText id="title" value="#{book.title}" />
<h:outputLabel for="year" value="Year: " />
<h:inputText id="year" p:type="number" value="#{book.year}" />
</h:panelGrid>
<h:commandButton value="Update"
action="#{bookCtrl.update( book.isbn, book.title, book.year)}"/>
</h:form>
<h:button value="Main menu" outcome="index" />
</ui:define>
</ui:composition>
</html>
In this view, a single selection list (select
) element is created by using h:selectOneMenu
. The @value
attribute allows to save the value of selection by assigning it to a property of an object. In our case, since the selected Book
is identified by its isbn
property value which is stored in the book.isbn
property. Populating the list with book records is performed by using the f:selectItems
element, which requires a list of records (using the @value
attribute), as well as defining a variable name (using @var
attribute) which is bound to the element of the list which is has the cursor in the loop used internally to populate the select list. The attributes @itemValue
and @itemLabel
are used to provide the information used to create the option
HTML5 elements when the view is rendered, by specifying the content of the option
element (i.e. the value of the @itemLabel
attribute) and the value of the option/@value
attribute (i.e. the value of @itemValue
attribute).
In the update view, the user selects a book from the drop-down list, while the rest of the form has to be auto-completed with the details (isbn, title and year) of the selected book. The ISBN we already know, being the selection value of the drop-down list. However, we need to show also the title and year of the selected book. To do this, we use f:ajax
JSFelement which allows to perform a request (an AJAX request) which results in calling a specified method. The method will get as parameter the managed book
instance, and updates its title
and year
properties with the right values by reading them from the database. The used method is part of the BookController
class and is named refreshObject
. The code of this method is shown below:
public void refreshObject( Book book) {
Book foundBook = Book.retrieve( em, book.getIsbn());
book.setTitle( foundBook.getTitle());
book.setYear( foundBook.getYear());
}
It simply uses the Book.retrieve
method which returns the Book
instance stored in the database for the given isbn
value. Then, the reference parameter of the method (i.e. book
) is then updated so the title
and year
have the correct values. In the view code, we parse the reference of the book
as value of the refreshObject
method. The book
properties are connected with the h:inputText
and h:outputText
(for isbn
which must be read only) JSF elements, which means the form input elements will be display the values of the book
properties. To enforce the refresh of the form (this is required, because the form updates automatically only on submit actions), so it displays the updated isbn
, title
and year
values, the f:ajax
JSF element allows to specify the form (to which the f:ajax
is child of) elements to be updated, i.e. f:ajax/@render="isbn title year"
in our case.
Last, the h:commandButton
specifies that the action
is to invole the update
action method of the BookController
with the parameters isbn
, title
and year
, so the the changes are made persistent.
7. Step 7 - Implement the Delete Use Case
This use case corresponds to the "Delete" from the four basic data management use cases Create-Read-Update-Delete (CRUD).
The corresponding destroy
action method code from the src/pl/ctrl/BookController.java
is presented below:
public String destroy( String isbn) {
try {
Book.destroy( em, ut, isbn);
} catch ( Exception e) {
e.printStackTrace();
}
return "delete";
}
The main functionality of the controller action method is to invoke the Book.destroy
method and provide the isbn of the Book
entry to be deleted. The Book.destroy
method takes care of finding the right entry (based on the given value of the standard identifier, i.e. isbn
) as well as deleting it from the database.
public static void destroy( EntityManager em, UserTransaction ut, String isbn)
throws Exception, HeuristicRollbackException, RollbackException {
ut.begin();
Book book = em.find( Book.class, isbn);
em.remove( book);
ut.commit();
}
Finding the Book
entry with the given isbn
value is easy by using the builtin find
method of the EntityManager
object (see Section 2.4). Removing the Book
entry from the database is performed by simply invoking the remove
method of the EntityManager
object (more details: Section 2.5 ).
The last thing we have to do is to create the associated view for the delete action. The view contains a drop-down list which allows to select the Book
to be deleted, then a "Delete" button performs the deletion of the book. The code of the view (WebContent/views/books/delete.xhtml
) file si shown below
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="..."
xmlns:h="..."
xmlns:f="...">
<ui:composition template="/WEB-INF/templates/page.xhtml">
<ui:define name="main">
<h:form id="deleteBookForm">
<h:panelGrid columns="2">
<h:outputText value="Select book: " />
<h:selectOneMenu value="#{book.isbn}">
<f:selectItems value="#{bookCtrl.books}" var="b"
itemValue="#{b.isbn}" itemLabel="#{b.title}" />
</h:selectOneMenu>
</h:panelGrid>
<h:commandButton value="Delete"
action="#{bookCtrl.destroy( book.isbn)}"/>
</h:form>
<h:button value="Main menu" outcome="index" />
</ui:define>
</ui:composition>
</html>
As in the update object use case, a h:selectOneMenu
JSF element is used to create and populate a drop-down list containing all the books, so we can choose which one we like to delete. Clicking on "Delete" h:commandButton
results in performing the associated action which invokes the destroy
action method of the controller with the isbn
value of the selected book, thus resulting in the deletion of the Book
entry from the database.
8. Run the App and Get the Code
You can run the minimal app on our server or download the code as a ZIP archive file.
For running the minimal app on your computer, first download the code and edit the ANT script file by modifying the server.folder
property value as described in Section 3.5. You may then have to stop your Tomcat/TomEE server with bin/shutdown.bat
for Windows or bin/shutdown.sh
for Linux. Now execute the following command in your console or terminal: ant deploy -Dappname=minimalapp
. Last, start your Tomcat web server (by using bin/startup.bat
for Windows OS or bin/startup.sh
for Linux). Please be patient, this can take some time depending on the speed of your computer. It will be ready when the console displays the following info: INFO: Initializing Mojarra [some library versions and paths are shonw here] for context '/minimalapp'
. Finally, open your favorite browser and type: http://localhost:8080/minimalapp/faces/views/books/index.xhtml
For new projects, you may want to download the dependency libaries.
9. Possible Variations and Extensions
9.1. Accessibility for Web Apps
The recommended approach to providing accessibility for web apps is defined by the Accessible Rich Internet Applications (ARIA) standard. As summarized by Bryan Garaventa in his article on different forms of accessibility, there are 3 main aspects of accessibility for interactive web technologies: 1) keyboard accessibility, 2) screen reader accessibility, and 3) cognitive accessibility.
Further reading on ARIA:
Whenever an app provides public information about entities, such as the books available in a public library, it is desirable to publish this information with the help of self-descriptive resource URLs, such as http://publiclibrary.norfolk.city/books/006251587X
, which would be the resource URL for retrieving information about the book "Weaving the Web" available in the public library of Norfolk.
9.3. Offline availability
It is desirable that a web app can still be used when the user is offline.
The code of this app should be extended by
We show how to do this in the follow-up tutorial Building Java Web Apps Tutorial Part 2: Adding Constraint Validation.
We briefly discuss three further points of attention: boilerplate code, code clarity, using resource URLs and architectural separation of concerns.
Any damn fool can write code that a computer can understand, the trick is to write code that humans can understand. (Martin Fowler in After the Program Runs)
Code is often "unnecessarily complicated, convoluted, disorganized, and stitched together with disdain", as observed in a post by Santiago L. Valdarrama who recommends using comments when necessary, only to explain things that can't be made clear enough, but rather make your code reveal its intention through the use of better names, proper structure, and correct spacing and indentation.
Another issue with the code of this Java example app is the repetitious boilerplate code needed
-
per model class for the storage management methods add
, update
, destroy
, etc.;
-
per model class and property for getters, setters and validation checks.
While it is good to write this code a few times for learning app development, you don't want to write it again and again later when you work on real projects.
10.3. Architectural separation of concerns
One of the most fundamental principles of software architecture is separation of concerns. This principle is also the basis of the Model-View-Controller (MVC) architecture paradigm. It requires to keep the different functional parts of a software application independent of each other as much as possible. More specifically, it implies to keep the app's model classes independent of
-
the user interface (UI) code because it should be possible to re-use the same model classes with different UI technologies;
-
the storage management code because it should be possible to re-use the same model classes with different storage technologies.
In this tutorial, we have kept the model class Book
independent of the UI code, since it does not contain any references to UI elements, nor does it invoke any view method. However, for simplicity, we didn't keep it independent of storage management code, since we have used JPA annotations, which bind the class to JPA object-to-storage mapping technology, and we have included the method definitions for add, update, destroy, etc., which invoke the storage management methods of a JPA environment's entity manager. Therefore, the separation of concerns is incomplete in our minimal example app.
We will show in a follow-up tutorial how to achieve a more complete separation of concerns.
History
- 19-Nov-2015. First version created.