© Peter Späth 2021
P. SpäthBeginning Java MVC 1.0https://doi.org/10.1007/978-1-4842-6280-1_10

10. Connecting Java MVC to a Database

Peter Späth1 
(1)
Leipzig, Sachsen, Germany
 

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.
../images/499016_1_En_10_Chapter/499016_1_En_10_Fig1_HTML.png
Figure 10-1

JPA inside Jakarta EE at work

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:
ij> call SYSCS_UTIL.SYSCS_CREATE_USER('user0','pw715');
Note

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.
../images/499016_1_En_10_Chapter/499016_1_En_10_Fig2_HTML.jpg
Figure 10-2

JDBC resources

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:
package book.javamvc.jpa.i18n;
import java.util.Enumeration;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.servlet.http.HttpServletRequest;
public class BundleForEL extends ResourceBundle {
    private BundleForEL(Locale locale, String baseName) {
        setLocale(locale, baseName);
    }
    public static void setFor(HttpServletRequest request,
        String i18nAttributeName, String i18nBaseName) {
     if (request.getSession().
           getAttribute(i18nAttributeName) == null) {
         request.getSession().setAttribute(
           i18nAttributeName,
           new BundleForEL(request.getLocale(),
                           i18nBaseName));
           }
    }
    public void setLocale(Locale locale,
           String baseName) {
        if (parent == null ||
              !parent.getLocale().equals(locale)) {
            setParent(getBundle(baseName, locale));
        }
    }
    @Override
    public Enumeration<String> getKeys() {
        return parent.getKeys();
    }
    @Override
    protected Object handleGetObject(String key) {
        return parent.getObject(key);
    }
}
and
package book.javamvc.jpa.i18n;
import java.io.IOException;
import java.util.Map;
import javax.inject.Inject;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Application;
@WebFilter("/*")
public class SetBundleFilter implements Filter {
    @Inject private Application appl;
    private String i18nAttributeName;
    private String i18nBaseName;
    @Override
    public void init(FilterConfig filterConfig)
          throws ServletException {
      Map<String,Object> applProps = appl.getProperties();
      i18nAttributeName = (String) applProps.get(
          "I18N_TEXT_ATTRIBUTE_NAME");
      i18nBaseName = (String) applProps.get(
          "I18N_TEXT_BASE_NAME");
    }
    @Override
    public void doFilter(ServletRequest request,
        ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
      BundleForEL.setFor((HttpServletRequest) request,
        i18nAttributeName, i18nBaseName);
      chain.doFilter(request, response);
    }
    @Override
    public void destroy() {
    }
}
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:
package book.javamvc.jpa;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application;
@ApplicationPath("/mvc")
public class App extends Application {
    @PostConstruct
    public void init() {
    }
    @Override
    public Map<String, Object> getProperties() {
      Map<String, Object> res = new HashMap<>();
      res.put("I18N_TEXT_ATTRIBUTE_NAME",
        "msg");
      res.put("I18N_TEXT_BASE_NAME",
        "book.javamvc.jpa.messages.Messages");
      return res;
    }
}
and
package book.javamvc.jpa;
import javax.servlet.FilterChain;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * Redirecting http://localhost:8080/HelloJpa/
 * This way we don't need a <welcome-file-list> in web.xml
 */
@WebFilter(urlPatterns = "/")
public class RootRedirector extends HttpFilter {
    private static final long serialVersionUID =
          7332909156163673868L;
    @Override
    protected void doFilter(final HttpServletRequest req,
          final HttpServletResponse res,
          final FilterChain chain) throws IOException {
        res.sendRedirect("mvc/hello");
    }
}
build.gradle takes the following code:
plugins {
  id 'war'
}
java {
  sourceCompatibility = JavaVersion.VERSION_1_8
  targetCompatibility = JavaVersion.VERSION_1_8
}
repositories {
  jcenter()
}
dependencies {
  testImplementation 'junit:junit:4.12'
  implementation 'javax:javaee-api:8.0'
  implementation 'javax.mvc:javax.mvc-api:1.0.0'
  implementation 'org.eclipse.krazo:krazo-jersey:1.1.0-M1'
  implementation 'jstl:jstl:1.2'
  implementation 'org.eclipse.persistence:'+
      'eclipselink:2.7.7'
}
task localDeploy(dependsOn: war,
    description:">>> Local deploy task") {
  // Take the code from the HelloWorld example
}
task localUndeploy(
    description:">>> Local undeploy task") {
  // Take the code from the HelloWorld example
}

The settings.gradle file is prepared by the project generator wizard, and the gradle.properties file can directly be taken from Chapter 4.

