Object-relational mapping and the Java Persistence API (JPA)

Practice for Week 9

In this exercise you will create an address book application using JPA.

Create an Enterprise Application

During the last tutorial, you created an Enterprise Application called Week8.

Having EJBs in a separate project is "best" practice. It helps enforce separation between presentation and domain logic. In previous Java EE versions, this separation was mandatory. However, Java EE 6 introduced the ability to include EJBs in WAR files. For simple projects, placing all files in a single WAR may be more practical. However, for the sake of this exercise we will use two separate projects within an Enterprise Application (a total of three projects).

This week, you should create a new Enterprise Application called Week9. Be careful! Last week's tutorial may have changed the default location. Make sure the project location is your NetBeansProjects folder and NOT NetBeansProjects/Week8.

  1. Create a new project of type "Enterprise Application" in the "Java EE" category.
  2. Name the project Week9 and ensure that the "Project Location" is correct.
  3. Use GlassFish as your server and leave the option to create Week9-ejb and Week9-war checked.

Before continuing, you should start your database and GlassFish. The easy way to do this is to right click on your Week9 Enterprise Application and click on "Run".

Important Tip

During the Week 8 tutorial, I provided some suggestions for building and running your project. I repeat those suggestions here:

Java EE 7 allows you to deploy EJBs using a WAR or an EAR file. In NetBeans, this means that even though your Week9-war is only part of your Enterprise Application, you are still able to run Week9-war directly.

This can result in problems when/if the same EJB is deployed twice. I recommend only deploying or running your application via the Week9 enterprise application.

This means that if you want to run your project, you should NOT run individual JSF (xhtml) files. Instead, you should right click on, and run, the Week9 Enterprise Application. Avoid using the large green "play" button in NetBeans as it may not run the project that you are intending to run.

If you are experiencing problems with NetBeans and/or GlassFish, here are some steps you might try to resolve the problem:

  • Deploy the Enterprise Application again. Right click on the Enterprise Application (e.g., Week9) and then click "Deploy".
  • Undeploy all applications. In the Services tab, locate Servers > GlassFish Server > Applications. Right click on Applications to refresh the list. Select all the Applications with your mouse (hold down the shift key). Then right click and select "Undeploy".
  • Restart GlassFish. If you are seeing error messages about NetBeans being unable to delete JAR files, then you should undeploy all applications and restart GlassFish. To restart GlassFish, in the Services tab locate Servers > GlassFish Server. Then, right click on GlassFish Server and select Restart (or Stop and then Start).

Ensure your Database is Configured

In the Week 5 labs, you created a database called "aip". You used the GlassFish admin console to configured it as a JNDI resource named jdbc/aip.

Please check that you still have this database and JNDI name configured. If you do not have these configured, you should refer to the exercises from the Week 5 labs. Follow the note titled "Create Database" to set up the aip database. Follow the note "Container Managed Connections" to configure a Connection pool and JNDI resource named "jdbc/aip".

In NetBeans, check the tables in the AIP database. There may be one or more tables already in the database. This is fine. However, if you have created a table named "Person" or "ContactMethod", then please delete (i.e., drop) those tables because we will be creating new tables with those names.

You do not need to delete Account or any other tables. Just make sure that there is no table named "Person" or "ContactMethod".

The two images below illustrate the process of connecting to the database and deleting an unwanted table.

Connect to the database

Drop table

In this step you will create a Persistence Unit and a JPA Entity.

JPA will automatically map the entity into database table(s).

Create a Persistence Unit

Create a new file in the Week9-ejb project. You can do this by right clicking on Week9-ejb, clicking on "New" and then clicking on "Other...".

  1. The file should be of type "Persistence Unit" in the "Persistence" category.
  2. Leave the default persistence unit name ("Week9-ejbPU"), in "Data Source" enter "jdbc/aip" (without the quotes), ensure "Use Java Transaction APIs" is checked and that the "Create" table generation strategy is selected.

To assist with debugging, we will increase the level of logging detail.

  1. Double click on persistence.xml in your Week9-ejb project.
  2. Switch to the Source view and add the following XML element inside the <properties> element:
    <property name="eclipselink.logging.level" value="FINE"/>

Create a Stateless Bean

Because we selected the "Create" table generation strategy, JPA will automatically create all the necessary tables.

To get JPA to create the tables, we need to use the persistence unit.

In the Week9-ejb project, create a Java class called AddressBookBean in the au.edu.uts.aip.addressbook.domain package.

Enter the following Java code:

package au.edu.uts.aip.addressbook.domain;

import javax.ejb.*;
import javax.persistence.*;

@Stateless
public class AddressBookBean {

    @PersistenceContext
    private EntityManager em;

}

