Databases are needed if you want to persist data for a longer period of time, or if data must consistently be accessible from different sessions (different users). This chapter refers to SQL (Structured Query Language) databases. For an introduction, see for example the article at https://en.wikipedia.org/wiki/Database.
JPA (Java Persistence API) is the dedicated technology used to access relational databases from inside Jakarta EE. Its aim is to provide a bridge between SQL tables and Java objects. This task is much more complex than in other basic data schemes. The reason for this is that, in relational database schemes, we have associations between different tables: One row in one table may refer to one or many rows in another table or the other way round, and there could be references spanning three or more tables. And think of column-type conversions—a database may have different ideas about numbers, boolean indicators, and dates and times compared to Java. In addition, null values in database tables require increased attention if they’re used in table references and while converting to Java values.
In this chapter, we talk about basic issues when using JPA inside Java MVC. For a complete and deep overview of JPA, covering more complex issues than we do in this chapter, consult the online JPA documentation and specification on the web. A good starting URL is https://docs.oracle.com/javaee/6/tutorial/doc/bnbpy.html.
Abstracting Away Database Access with JPA
One of the primary purposes of JPA is to abstract away database access and map database objects to Java classes. In the end, we want to be able to query the database and get Java objects, or to put Java objects in the database. JPA hides the details of how this can be done, including connection properties like usernames and passwords, and including handling connection lifecycles.
The central JPA class for this purpose is EntityManager, which uses a single configuration file called persistence.xml, together with some settings inside the Jakarta EE application server. On the Java side, the classes that correspond to table rows are called entity classes. See Figure 10-1 for an overview of JPA.
Setting Up a SQL Database
SQL databases come in two flavors—you can have fully fledged client-server databases and embedded databases (possibly using some in-memory storage). In this book, we use the Apache Derby database included in the GlassFish server distribution. This database runs independent from GlassFish, but the GlassFish administrative tools also provide some commands for handling the Apache Derby instance. As a client, we use JPA from inside the Java MVC application.
Note
In the GlassFish documentation, you’ll frequently see the name “JavaDB” as the product name for the database. JavaDB actually was the name of Derby while it was included in the JDK versions 6 through 8. Now it’s kind of obsolete, so we don’t use the name “JavaDB” in this book.
Switching to a different database product is a non-intrusive operation, so you can start learning JPA with Apache Derby and only later switch to some other database management system.
Note
From an architectural point of view, database access is best included in an EJB or EAR module. For simplicity, we include JPA directly in a Java MVC project, but the procedures to do that in an EJB or EAR module are very similar.
If you start a GlassFish server, the Apache Derby database does not automatically start as well. Instead, you must run it inside the console:
cd [GLASSFISH_INST]
bin/asadmin start-database
where [GLASSFISH_INST] is your GlassFish server’s installation directory.
Caution
Although they are both administered by asadmin, the GlassFish Jakarta EE server and the Apache Derby database management system are independent of each other. If you stop either of them, the other one continues running.
To stop a running Apache Derby, enter this inside the console:
cd [GLASSFISH_INST]
bin/asadmin stop-database
Creating a Datasource
In order for JPA to work, we need to add an object relational mapping (ORM) library to the project. There are several options here, but we choose EclipseLink as an ORM library, since EclipseLink is the reference implementation of JPA 2.2 (this is the version of JPA used in Jakarta EE 8 and Java MVC 1.0).
ORMs do not directly connect to databases but instead connect to datasources that abstract away the database access. This indirection allows for establishing connection pools, caches, transactionality, and administration of data handling using server-side administrative tools.
In order to create a suitable datasource for GlassFish, enter the following in the same terminal you used to start the database:
cd [GLASSFISH_INST]
cd javadb/bin
# start the DB client
./ij
(Or use ij for Windows.) We are now inside the ij database client, which you can see since the ij> prompt appears in the terminal. Enter the following to create a database named hello (enter this in one line without spaces in front of the create=):
ij> connect 'jdbc:derby://localhost:1527/hello;
create=true;user=user0';
The database is now created with an owner named user0. We also add a password for the user:
Apache Derby by default does not enable authentication for new databases. This normally does not cause problems if you’re using the database only for development, because network access is restricted to local users only. Many Java applications and database tools, however, behave strangely if you try to access the database without authentication, so we add a password.
Next, restart the database for the authentication to start working:
cd [GLASSFISH_INST]
cd bin
./asadmin stop-database
./asadmin start-database
This needs to be done only once. Quit and reopen the connection inside the ij tool (or quit ij altogether by pressing Ctrl+D; then restart ij and connect again):
ij> disconnect;
ij> connect 'jdbc:derby://localhost:1527/hello;
user=user0;password=pw715';
(Enter the last ij command in one line.) You can check the authentication mechanism: if you omit the username or password, or both, you’ll get an appropriate error message.
For transparent and simple connection to the database, we create two resources in the GlassFish server configuration:
cd [GLASSFISH_INST]
cd bin
./asadmin create-jdbc-connection-pool
--datasourceclassname
org.apache.derby.jdbc.ClientXADataSource
--restype javax.sql.XADataSource
--property
portNumber=1527:password=pw715:user=user0:
serverName=localhost:databaseName=hello:
securityMechanism=3
HelloPool
./asadmin create-jdbc-resource
--connectionpoolid HelloPool jdbc/Hello
(No line break and no spaces after user=user0: or databaseName = hello:.) This creates a connection pool and a JDBC resource connecting to it. We will later be using the jdbc/Hello identifier to allow JPA to connect to the database.
You can see both configuration items if you enter the administration console in your web browser at http://localhost:4848. Navigate to Resources ➤ JDBC ➤ JDBC Resources and Resources ➤ JDBC ➤ JDBC Connection Pools. See Figure 10-2.
In the rest of this chapter, we assume you know how to enter database commands. Either use the ij tool (don’t forget to connect after you start it), or use any other database client, such as the open source tool called Squirrel.
Preparing the Member Registration Application
In this chapter, we develop a basic member administration application for Java MVC. The members are stored in a database table called MEMBER. The SQL commands to create the table and a sequence generator for the unique ID generation are as follows:
CREATE TABLE MEMBER (
ID INT NOT NULL,
NAME VARCHAR(128) NOT NULL,
PRIMARY KEY (ID));
INSERT INTO MEMBER (ID, NAME)
VALUES (-3, 'John'),
(-2, 'Linda'),
(-1, 'Pat');
CREATE SEQUENCE MEMBER_SEQ start with 1 increment by 50;
We also added a couple of example entries.
Note
Apache Derby knows how to auto-generate unique IDs. We however let EclipseLink take care of that. For this reason, the ID field is left as a simple integer value field without any additional semantics. EclipseLink needs the sequence to take care of generating such unique IDs (at least if it’s used the way we are going to use it).
The project structure for the new database project is as follows:
Project HelloJpa
src/main/java
book.javamvc.jpa
data
User.java
db
Member.java
MemberDAO.java
i18n
BundleForEL.java
SetBundleFilter.java
model
UserEntering.java
UserList.java
AjaxController.java
App.java
HelloJpaController.java
RootRedirector.java
src/main/resources
book.javamvc.jpa.messages
Messages.properties
META-INF
persistence.xml
src/main/webapp
js
jquery-3.5.1.min.js
WEB-INF
views
index.jsp
beans.xml
glassfish-web.xml
build.gradle
gradle.properties
settings.gradle
We don’t want to mix Java MVC model classes and database model classes, so in the User.java class, we abstract away any user data:
package book.javamvc.jpa.data;
public class User {
private int id;
private String name;
public User() {
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
// Getters and setters...
}
The BundleForEL and SetBundleFilter classes are exactly the same as in the HelloWorld application, but with the addition to factor out configuration values (made in one of the exercises). For clarity, I repeat the code here:
We place the two Java MVC model classes for the new member entry and the member list into the book.javamvc.jpa.model package. The code reads as follows:
package book.javamvc.jpa.model;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import book.javamvc.jpa.data.User;
@Named
@RequestScoped
public class UserEntering extends User {
}
and
package book.javamvc.jpa.model;
import java.util.ArrayList;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import book.javamvc.jpa.data.User;
@Named
@RequestScoped
public class UserList extends ArrayList<User>{
private static final long serialVersionUID =
8570272213112459191L;
}
The App and RootRedirector classes are the same as in the HelloWorld application, but with the refactoring done in one of the exercises:
This is the central configuration file for JPA. Here, we indicate how to connect to the database. Note that we refer to the datasource resources we configured previously.
Note
The Eclipse IDE has a few helper wizards for JPA-related development, and it also has a JPA facet you can add to projects. I decided against using these in this introductory level chapter, to avoid a vendor lock-in and to show the basics needed while following the JPA specification. You are free to try the JPA facet of Eclipse.
Controllers
The controller for the member registration application closely resembles the HelloWorld controller from previous chapters—we again have a landing page that this time lists all the members, and an input form for new members. Adding a member leads to a database INSERT operation, and in contrast to HelloWorld, we don’t show a response page but reload the index page with the updated member list. The code reads as follows:
An important distinction in the HelloWorld example application is the inclusion of the MemberDAO data access object for database operations it gets referred to from the member addition and listing methods. We’ll talk about the DAO in the next sections.
A member deletion is handled by an AJAX request. In contrast to what we did in previous chapters, we don’t let the Java MVC controller deal with AJAX requests. Instead, we add an additional JAX-RS controller, as follows:
just for AJAX:
package book.javamvc.jpa;
import javax.ejb.EJB;
import javax.ws.rs.DELETE;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import book.javamvc.jpa.db.MemberDAO;
@Path("/ajax")
public class AjaxController {
@EJB private MemberDAO memberDao;
@DELETE
@Path("/delete/{id}")
public Response delete(@PathParam("id") int id) {
memberDao.deleteMember(id);
return Response.ok("{}").build();
}
}
Adding Data Access Objects
A data access object, or DAO, is a Java class that encapsulates database operations like CRUD (create, read, update, and delete). A client of the DAO then doesn’t have to know how the DAO works and only needs to take care of the business functionality.
Inside the controllers, a DAO class called MemberDAO is injected via the @EJB annotation. This class goes to the book.javamvc.jpa.db package. Create the package and the class, and then write the following class code:
package book.javamvc.jpa.db;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
@Stateless
public class MemberDAO {
@PersistenceContext
private EntityManager em;
public int addMember(String name) {
List<?> l = em.createQuery(
"SELECT m FROM Member m WHERE m.name=:name").
setParameter("name", name).
getResultList();
int id = 0;
if(l.isEmpty()) {
Member member = new Member();
member.setName(name);
em.persist(member);
em.flush(); // needed to get the ID
id = member.getId();
} else {
id = ((Member)l.get(0)).getId();
}
return id;
}
public List<Member> allMembers() {
TypedQuery<Member> q = em.createQuery(
"SELECT m FROM Member m", Member.class);
List<Member> l = q.getResultList();
return l;
}
public void deleteMember(int id) {
Member member = em.find(Member.class, id);
em.remove(member);
}
}
We provide methods to add members (avoiding duplicates), to list all members, and to delete members. Update and search methods are left for future improvements. You can see that database operations are exclusively handled by an EntityManager, which is injected by the @PersistenceContext annotation. By the configuration file persistence.xml, JPA knows which database the entity manager needs to access. For most operations currently needed, we can use the methods from the EntityManager class. The only exception is the complete list for which we use the JPA query language expression SELECT m FROM Member m.
The application knows that this DAO is an EJB by the @Stateless class annotation. Because of this, the container (the part of the server that handles EJB objects) knows that instances of this class don’t have a state.
Updating the View
For the basic member registration application, as a view, we only need the index.jsp file:
<%@ page contentType="text/html;charset=UTF-8"
language="java" %>
<%@ taglib prefix="c"
uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt"
uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<meta charset="UTF-8">
<script type="text/javascript"
src="${mvc.basePath}/../js/jquery-3.5.1.min.js">
</script>
<title>${msg.title}</title>
<script type="text/javascript">
function deleteItm(id) {
var url =
"${pageContext.servletContext.contextPath}" +
"/mvc/ajax/delete/" + id;
jQuery.ajax({
url : url,
method: "DELETE",
dataType: 'json',
success: function(data, textStatus, jqXHR) {
jQuery('#itm-'+id).remove();
},
error: function (jqXHR, textStatus,
errorThrown) {
console.log(errorThrown);
}
});
return false;
}
</script>
</head>
<body>
<form method="post"
action="${mvc.uriBuilder(
'HelloJpaController#greeting').build()}">
${msg.enter_name}
<input type="text" name="name" />
<input type="submit" value="${msg.btn_submit}" />
</form>
<table>
<thead>
<tr>
<th>${msg.tblhdr_id}</th>
<th>${msg.tblhdr_name}</th>
<th></th>
</tr>
<thead>
<tbody>
<c:forEach items="${userList}" var="itm">
<tr id="itm-${itm.id}">
<td>${itm.id}</td>
<td>${itm.name}</td>
<td><button onclick="deleteItm(${itm.id})">
${msg.btn_delete}</button></td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
This page shows the form for entering a new member and the full member list. Because of the itm-[ID] we add to each table row, the AJAX code to remove an item can remove a table row without having to reload the full page.
The view refers to the jQuery library. Download it and copy it to src/main/webapp/js. Adapt versions accordingly.
A language resource goes to src/main/resources/book/javamvc/jpa/messages/Messages.properties:
title = Hello Jpa
enter_name = Enter your name:
btn_delete = Delete
btn_submit = Submit
tblhdr_id = ID
tblhdr_name = Name
You can copy the beans.xml and glassfish-web.xml files from Chapter 4.
Adding Entities
An entity is a representation of a table row as an object. If we think of the MEMBER table, an entity is something that has a name and a single ID. Obviously, this corresponds to a Java class with the name and id fields. So we create such a class and put it in the book.javamvc.jpa.db package:
public class Member {
private int id; // + getter/setter
private String name; // + getter/setter
}
To complete the database-interfacing process, we need to add meta-information though. The information that this is an entity class, the table name, column names, a dedicated ID column name, a unique ID generator specification, and database field value constraints. As is usually the case with Java, we use annotations for such meta-information. Our class, with all those amendments, reads as follows:
@Entity: Marks this as an entity so JPA knows this is an entity class.
@Table: Used to specify the table name. If omitted, the class name (without package) will be used as a table name.
@SequenceGenerator: Used to specify a sequence generator for unique IDs.
@Id: Indicates that the corresponding field refers to the unique ID of the entity.
@GeneratedValue: Indicates that new entities will auto-generate values for this field.
@Column: Used to specify the column name corresponding to this field. If unspecified, the field name will be used as the column name.
@NotNull: A constraint indicating that neither the field nor the database field can be null.
Given the entity classes, JPA now knows how to map database entry fields to Java classes. With the Java MVC controller adapted and the DAO and entity classes added, the application has a fully functional JPA support engaged and you can deploy and try it at http://localhost:8080/HelloJpa. Also try restarting the server and verify that the entries were persisted and survive a server restart. You can also directly check the database using a database client tool and investigate the table rows that were added there.
Adding Relations
Relational data is about relationships like one table entry referring to entries from other tables. JPA provides a solution to such relationships, again by special annotations you can add to entity classes.
Consider the following example: In our membership application, we add another table called STATUS that contains membership status entries, such as Gold, Platinum, Senior, or whatever you might think of. Each member may have 0 to N status entries, so we talk about a “one-to-many” relationship between members and status entries.
To achieve this, we first create the STATUS table and a STATUS_SEQ sequence for it:
CREATE TABLE STATUS (
ID INT NOT NULL,
MEMBER_ID INT NOT NULL,
NAME VARCHAR(128) NOT NULL,
PRIMARY KEY (ID));
CREATE SEQUENCE STATUS_SEQ start with 1 increment by 50;
Next, we create a new entity class called Status inside the book.javamvc.jpa.db package with the following contents:
package book.jakarta8.calypsojpa.jpa;
import javax.persistence.*;
import javax.validation.constraints.*;
@Entity
@Table(name="STATUS")
@SequenceGenerator(name="STATUS_SEQ",
initialValue=1, allocationSize = 50)
public class Status implements Comparable<Status> {
We added a constructor for easy construction using the name. It is important to know that the JPA specification requires that there be a public no-argument constructor.
Inside the entity class Member, we add a field that corresponds to the actual relationship between member and status:
Everything else is untouched. The @JoinColumn field refers to a member in the associated class or table, so we don’t have to update the member table for this new field.
Because the two entity classes’ relationship is announced via @OneToMany, any entity manager operations will automatically take care of correctly cascading database operations to related entities. For example, to create a new member, you can write the following:
...
Member m = new Member();
m.setName(...);
Set<Status> status = new HashSet<>();
status.add(new Status("Platinum"));
status.add(new Status("Priority"));
m.setStatus(status);
em.persist(m);
...
So you don’t have to explicitly tell the entity manager to persist the related Status entities.
In the frontend code, you can add a text field with a comma-separated list of status values, or a select listbox or menu to reflect the relationship. The same holds for UPDATE and DELETE operations. Because of the cascade = CascadeType.ALL inside the @OneToMany annotation, JPA will even delete related Status entries from the STATUS table if members are deleted.
There are other association types in a relational data model. The possible association types you can declare for entities in JPA are as follows:
@OneToMany
For entities of entity class A, zero to many related entries of entity class B exist. Inside class A, you define a field of type Set with the OneToMany annotation. Inside entity B’s table, you then have a foreign key called ID_A (or whatever name you like), and inside the entity class B is a field called aId (or whatever name you like) pointing to A IDs. To tell A how it is related to B, you then add another annotation called @JoinColumn, as in:
@OneToMany
@JoinColumn(name="ID_A") // In table B!
private Set<B> b;
Or you add an attribute to @OneToMany, as in:
@OneToMany(mappedBy = "aId") // Field in class B!
private Set<B> b;
@ManyToOne
For zero or many entities of entity class A, one related entry of entity class B exists. Inside class A, you add a field of type B with the @ManyToOne and @JoinColumn annotations, where for the latter, you provide a column name (inside A’s table) for the join:
@ManyToOne
@JoinColumn(name="ID_B") // In table A
private B b;
@OneToOne
For one entity of entity class A, one related entry of entity class B exists. Inside class A, you add a field of type B with the @OneToOne and @JoinColumn annotations, where for the latter, you provide a column name (inside A’s table) for the join:
@OneToOne
@JoinColumn(name="ID_B") // In table A
private B b;
@ManyToMany
For zero or many entities of entity class A, zero or many related entries of entity class B exist. Here, we need a third table serving as an intermediate join table; for example MTM_A_B, with columns ID_A and ID_B. The annotations in entity class A (with ID column "ID") then read as follows:
@ManyToMany
@JoinTable(
name = "MTM_A_B",
joinColumns = @JoinColumn(
name = "ID_A",
referencedColumnName="ID"),
inverseJoinColumns = @JoinColumn(
name = "ID_B",
referencedColumnName="ID"))
private Set<B> b;
Exercises
Exercise 1: Which of the following are true?
1.
JPA connects to a database via some datasource, which is a server-managed resource.
2.
JPA connects to a database via some datasource, which JPA itself provides.
3.
JPA connects to a database via JDBC.
4.
JPA connects to a database via EJB.
Exercise 2: Which component of JPA (or concept, if you like) translates between database tables and Java objects (three letter acronym)?
Exercise 3: Which of the following is true:
1.
DAOs are needed to connect to databases via JPA.
2.
DAOs are needed to provide the database username and password.
3.
In DAOs, database column names have to be specified.
4.
DAOs are used to avoid using database table details in JPA client classes.
5.
To use DAOs, they must be injected as EJBs.
Exercise 4: Which of the following are true?
1.
One entity class corresponds to one database table.
2.
An entity class must have the same name as the database table.
3.
Properties (fields) of entity classes must have the same names as the columns in the database table.
4.
Properties of entity classes can have restrictions.
Exercise 5: Add the STATUS table to the database and update the member entry application’s code to reflect the status of members. For simplicity, use a text field whereby you can enter a comma-separated list of status values.
Exercise 6: Name the four annotations used inside JPA for relationships between tables.
Summary
JPA (Java Persistence API) is the dedicated technology used to accessing relational databases from inside Jakarta EE. Its aim is to provide a bridge between SQL tables and Java objects.
One of the primary purposes of JPA is to abstract away database access and map database objects to Java classes. In the end, we want to be able to query the database and get Java objects, or to put Java objects in the database. JPA helps to hide the details of how this can be done, including connection properties like usernames and passwords, and including handling connection lifecycles.
The central JPA class for this purpose is the EntityManager class, which uses a single configuration file called persistence.xml, together with some settings inside the Jakarta EE application server. On the Java side, the classes that correspond to table rows are called entity classes.
In order for JPA to work, we need to add an object relational mapping (ORM) library to the project. There are several options here, but we choose EclipseLink as an ORM library, since EclipseLink is the reference implementation of JPA 2.2 (this is the version of JPA used in Jakarta EE 8 and Java MVC 1.0).
ORMs do not directly connect to databases, but instead connect to datasources that abstract away the database access. This indirection allows for establishing connection pools, caches, transactionality, and administration of data handling using server-side administrative tools. Datasources are installed in a server product specific manner.
A data access object, or DAO, is a Java class that encapsulates database operations like CRUD (create, read, update, and delete). A client of the DAO then doesn’t have to know how the DAO works and only needs to take care of the business functionality.
An entity is a representation of a table row as an object. To complete the database-interfacing process, we need to add meta-information. The information that this is an entity class, the table name, column names, a dedicated ID column name, a unique ID generator specification, and database field value constraints. As is usually the case with Java, we use annotations for such meta-information.
Given the entity classes, JPA now knows how to map database entry fields to Java classes. With the Java MVC controller adapted and the DAO and entity classes added, the application has fully functional JPA support engaged.
Relational data is about relationships, such as one table entry referring to entries from other tables. JPA provides a solution to such relations, again by special annotations you can add to entity classes.
The possible association types you can declare for entities in JPA are as follows:
@OneToMany
For entities of entity class A, zero to many related entries of entity class B exist. Inside class A, you define a field of type Set with the OneToMany annotation. Inside entity B’s table, you then have a foreign key called ID_A (or whatever name you like), and inside the entity class B is a aId field (or whatever name you like) pointing to A IDs. To tell A how it is related to B, you then add another annotation called @JoinColumn, as in:
@OneToMany
@JoinColumn(name="ID_A") // In table B!
private Set<B> b;
Or you add an attribute to @OneToMany, as in:
@OneToMany(mappedBy = "aId") // Field in class B!
private Set<B> b;
@ManyToOne
For zero or many entities of entity class A, one related entry of entity class B exists. Inside class A, you add a field of type B with the @ManyToOne and @JoinColumn annotations, where for the latter you provide a column name (inside A’s table) for the join:
@ManyToOne
@JoinColumn(name="ID_B") // In table A
private B b;
@OneToOne
For one entity of entity class A, one related entry of entity class B exists. Inside class A, you add a field of type B with the @OneToOne and @JoinColumn annotations, where for the latter, you provide a column name (inside A’s table) for the join:
@OneToOne
@JoinColumn(name="ID_B") // In table A
private B b;
@ManyToMany
For zero or many entities of entity class A, zero or many related entries of entity class B exist. Here, we need a third table serving as an intermediate join table; for example, MTM_A_B, with columns ID_A and ID_B. The annotations in entity class A (with ID column "ID") then read as follows:
@ManyToMany
@JoinTable(
name = "MTM_A_B",
joinColumns = @JoinColumn(
name = "ID_A",
referencedColumnName="ID"),
inverseJoinColumns = @JoinColumn(
name = "ID_B",
referencedColumnName="ID"))
private Set<B> b;
In the next chapter, we talk about logging in Java MVC.