All other files are described in the subsequent sections.

Adding EclipseLink as ORM

To add the EclipseLink ORM to the project, add the following to the dependencies { } section of the build.gradle file:
dependencies {
    ...
    implementation 'org.eclipse.persistence:'+
        'eclipselink:2.7.7'
}
Next, create a src/main/resources/META-INF/persistence.xml file with the following contents:
<persistence
    xmlns:=
        "http://java.sun.com/xml/ns/persistence"
    xmlns:xsi=
        "http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation=
        "http://java.sun.com/xml/ns/persistence persistence_1_0.xsd"
    version="1.0">
<persistence-unit name="default"
      transaction-type="JTA">
    <jta-data-source>jdbc/Hello</jta-data-source>
    <exclude-unlisted-classes>
      false
    </exclude-unlisted-classes>
    <properties />
</persistence-unit>
</persistence>

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:
package book.javamvc.jpa;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.mvc.Controller;
import javax.mvc.binding.BindingResult;
import javax.mvc.binding.MvcBinding;
import javax.mvc.binding.ParamError;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import book.javamvc.jpa.data.User;
import book.javamvc.jpa.db.MemberDAO;
import book.javamvc.jpa.model.UserEntering;
import book.javamvc.jpa.model.UserList;
@Path("/hello")
@Controller
public class HelloJpaController {
  @Named
  @RequestScoped
  public static class ErrorMessages {
    private List<String> msgs = new ArrayList<>();
    public List<String> getMsgs() {
      return msgs;
    }
    public void setMsgs(List<String> msgs) {
      this.msgs = msgs;
    }
    public void addMessage(String msg) {
      msgs.add(msg);
    }
  }
  @Inject private ErrorMessages errorMessages;
  @Inject private BindingResult br;
  @Inject private UserEntering userEntering;
  @Inject private UserList userList;
  @EJB private MemberDAO memberDao;
  @GET
  public String showIndex() {
    addUserList();
    return "index.jsp";
  }
  @POST
  @Path("/add")
  public Response addMember(
        @MvcBinding @FormParam("name") String name) {
    if(br.isFailed()) {
      br.getAllErrors().stream().
            forEach((ParamError pe) -> {
        errorMessages.addMessage(pe.getParamName() +
              ": " + pe.getMessage());
      });
    }
    userEntering.setName(name);
    memberDao.addMember(userEntering.getName());
    addUserList();
    return Response.ok("index.jsp").build();
  }
  ////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////
  private void addUserList() {
    userList.addAll(
      memberDao.allMembers().stream().map(member -> {
          return new User(member.getId(),
                          member.getName());
      }).collect(Collectors.toList())
    );
  }
}

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:
package book.javamvc.jpa.db;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
@Entity
@Table(name="MEMBER")
@SequenceGenerator(name="HELLO_SEQ",
                  initialValue=1, allocationSize = 50)
public class Member {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY,
        generator = "HELLO_SEQ")
  @Column(name = "id")
  private int id;
  @NotNull
  @Column(name = "name")
  private String name;
  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}
In detail, the annotations we added are:
  • @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> {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY,
                    generator="STATUS_SEQ")
    @Column(name = "ID")
    private int id;
    @NotNull
    @Column(name = "MEMBER_ID")
    private int memberId;
    @NotNull
    @Column(name = "NAME")
    private String name;
    public Status() {
    }
    public Status(String name) {
      this.name = name;
    }
    @Override
    public int compareTo(Status o) {
      return -o.name.compareTo(name);
    }
    // + getters and setters
}

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:
...
@JoinColumn(name = "MEMBER_ID")
@OneToMany(cascade = CascadeType.ALL, orphanRemoval= true)
private Set<Status> status; // + getter / setters
...

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. 1.

    JPA connects to a database via some datasource, which is a server-managed resource.

     
  2. 2.

    JPA connects to a database via some datasource, which JPA itself provides.

     
  3. 3.

    JPA connects to a database via JDBC.

     
  4. 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. 1.

    DAOs are needed to connect to databases via JPA.

     
  2. 2.

    DAOs are needed to provide the database username and password.

     
  3. 3.

    In DAOs, database column names have to be specified.

     
  4. 4.

    DAOs are used to avoid using database table details in JPA client classes.

     
  5. 5.

    To use DAOs, they must be injected as EJBs.

     
  • Exercise 4: Which of the following are true?

  1. 1.

    One entity class corresponds to one database table.

     
  2. 2.

    An entity class must have the same name as the database table.

     
  3. 3.

    Properties (fields) of entity classes must have the same names as the columns in the database table.

     
  4. 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.26.176