This code is a stateless session bean. It doesn't do anything (yet). However, by referencing the persistence unit, it will cause JPA to create tables for any entities it finds.

Create a JPA Entity

Create a new class named Person in the au.edu.uts.aip.addressbook.domain package.

Enter the following code:

package au.edu.uts.aip.addressbook.domain;

import java.io.*;
import javax.persistence.*;

@Entity
public class Person implements Serializable {

    private int id;

    @Id
    @GeneratedValue
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

}

Remember that there are two ways to annotate entities: fields or properties. A field is a variable of a class, whereas a property is a getter/setter. In the above code we have annotated the getter for the "id" property with @Id and @GeneratedValue. This means that "id" is the primary key and that it will be automatically generated if none is provided.

Now you can redeploy your project. Right click on Week9 and select "Deploy". If you look at the GlassFish server log, you should see logging information from JPA. You will see create SQL statements such as the following:

Fine:   CREATE TABLE PERSON (ID INTEGER NOT NULL, PRIMARY KEY (ID))
Fine:   CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(15), PRIMARY KEY (SEQ_NAME))
Fine:   SELECT * FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
Fine:   INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values (SEQ_GEN, 0)

A table called SEQUENCE has also been created. On JavaDB, EclipseLink JPA uses a special table called SEQUENCE to keep track of unique ids used in entities.

You can also confirm that the table is created by viewing the database in the "Services" tab as depicted below.

Note: You will probably need to refresh your view. Do this by right clicking on the database or "Tables" and clicking "Refresh".

Created tables

Reflect

Can you think of any advantages or disadvantages in choosing between field or property annotations?

Now that you have confirmed that JPA is creating database tables, you can create a schema for the entire database.

First, delete (or drop) the Person table. You can do this manually from the Services tab by right clicking on the table and selecting delete.

Note also that whenever you save changes your Java files, NetBeans may redeploy your project. Whenever your project is redeployed, JPA will generate any tables it needs. So if you save changes to a half-finished JPA entity file you might need to delete the underlying table from the database again. (Advanced tip: you can change this behaviour by setting the table generation strategy in persistence.xml)

Create a class named PhoneType in the au.edu.uts.aip.addressbook.domain package of the Week9-ejb project.

Enter the following code:

package au.edu.uts.aip.addressbook.domain;

public enum PhoneType {

    HOME,
    WORK,
    MOBILE,
    OTHER

}

You have just created a Java enum type. If you do not understand enums, you can learn more about them in the Java Tutorial.

ContactMethod Entity

Create a class named ContactMethod in the au.edu.uts.aip.addressbook.domain package of the Week9-ejb project.

You should now modify the code in Person.java and ContactMethod.java so that JPA generates database tables corresponding to the E-R diagram below.

E-R diagram

The result should be equivalent to the following SQL. In fact, you should see the following SQL in your GlassFish server logs (except that it will not be formatted into lines and it will all be in capital letters).

create table ContactMethod (
  id integer not null,
  phoneNumber varchar(255),
  phoneType varchar(255),
  person_id integer,
  primary key (id)
);

create table Person (
  id integer not null,
  dateOfBirth date,
  firstName varchar(255),
  lastName varchar(255),
  primary key (id)
);

alter table ContactMethod add constraint cntCtMethodPrsonId foreign key (person_id) references Person (id);

Hints

Whenever you change the entities, you should delete the underlying tables (you can do this manually) and deploy the enterprise application (right click on Week9 and select deploy) again to get the tables to be regenerated.

Remember to add @Entity and implements Serializable on the ContactMethod class.

You should approach the problem in stages. First try to create the two tables, without the one-to-many/many-to-one relationships. Then extend your entities with the relationship.

You might use annotations such as @Id, @GeneratedValue, @Temporal and @Enumerated.

Once you have the basic attributes of the tables working, try adding the relationship between the two entities. You might use @OneToMany and/or @ManyToOne.

Reflect

How would your approach to using JPA be different if you were interfacing with an existing database that could not be changed versus creating a new database?

Now that you have created the entities of your domain model (and generated a database), you can write code to update the database.

Sample Data

We will begin with a simple update function that adds sample data to the database.

In your stateless session bean (AddressBookBean.java), add a method named addSampleData.

Create instances of your entities and save it to the database using em.persist(object);

For example, your method might look something like this:

public void addSampleData() {
    Person mike = new Person();
    mike.setFirstName("Mike");
    mike.setLastName("Brady");
    mike.setDateOfBirth(new GregorianCalendar(1932, 9, 19).getTime());

    ContactMethod mike1 = new ContactMethod();
    mike1.setPhoneNumber("762-0799");
    mike1.setPhoneType(PhoneType.HOME);

    ContactMethod mike2 = new ContactMethod();
    mike2.setPhoneNumber("555-6161");
    mike2.setPhoneType(PhoneType.WORK);

    mike.getContacts().add(mike1);
    mike.getContacts().add(mike2);
    mike1.setPerson(mike);
    mike2.setPerson(mike);

    Person marcia = new Person();
    marcia.setFirstName("Marcia");
    marcia.setLastName("Brady");
    marcia.setDateOfBirth(new GregorianCalendar(1956, 7, 5).getTime());

    ContactMethod marcia1 = new ContactMethod();
    marcia1.setPhoneNumber("762-0799");
    marcia1.setPhoneType(PhoneType.HOME);

    marcia.getContacts().add(marcia1);
    marcia1.setPerson(marcia);

    Person alice = new Person();
    alice.setFirstName("Alice");
    alice.setLastName("Nelson");
    alice.setDateOfBirth(new GregorianCalendar(1926, 4, 5).getTime());

    ContactMethod alice1 = new ContactMethod();
    alice1.setPhoneNumber("72485899");
    alice1.setPhoneType(PhoneType.MOBILE);

    alice.getContacts().add(alice1);
    alice1.setPerson(alice);

    em.persist(mike);
    em.persist(mike1);
    em.persist(mike2);
    em.persist(marcia);
    em.persist(marcia1);
    em.persist(alice);
    em.persist(alice1);
}

Invoke Add Sample Data

Now, we can call the addSampleData function from a JSF page.

Add JavaServer Faces as a framework to your Week9-war project:

  1. Right click on Week9-war and select "Properties"
  2. Change to the "Frameworks" category
  3. Click on "Add..." and add "JavaServer Faces"
  4. Click "OK"

Also, remember to add the Java EE 7 API Library to your Week9-war project (you can do that within the project properties, or by right clicking on the "Libraries" folder in your project).

Add a JSF page named "addressbook" (i.e., addressbook.xhtml) to the Week9-war project.

Inside your Week9-war project, create a backing bean (i.e., a Java class). Name the backing bean "AddressBookController" and create it in the au.edu.uts.aip.addressbook.web package.

You can use this sample code for your controller:

package au.edu.uts.aip.addressbook.web;

import au.edu.uts.aip.addressbook.domain.*;
import javax.ejb.*;
import javax.enterprise.context.*;
import javax.inject.*;

@Named
@RequestScoped
public class AddressBookController {

    @EJB
    private AddressBookBean addressBookBean;

    public void addSampleData() {
        addressBookBean.addSampleData();
    }

}

Now, add a command button to addressbook.xhtml that will call the addSampleData method of the backing bean.

Run your application (remember to do this by running the Week9 project, NOT Week9-war).

After clicking on the command button, you can check the database in NetBeans to ensure that the entities have been saved (i.e., "persisted") to the database.

Reflect

Can you explain the full sequence of steps that happens when you click on the command button?

How could addSampleData be modified/simplified if you were to use cascade = CascadeType.ALL inside the @OneToMany and/or @ManyToOne annotations?

In addSampleData, why have we populated both sides of the one-to-many relationship (i.e., setPerson and getContacts().add)? What would happen if you do not set both sides?

Query Data

In this exercise, you will write a JPA query.

Add the following method to your EJB:

public List<Person> findByLastName(String lastName) {
    // This method will be replaced later with a JPA query
    return new ArrayList<>();
}

Add the following method to your JSF Backing Bean:

public void dumpBradys() {
    Logger log = Logger.getLogger(this.getClass().getName());
    for (Person p : addressBookBean.findByLastName("Brady")) {
        log.log(
            Level.INFO,
            "firstName = {0}, lastName = {1}, dateOfBirth = {2}",
            new Object[] {p.getFirstName(), p.getLastName(), p.getDateOfBirth()}
        );
    }
}

You may need to change the name ("Brady") used in the search if you have different sample data.

Add the following button to addressbook.xhtml:

<h:commandButton value="Dump Bradys to Log" action="#{addressBookController.dumpBradys}"/>

Clicking on the button will cause dumpBradys to be invoked. This function will use findByLastName to retrieve all JPA entities with the last name of "Brady". The result of the query will be output to the server log.

You can now modify the findByLastName method in your EJB to query the database. The method should result in the following output in your server log:

Info:   firstName = Mike, lastName = Brady, dateOfBirth = 19/10/32 12:00 AM
Info:   firstName = Marcia, lastName = Brady, dateOfBirth = 5/09/56 12:00 AM

Hints

You could use a simple JPQL query, a named query or a criteria query. You will replace the body of the findByLastName function.

For example, you might have code that looks something like this (you will need to replace the red text with something else):

TypedQuery<Person> query = em.createSomething(something, Person.class);
em.doMoreConfiguration(....);
return query.getResultList();

You can see example JPQL queries in the lecture notes or at the following web sites:

  1. JPQL / named queries:
    http://docs.oracle.com/javaee/6/tutorial/doc/bnbtl.html
  2. Criteria queries:
    http://www.objectdb.com/java/jpa/query/criteria
    http://docs.oracle.com/javaee/6/tutorial/doc/gjivm.html

Now you can create a CRUD-style interface to view, update and delete people from your address book.

At this stage, you do not need to create a full application. The objective is to experiment with JPA, rather than focus on JavaServer Faces.

Creating a View

A simple way to show all contacts would be to use a h:dataTable to view people, and then use a nested h:dataTable inside a column to show all the contacts for that person.

Here is one way you might view the data in the address book:

<h:dataTable var="person" value="#{addressBookController.people}">
    <h:column>
        <f:facet name="header">First Name</f:facet>
        #{person.firstName}
    </h:column>
    <h:column>
        <f:facet name="header">Last Name</f:facet>
        #{person.lastName}
    </h:column>
    <h:column>
        <f:facet name="header">Date of Birth</f:facet>
        #{person.dateOfBirth}
    </h:column>
    <h:column>
        <f:facet name="header">Contacts</f:facet>
        <h:dataTable var="contact" value="#{person.contacts}">
            <h:column>
                #{contact.phoneNumber}
            </h:column>
            <h:column>
                #{contact.phoneType}
            </h:column>
        </h:dataTable>
    </h:column>
</h:dataTable>

You can edit addressbook.xhtml to show a list of all people, and their contact details, in the database.

Note that you will also need to add a method to the backing bean. In the example above, I have used the following method signature:

public List<Person> getPeople() { ... }

You will also need to add a method to the stateless session bean (AddressBookBean.java) to retrieve the list of people (e.g., a function called findAll()). Your getPeople() function should call that new method.

Hints

Your findAll method in the EJB (AddressBookBean.java) will need to use a JPA query.

As with the search function, you could use a simple JPQL query in a string, a named query or a criteria query.

Edit and Delete

You can now add functionality to edit and delete people.

To do this you should:

  1. Add two JSF views (e.g., person_delete.xhtml and person_edit.xhtml) (person_edit.xhtml should only edit the first name, last name and date of birth, not the list of contacts).
  2. Add buttons to the address book list to edit the individual views
  3. Add appropriate methods to your JSF backing bean
  4. Add appropriate methods to your EJB (e.g., update and delete)
  5. Have the backing bean call your EJB
  6. Have the EJB use the EntityManager to modify the database

Hints

The EntityManager should be used in the EJB and not in the JSF backing bean.

You will probably use the delete and merge functions of the EntityManager.

Reflect

In what ways is the stateless EJB that you have created (AddressBookBean) similar to a Data Access Object?

You have created a stateless session bean named AddressBookBean. This AddressBookBean is similar to a DAO and also replicates many of the functions of the EntityManager.

It would probably not be a good idea to directly use the EntityManager inside the presentation logic. While the EntityManager is very similar to a DAO, it is slightly too "low level" to be appropriate for use in the presentation logic. Using the EntityManager directly in the presentation logic would be poor design. This is because it creates dependencies between the presentation logic and the underlying persistence technology, and it would result in transactions and complex data queries occuring inside the persistence logic.

The AddressBookBean class that you created provides a higher-level, business-focused interface. It provides a more "coarse-grained" service to clients. Rather than using the complex and powerful EntityManager, it offers simpler methods that provide business-focused operations that are used by the presentation logic.

In fact, the AddressBookBean class that you created is an example of a common enterprise design pattern known as a Session Façade.

High level details of the design pattern may be found at the following address:
http://www.corej2eepatterns.com/SessionFacade.htm

NetBeans has an ability to automatically generate such classes for you.

Create Session Façades for Entities

We will use a NetBeans "wizard" to automatically generate Session Façades for all of the JPA Entities in the application.

  1. Right click on your Week9-ejb project and select "New"... "Other...".
  2. Choose the "Session Beans for Entity Classes" file type in the "Persistence" category
  3. Click on "Add All >>"
  4. Ensure that the package is au.edu.uts.aip.addressbook.domain.
  5. Click on Finish.

Three classes will be automatically generated:

  1. AbstractFacade
  2. PersonFacade
  3. ContactMethodFacade

You will note that PersonFacade and ContactMethodFacade both extend AbstractFacade. This means that all the methods in AbstractFacade also apply to both PersonFacade and ContactMethodFacade.

Use Session Façades

You can now modify your application to use the Façade(s) instead of the AddressBookBean class that you originally created.

Note that you can add additional methods to the session façades, as appropriate.