<--Back

Introduction

This first tutorial gives you an opportunity to work with:
  • setting up a persistent unit,
  • creating in-memory entities and
  • persisting those entities to a database.

You'll write the code such that the environment creates the database schema based on meta-information (annotations) in your entity classes.

Good luck!

JPA Tutorial 1: Background

In this first tutorial we are going to perform some basic inserts, removes and queries against a database.

JPA allows us to work with entity classes, which are denoted as such using the annotation @Entity or configured in an XML file (we'll call this persistence meta information). When we acquire the Entity Manager Factory using the Persistence class, the Entity Manager Factory finds and processes the persistence meta information.

To work with a database using JPA, we need an Entity Manager. Before we can do that, we need to create an Entity Manager Factory.
  • EntityManagerFactory.jpeg

  • PersistenceSequence.jpg

To acquire an Entity Manager Factory, we use the class javax.persistence.Persistence. It reads a file called persistence.xml in the META-INF directory. It then creates the named Entity Manager Factory, which processes persistence meta information stored in XML files or annotations (we only use annotations).

Creating an Entity Manager once we have the Entity Manager Factory is simple:
  • CreateEntityManager.jpeg

Once we have an Entity Manager, we can ask it to perform several operations such as persisting or removing an entity from the database or creating a query.

Term
Description
javax.persistence.Persistence
This is a class used as an entry point for using JPA. The primary method you'll use on this class is createEntityManagerFactory("someName") to retrieve an entity manager factory with the name "someName". This class requires a file called persistence.xml to be in the class path under a directory called META-INF.
EntityManagerFactory
An instance of this class provides a way to create entity managers. Entity Managers are not multi-thread safe so we need a way to create one per thread. This class provides that functionality. The Entity Manager Factory is the in-memory representation of a Persistence Unit.
EntityManager
An Entity Manager is the interface in your underlying storage mechanism. It provides methods for persisting, merging, removing, retrieving and querying objects. It is not multi-thread safe so we need one per thread. The Entity Manager also serves as a first level cache. It maintains changes and then attempts to optimize changes to the database by batching them up when the transaction completes.
persistence.xml
A required file that describes one or more persistence units. When you use the javax.persistence.Persistence class to look up an named Entity Manager Factory, the Persistence class looks for this file under the META-INF directory.
Persistence Unit
A Persistence Unit has a name and it describes database connection information either directly (if working in a JSE environment) or indirectly by referencing a JNDI-defined data source (if working in a managed/JEE environment). A Persistence Unit can also specify the classes(entities) it should or should not manage .
Persistence Meta Information
Information describing the configuration of entities and the database and the association between entity classes and the persistence units to which they relate. This is either through annotations added to classes or though XML files. Note that XML files take precedence over annotations.

JPA Initial Setup

This example requires Java 5 (JDK 1.5) or later and Eclipse 3.2 or later. This page gives you a link to all of the downloads you'll need to get to get started. While I might mention specific version numbers, it's a good bet that newer versions should work as well... of course that's not always the case.

Note: We need the jar file that contains javax.persistence and the various annotations associated with JPA. You can either download JEE or get it from somewhere else. For this series of tutorials, we eventually use the JBoss EJB3 Embeddable container, so we'll use that to avoid an extra 150+ meg download for one jar file.

Download Everything

First the basic stuff:
  1. Download JSE 5 (choose just JDK 5.0 (there will be an update after it))
  2. Download Eclipse

Next, the libraries:
  1. Download Hibernate 3.3.1 GA
  2. Download Hibernate Annotations 3.4.0 GA
  3. Download Hibernate Entity Manager 3.4.0.GA
  4. Download HSQL Database Engine 1.8.0.10
  5. Download SLF4J 1.5.8
  6. Download Open EJB 3.1.1

Setup the JDK & Eclipse

  1. Install the JSE 5.
  2. Extract the eclipse download somewhere. For all examples I use C:/eclipse.

Extract Jar Files

Extract each of the libraries to some location. In my case I extracted everything to C:/libs, so I have the following directories
  • C:/libs/hibernate-annotations-3.4.0.GA
  • C:/libs/hibernate-entitymanager-3.4.0.GA
  • C:/libs/hibernate-distribution-3.3.1.GA
  • C:/libs/hsqldb-1.9.0-beta3
  • C:/libs/openejb-3.1.1
  • C:/libs/slf4j-1.5.8


Eclipse Project Setup

Next we need to start eclipse and create a workspace.

Create Initial Project

  1. Start eclipse.
  2. When prompted, enter a directory for your workspace. I used C:\workspaces\JpaAndEjb3. To understand why I recommend not using a space in the name, read this sidebar.
  3. Close the Welcome window

User Library

We are going to define a user library, which is just a collection of jar files with a name. Once we create this, we can add it to our classpath with one command. This also makes setting up new projects in the same workspace a snap. We can also export workspace settings and import them into a new workspace.
  1. Pull down Window:Preferences
  2. Navigate to Java:Build Path:User Libraries
  3. Click on New
  4. Enter JPA_JSE for the name and click on OK

Now we need to add several jars to this list. For each of the following jars, do the following:
  1. Select JPA_JSE (after you add the first one, you'll have to go back and click the library, which seems to be a poor UI design)
  2. Click on Add JARs...
  3. Navigate to the jar file
  4. Select the jar file
  5. Click on Open
  6. Repeat at step one.

Here is a list of all the jar files you'll need to add (note the path's listed assume you extracted your jar files to C:/libs):
  • C:/libs/hibernate-distribution-3.3.1.GA/lib/required/antlr-2.7.6.jar
  • C:/libs/hibernate-distribution-3.3.1.GA/lib/required/commons-collections-3.1.jar
  • C:/libs/hibernate-distribution-3.3.1.GA/lib/required/dom4j-1.6.1.jar
  • C:/libs/hibernate-distribution-3.3.1.GA/lib/required/javassist-3.4.GA.jar
  • C:/libs/hibernate-distribution-3.3.1.GA/hibernate3.jar
  • C:/openejb-3.1.1/lib/javaee-api-5.0-2.jar
  • C:/openejb-3.1.1/lib/log4j-1.2.12.jar
  • C:/hibernate-entitymanager-3.4.0.GA/hibernate-entitymanager.jar
  • C:/hsqldb-1.9.0-beta3/hsqldb/lib/hsqldb.jar
  • C:/hibernate-annotations-3.4.0.GA/hibernate-annotations.jar
  • C:/hibernate-annotations-3.4.0.GA/lib/hibernate-commons-annotations.jar
  • C:/slf4j-1.5.8/slf4j-simple-1.5.8.jar
  • C:/slf4j-1.5.8/slf4j-api-1.5.8.jar

Create Java Project

Next we need to create a Java project. We'll keep the source separate from the bin directory:
  1. Pull down the File menu and select New:Project
  2. Select Java Project and click Next
  3. Enter a project name: JpaTutorial1, again read this sidebar to know why I did not use a space in the project name.
  4. Make sure "Create new project in workspace" is selected.
  5. Make sure the JRE selected is 1.5.x. If a 1.5 JRE does not show in the list, you can add it through Window->Preferences->JAVA->Installed JRE's.
  6. Select "Create separate source and output folders"
  7. Click "finish"

Create folders and packages

  1. Expand your project JpaTutorial1
  2. Select the src folder
  3. Right-click, select new:Folder
  4. Enter the name META-INF
  5. Click Finish
  6. Select the src folder again
  7. Right-click, select new:Package
  8. Enter the name entity
  9. Click on Finish
  10. Select the Tutoria1 project again
  11. Right-click, select new:Source Folder
  12. Enter the name test
  13. Click Finish
  14. Select the test folder
  15. Right-click, select new:Package
  16. Enter the name entity

Add Required Libraries

We now need to add two libraries. One will be the user-defined library we created above. The second will be JUnit 4.x.

  1. Edit the project properties. Select your project (e.g. JpaTutorial1) and either press alt-enter or right-click and select properties.
  2. Select Java Build Path
  3. Click on the Libraries tab
  4. Click on Add Library
  5. Select User Libraries and click Next
  6. Select JPA_JSE by clicking on the checkbox
  7. Click OK
  8. Click on Add Library again
  9. Click on JUnit
  10. Click Next
  11. In the pull-down list, select JUnit 4
  12. Click Finish
  13. Click OK


Sidebar

The JPA specification says that in a managed environment (read as running in the container), you do not need to list your entity classes in the persistence.xml (this is coming up). When you're using JPA in a JSE environment, this is not guaranteed. In all of these examples, we're using the hibernate implementation of the JEE Entity Manager. It provides the functionality of automatically registering all of your entities without your having to explicitly list them. However, if you happen to have a space in your class path, it appears to fail.

If you followed the instructions above, you'll have the following directory: C:/Workspaces/JpaAndEjb3/JpaTutorial1. Under that directory will be a bin directory where our compiled class files reside. Hibernate will look in this directory and find all classes that are entities (denoted with @Entity) and add those to our persistent unit. In the first version of this tutorial, I recommended the following name: C:/Workspaces/Jpa And Ejb3/Tutorial 1. When I ran the driver program, hibernate was unable to automatically find the bin directory, I assume this was because of the spaces in the name. I changed the name by removing all of the spaces and the problem went away.
 

Persistence Unit Configuration

We now need to create the Persistent Unit definition. Create a file called persistence.xml in the src/META-INF directory with the following contents:


persistence.xml

<persistence>
    <persistence-unit name="examplePersistenceUnit" 
                      transaction-type="RESOURCE_LOCAL">
        <properties>
            <property name="hibernate.show_sql" value="false" />
            <property name="hibernate.format_sql" value="false" />
 
            <property name="hibernate.connection.driver_class" 
                      value="org.hsqldb.jdbcDriver" />
            <property name="hibernate.connection.url" 
                      value="jdbc:hsqldb:mem:mem:aname" />
            <property name="hibernate.connection.username" value="sa" />
 
            <property name="hibernate.dialect" 
                      value="org.hibernate.dialect.HSQLDialect" />
            <property name="hibernate.hbm2ddl.auto" value="create" />
        </properties>
    </persistence-unit>
</persistence>

The Steps

  1. Expand your project (JpaTutorial1)
  2. Select the src/META-INF directory
  3. Right click and select new:File
  4. Enter persistence.xml for the name and press Finish (Note: all lowercase. It won't make a difference on Windows XP but it will on Unix.)
  5. Copy the contents (above) into the file and save it.

Verify This Works

  1. Select the test
  2. Right-click on entity and select new:Class
  3. Enter PersonTest and click Finish
  4. Enter the example code below:
package entity;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
 
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
 
public class PersonTest {
    private EntityManagerFactory emf;
 
    private EntityManager em;
 
    @Before
    public void initEmfAndEm() {
        BasicConfigurator.configure();
        Logger.getLogger("org").setLevel(Level.ERROR);
 
        emf = Persistence.createEntityManagerFactory("examplePersistenceUnit");
        em = emf.createEntityManager();
    }
 
    @After
    public void cleanup() {
        em.close();
    }
 
    @Test
    public void emptyTest() {
    }
}

  1. When you're finished and it all compiles, right-click within the source pane, select Run As:JUnit Test
  2. You should see all green
  3. If you do not, comment out the following line and review the console output
        Logger.getLogger("org").setLevel(Level.ERROR);

Create Your First Entity

For this example we'll use a "top-down" approach. This means we'll create a Plain Old Java Object (POJO) with some annotations to indicate how we want JPA to persist it. We're letting the EntityManager take care of creating the tables in the database for us.

Create a Simple Class


The following class contains everything you need to begin persisting it to a database:

Person.java
package entity;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
 
@Entity
public class Person {
    @Id
    @GeneratedValue
    private int id;
    private String firstName;
    private char middleInitial;
    private String lastName;
    private String streetAddress1;
    private String streetAddress2;
    private String city;
    private String state;
    private String zip;
 
    public Person() {
    }
 
    public Person(final String fn, final char mi, final String ln,
            final String sa1, final String sa2, final String city,
            final String state, final String zip) {
        setFirstName(fn);
        setMiddleInitial(mi);
        setLastName(ln);
        setStreetAddress1(sa1);
        setStreetAddress2(sa2);
        setCity(city);
        setState(state);
        setZip(zip);
    }
 
    public String getCity() {
        return city;
    }
 
    public void setCity(final String city) {
        this.city = city;
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(final String firstName) {
        this.firstName = firstName;
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(final int id) {
        this.id = id;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(final String lastName) {
        this.lastName = lastName;
    }
 
    public char getMiddleInitial() {
        return middleInitial;
    }
 
    public void setMiddleInitial(final char middleInitial) {
        this.middleInitial = middleInitial;
    }
 
    public String getState() {
        return state;
    }
 
    public void setState(final String state) {
        this.state = state;
    }
 
    public String getStreetAddress1() {
        return streetAddress1;
    }
 
    public void setStreetAddress1(final String streetAddress1) {
        this.streetAddress1 = streetAddress1;
    }
 
    public String getStreetAddress2() {
        return streetAddress2;
    }
 
    public void setStreetAddress2(final String streetAddress2) {
        this.streetAddress2 = streetAddress2;
    }
 
    public String getZip() {
        return zip;
    }
 
    public void setZip(final String zip) {
        this.zip = zip;
    }
}

Update persistence.xml

Note, for our configuration this step is optional.

If you use libraries provided exclusively by JBoss and Company, then you do not need to update your persistence.xml. If you are using another vendor or you want to make sure that your solution will work regardless of your persistence provider, add the following line to your persistence.xml:
        <class>entity.Person</class>

Your updated persistence.xml is now:
<persistence>
    <persistence-unit name="examplePersistenceUnit" 
                      transaction-type="RESOURCE_LOCAL">
        <class>entity.Person</class>
        <properties>
            <property name="hibernate.show_sql" value="false" />
            <property name="hibernate.format_sql" value="false" />
 
            <property name="hibernate.connection.driver_class" 
                      value="org.hsqldb.jdbcDriver" />
            <property name="hibernate.connection.url" 
                      value="jdbc:hsqldb:mem:mem:aname" />
            <property name="hibernate.connection.username" value="sa" />
 
            <property name="hibernate.dialect" 
                      value="org.hibernate.dialect.HSQLDialect" />
            <property name="hibernate.hbm2ddl.auto" value="create" />
        </properties>
    </persistence-unit>
</persistence>

Inserting and Querying

Now we need to update our unit test class, Person.java. We will have it insert two people, query and verify that the people we created are in the database:
PersonTest.java
package entity;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
 
import java.util.List;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
 
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
 
public class PersonTest {
    private final Person p1 = new Person("Brett", 'L', "Schuchert", "Street1",
            "Street2", "City", "State", "Zip");
    private final Person p2 = new Person("FirstName", 'K', "LastName",
            "Street1", "Street2", "City", "State", "Zip");
 
    private EntityManagerFactory emf;
    private EntityManager em;
 
    @Before
    public void initEmfAndEm() {
        BasicConfigurator.configure();
        Logger.getLogger("org").setLevel(Level.ERROR);
 
        emf = Persistence.createEntityManagerFactory("examplePersistenceUnit");
        em = emf.createEntityManager();
    }
 
    @After
    public void cleanup() {
        em.close();
    }
 
    @SuppressWarnings("unchecked")
    @Test
    public void insertAndRetrieve() {
        em.getTransaction().begin();
        em.persist(p1);
        em.persist(p2);
        em.getTransaction().commit();
 
        final List<Person> list = em.createQuery("select p from Person p")
                .getResultList();
 
        assertEquals(2, list.size());
        for (Person current : list) {
            final String firstName = current.getFirstName();
            assertTrue(firstName.equals("Brett")
                    || firstName.equals("FirstName"));
        }
    }
}
Re-run this test (the short-cut for this is Ctrl-Fll). Verify that everything is green.

Add an Embedded Entity

When we created Person we directly included address information into them. This is alright, but what if we want to use Address in another class? Let's introduce a new entity, Address, and make it embedded. This means its fields will end up as columns in the table of the entity that contains it.

First, we'll create Address:

Address.java
package entity;
 
import javax.persistence.Embeddable;
 
@Embeddable
public class Address {
    private String streetAddress1;
    private String streetAddress2;
    private String city;
    private String state;
    private String zip;
 
    public Address() {
    }
 
    public Address(final String sa1, final String sa2, final String city,
            final String state, final String zip) {
        setStreetAddress1(sa1);
        setStreetAddress2(sa2);
        setCity(city);
        setState(state);
        setZip(zip);
    }
 
    public final String getCity() {
        return city;
    }
 
    public final void setCity(final String city) {
        this.city = city;
    }
 
    public final String getState() {
        return state;
    }
 
    public final void setState(final String state) {
        this.state = state;
    }
 
    public final String getStreetAddress1() {
        return streetAddress1;
    }
 
    public final void setStreetAddress1(final String streetAddress1) {
        this.streetAddress1 = streetAddress1;
    }
 
    public final String getStreetAddress2() {
        return streetAddress2;
    }
 
    public final void setStreetAddress2(final String streetAddress2) {
        this.streetAddress2 = streetAddress2;
    }
 
    public final String getZip() {
        return zip;
    }
 
    public final void setZip(final String zip) {
        this.zip = zip;
    }
}

Next, we need to update Person (doing so will cause our unit test class to no longer compile):
Person.java
package entity;
 
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
 
@Entity
public class Person {
    @Id
    @GeneratedValue
    int id;
    private String firstName;
    private char middleInitial;
    private String lastName;
 
    @Embedded
    private Address address;
 
    public Person() {
    }
 
    public Person(final String fn, final char mi, final String ln,
            final Address address) {
        setFirstName(fn);
        setMiddleInitial(mi);
        setLastName(ln);
        setAddress(address);
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(final String firstName) {
        this.firstName = firstName;
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(final int id) {
        this.id = id;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(final String lastName) {
        this.lastName = lastName;
    }
 
    public char getMiddleInitial() {
        return middleInitial;
    }
 
    public void setMiddleInitial(final char middleInitial) {
        this.middleInitial = middleInitial;
    }
 
    public final Address getAddress() {
        return address;
    }
 
    public final void setAddress(final Address address) {
        this.address = address;
    }
}

Sure enough, if you review PersonTest.java, it no longer compiles. Before we go any further, let's update it to get it to compile and then verify that the unit tests still pass.

Replace the following two lines:
    private final Person p1 = new Person("Brett", 'L', "Schuchert", "Street1",
            "Street2", "City", "State", "Zip");
    private final Person p2 = new Person("FirstName", 'K', "LastName",
            "Street1", "Street2", "City", "State", "Zip");

with the following four lines

    private final Address a1 = new Address("A Rd.", "", "Dallas", "TX", "75001");
    private final Person p1 = new Person("Brett", 'L', "Schuchert", a1);
 
    private final Address a2 = new Address("B Rd.", "S2", "OkC", "OK", "73116");
    private final Person p2 = new Person("FirstName", 'K', "LastName", a2);

Rerun your tests (Ctrl-F11) and make sure everything is all green.

Next, we want to verify that the address we persist is in the database. Update the unit test method as follows:

PersonTest#insertAndRetrieve
    @SuppressWarnings("unchecked")
    @Test
    public void insertAndRetrieve() {
        em.getTransaction().begin();
        em.persist(p1);
        em.persist(p2);
        em.getTransaction().commit();
 
        final List<Person> list = em.createQuery("select p from Person p")
                .getResultList();
 
        assertEquals(2, list.size());
        for (Person current : list) {
            final String firstName = current.getFirstName();
            final String streetAddress1 = current.getAddress()
                    .getStreetAddress1();
 
            assertTrue(firstName.equals("Brett")
                    || firstName.equals("FirstName"));
            assertTrue(streetAddress1.equals("A Rd.")
                    || streetAddress1.equals("B Rd."));
        }
    }

Run your program and make sure it's all green.

Add an Entity with a One to Many Relationship

Now we'll make a company. In this first tutorial we're keeping things simple so we'll just create a Company that has a 1 to many relationship with People, who are its employees:


Company.java
package entity;
 
import java.util.ArrayList;
import java.util.Collection;
 
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
 
@Entity
public class Company {
    @Id
    @GeneratedValue
    int id;
    private String name;
    @Embedded
    private Address address;
    @OneToMany
    private Collection<Person> employees;
 
    public Company() {
    }
 
    public Company(final String name, final Address address,
            final Collection<Person> employees) {
        setName(name);
        setAddress(address);
        setEmployees(employees);
    }
 
    public Address getAddress() {
        return address;
    }
 
    public void setAddress(final Address address) {
        this.address = address;
    }
 
    public Collection<Person> getEmployees() {
        if (employees == null) {
            employees = new ArrayList<Person>();
        }
        return employees;
    }
 
    public void setEmployees(final Collection<Person> employees) {
        this.employees = employees;
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(final int id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(final String name) {
        this.name = name;
    }
 
    public void hire(final Person p) {
        getEmployees().add(p);
    }
 
    public void fire(final Person p) {
        getEmployees().remove(p);
    }
}

Factor out Common Test Code

We have some common initialization we can move up into a base since we are going to have two tests classes, PersonTest and CompanyTest:
TestBase.java
package entity;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
 
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
 
public class TestBase {
    protected EntityManagerFactory emf;
    protected EntityManager em;
 
    public TestBase() {
        super();
    }
 
    @Before
    public void initEmfAndEm() {
        BasicConfigurator.configure();
        Logger.getLogger("org").setLevel(Level.ERROR);
 
        emf = Persistence.createEntityManagerFactory("examplePersistenceUnit");
        em = emf.createEntityManager();
    }
 
    @After
    public void cleanup() {
        em.close();
    }
}

Update PersonTest.java to remove the two fields, emf and em and the initEmfAndEm() and cleanup() methods.
PersonTest.java
package entity;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
 
import java.util.List;
 
import org.junit.Test;
 
public class PersonTest extends TestBase {
    private final Address a1 = new Address("A Rd.", "", "Dallas", "TX", "75001");
    private final Person p1 = new Person("Brett", 'L', "Schuchert", a1);
 
    private final Address a2 = new Address("B Rd.", "S2", "OkC", "OK", "73116");
    private final Person p2 = new Person("FirstName", 'K', "LastName", a2);
 
    @SuppressWarnings("unchecked")
    @Test
    public void insertAndRetrieve() {
        em.getTransaction().begin();
        em.persist(p1);
        em.persist(p2);
        em.getTransaction().commit();
 
        final List<Person> list = em.createQuery("select p from Person p")
                .getResultList();
 
        assertEquals(2, list.size());
        for (Person current : list) {
            final String firstName = current.getFirstName();
            final String streetAddress1 = current.getAddress()
                    .getStreetAddress1();
 
            assertTrue(firstName.equals("Brett")
                    || firstName.equals("FirstName"));
            assertTrue(streetAddress1.equals("A Rd.")
                    || streetAddress1.equals("B Rd."));
        }
    }
}

Make sure everything is green before going on (rerun using Ctrl-F11).

Now we need to create a new CompanyTest class. Here's the first version:
package entity;
 
import static org.junit.Assert.assertEquals;
 
import org.junit.Test;
 
public class CompanyTest extends TestBase {
    @Test
    public void createCompany() {
        final Company c1 = new Company();
        c1.setName("The Company");
        c1.setAddress(new Address("D Rd.", "", "Paris", "TX", "77382"));
 
        em.getTransaction().begin();
        em.persist(c1);
        em.getTransaction().commit();
 
        final Company foundCompany = (Company) em.createQuery(
                "select c from Company c where c.name=?1").setParameter(1,
                "The Company").getSingleResult();
 
        assertEquals("D Rd.", foundCompany.getAddress().getStreetAddress1());
        // Note, we do not need an assert. Why? the method getSingleResult()
        // will throw an exception if there is not exactly one
        // object found. We'll research that in the second JPA tutorial.
    }
}
Run this unit test and make sure it is all green before going on (right-click in the source pane, select Run As:JUnit Test).

If you'd like to run all of your tests, right-click on the test folder, select Run As:JUnit Test and eclipse will execute all of your tests classes' test methods.

Hire some people

We need to create some people and add them to the company. The PersonTest class already has some people. Rather than re-creating new people, let's update PersonTest to make those fields available. Update the a1, p1, a2, and p2 fields as follows:
    public static List<Person> generatePersonObjects() {
        final List<Person> people = new ArrayList<Person>();
        final Address a1 = new Address("A Rd.", "", "Dallas", "TX", "75001");
        final Person p1 = new Person("Brett", 'L', "Schuchert", a1);
 
        final Address a2 = new Address("B Rd.", "S2", "OkC", "OK", "73116");
        final Person p2 = new Person("FirstName", 'K', "LastName", a2);
 
        people.add(p1);
        people.add(p2);
 
        return people;
    }

You will also need to update the beginning of the method insertAndRetrieve from:
        em.getTransaction().begin();
        em.persist(p1);
        em.persist(p2);
        em.getTransaction().commit();
 

to:

        final List<Person> people = generatePersonObjects();
 
        em.getTransaction().begin();
        for (Person p : people) {
            em.persist(p);
        }
        em.getTransaction().commit();

Now we'll add a new test into CompanyTest to verify that we can hire people:
    @SuppressWarnings("unchecked")
    @Test
    public void createCompanyAndHirePeople() {
        final Company c1 = new Company();
        c1.setName("The Company");
        c1.setAddress(new Address("D Rd.", "", "Paris", "TX", "77382"));
 
        List<Person> people = PersonTest.generatePersonObjects();
        for (Person p : people) {
            c1.hire(p);
        }
 
        em.getTransaction().begin();
        for (Person p : people) {
            em.persist(p);
        }
        em.persist(c1);
        em.getTransaction().commit();
 
        final List<Person> list = em.createQuery("select p from Person p")
                .getResultList();
        assertEquals(2, list.size());
 
        final Company foundCompany = (Company) em.createQuery(
                "select c from Company c where c.name=?1").setParameter(1,
                "The Company").getSingleResult();
        assertEquals(2, foundCompany.getEmployees().size());
    }

Update persistence.xml

Again, given our environment, this step is optional.

persistence.xml
<persistence>
    <persistence-unit name="examplePersistenceUnit" 
                      transaction-type="RESOURCE_LOCAL">
        <class>entity.Person</class>
        <class>entity.Company</class>
        <properties>
            <property name="hibernate.show_sql" value="false" />
            <property name="hibernate.format_sql" value="false" />
 
            <property name="hibernate.connection.driver_class" 
                      value="org.hsqldb.jdbcDriver" />
            <property name="hibernate.connection.url" 
                      value="jdbc:hsqldb:mem:mem:aname" />
            <property name="hibernate.connection.username" 
                      value="sa" />
 
            <property name="hibernate.dialect" 
                      value="org.hibernate.dialect.HSQLDialect" />
            <property name="hibernate.hbm2ddl.auto" 
                      value="create" />
        </properties>
    </persistence-unit>
</persistence>

Make sure everything compiles and runs green.

Make a Relationship Bi-Directional

Now we're going to make sure the Person knows the Company for which it works. This is the "one" side of a one to many relationship. We need to explicitly set this value and map it. We also need to update the Company @OneToMany relationship so that the Entity Manager knows it is a bi-directional relationship rather than just two unidirectional relationships.

First we need to update Person:
Person.java
public class Person {
    // ...
 
    /**
     * This relationship is optional. This means the database will allow this
     * relationship to be null. It turns out that true is the default value so
     * we only specify it to document its existence.
     */
    @ManyToOne(optional = true)
    private Company job;
 
    public Company getJob() {
        return job;
    }
 
    public void setJob(Company job) {
        this.job = job;
    }
 
    // ...
}

Next, we'll change Company to maintain both sides of the relationship:
public class Company {
    /**
     * Adding mappedBy lets the entity manager know you mean for this
     * relationship to be bi-directional rather that two unidirectional
     * relationships.
     */
    @OneToMany(mappedBy = "job")
    private Collection<Person> employees;
 
    // update this method
    public void setEmployees(final Collection<Person> newStaff) {
        // fire everybody
        final Collection<Person> clone = new ArrayList<Person>(employees);
 
        for (Person p : clone) {
            fire(p);
        }
 
        for (Person p : newStaff) {
            hire(p);
        }
    }
 
    // update this method
    public void hire(final Person p) {
        getEmployees().add(p);
        p.setJob(this);
    }
 
    // update this method
    public void fire(final Person p) {
        getEmployees().remove(p);
        p.setJob(null);
    }
}

Before going any further, make sure all of your tests still run green.

We are now adding and removing Person objects from collections. To make this work, we need to add an equals() method and a hashCode() method to the Person class:
    public boolean equals(final Object rhs) {
        if (rhs instanceof Person) {
            final Person other = (Person) rhs;
            return other.getLastName().equals(getLastName())
                    && other.getFirstName().equals(getFirstName())
                    && other.getMiddleInitial() == getMiddleInitial();
        }
 
        return false;
    }
 
    public int hashCode() {
        return getLastName().hashCode() * getFirstName().hashCode()
                * getMiddleInitial();
    }

Finally, we'll update CompanyTest in several stages:

First, add a utility method to retrieve companies by name:
    private Company findCompanyNamed(final EntityManager em, String name) {
        return (Company) em.createQuery(
                "select c from Company c where c.name=?1")
                .setParameter(1, name).getSingleResult();
    }

Add another support method to create a company and hire a few people:
    private Company createCompanyWithTwoEmployees() {
        final Company c1 = new Company();
        c1.setName("The Company");
        c1.setAddress(new Address("D Rd.", "", "Paris", "TX", "77382"));
 
        final List<Person> people = PersonTest.generatePersonObjects();
        for (Person p : people) {
            c1.hire(p);
        }
 
        em.getTransaction().begin();
        for (Person p : people) {
            em.persist(p);
        }
        em.persist(c1);
        em.getTransaction().commit();
 
        return c1;
    }

The method createCompany used to directly lookup a company by name. Update the test method to use this private method by changing this line:
        final Company foundCompany = (Company) em.createQuery(
                "select c from Company c where c.name=?1").setParameter(1,
                "The Company").getSingleResult();

to:
        final Company foundCompany = findCompanyNamed(em, "The Company");

Update the method createCompanyAndHirePeopl by using the support method createCompanyWithTwoEmployees():
    @SuppressWarnings("unchecked")
    @Test
    public void createCompanyAndHirePeople() {
        createCompanyWithTwoEmployees();
 
        final List<Person> list = em.createQuery("select p from Person p")
                .getResultList();
        assertEquals(2, list.size());
 
        final Company foundCompany = (Company) em.createQuery(
                "select c from Company c where c.name=?1").setParameter(1,
                "The Company").getSingleResult();
        assertEquals(2, foundCompany.getEmployees().size());
    }

Finally, add an additional unit test to hire and fire people:
    @Test
    public void hireAndFire() {
        final Company c1 = createCompanyWithTwoEmployees();
        final List<Person> people = PersonTest.generatePersonObjects();
 
        em.getTransaction().begin();
        for (Person p : people) {
            c1.fire(p);
        }
        em.persist(c1);
        em.getTransaction().commit();
 
        final Company foundCompany = findCompanyNamed(em, "The Company");
        assertEquals(0, foundCompany.getEmployees().size());
    }

Make sure everything compiles and is green.

Exercises

Questions

  • Describe what the @Entity annotation means to a class.
  • Describe what the @Id annotation means to a class.
  • Describe what the @GeneratedValue means to a class
  • Describe the difference between @Embeddable and @Embedded
  • In the previous section we mentioned using mappedBy to let the container know this was a bi-directional relationship instead of a unidirectional relationship. What does this even mean? Draw an instance diagram that would explain this difference.

New Class

Right now the relationship between Person and Company is direct. Let's make that a little less direct. The Person should now have a Job with a title, salary and employeeId. The company still has employees as before. This picture describes the before and after relationships.
Tutorial1Assignment1.gif

In order to refactor to the [above] "after" picture consider some of the following advice:

Create Job
Create a class, Job, with the following attributes:
  • int id
  • String title
  • String Salary
  • String employeeNumber
  • Company company
  • Person person (added - fix diagram )

Update Employee
The Person class should no longer have a Company attribute. Instead it has a Job attribute.

Update Company
Update Company.hire(). Change the signature to take a person, title, salary. It will then create the job, generate an employeeId, and set up all the relationships and return the job.

Update the Company.fire() as necessary.

Advanced: Person can have multiple jobs

Make the relationship between Person and Job 1 to many. Now fix everything to make it work.

Advanced: First cut at a Data Access Object

If you look at your various unit tests, there are several places were they perform direct queries, inserts and removes. Move all of the code behind one or more Data Access Objects. Update your unit tests to use the Dao's.

Entire Source Base

Address.java
package entity;
 
import javax.persistence.Embeddable;
 
@Embeddable
public class Address {
    private String streetAddress1;
    private String streetAddress2;
    private String city;
    private String state;
    private String zip;
 
    public Address() {
    }
 
    public Address(final String sa1, final String sa2, final String city,
            final String state, final String zip) {
        setStreetAddress1(sa1);
        setStreetAddress2(sa2);
        setCity(city);
        setState(state);
        setZip(zip);
    }
 
    public final String getCity() {
        return city;
    }
 
    public final void setCity(final String city) {
        this.city = city;
    }
 
    public final String getState() {
        return state;
    }
 
    public final void setState(final String state) {
        this.state = state;
    }
 
    public final String getStreetAddress1() {
        return streetAddress1;
    }
 
    public final void setStreetAddress1(final String streetAddress1) {
        this.streetAddress1 = streetAddress1;
    }
 
    public final String getStreetAddress2() {
        return streetAddress2;
    }
 
    public final void setStreetAddress2(final String streetAddress2) {
        this.streetAddress2 = streetAddress2;
    }
 
    public final String getZip() {
        return zip;
    }
 
    public final void setZip(final String zip) {
        this.zip = zip;
    }
}

Person.java
package entity;
 
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
 
@Entity
public class Person {
    @Id
    @GeneratedValue
    int id;
    private String firstName;
    private char middleInitial;
    private String lastName;
    @ManyToOne(optional = true)
    private Company job;
 
    @Embedded
    private Address address;
 
    public Person() {
    }
 
    public Person(final String fn, final char mi, final String ln,
            final Address address) {
        setFirstName(fn);
        setMiddleInitial(mi);
        setLastName(ln);
        setAddress(address);
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(final String firstName) {
        this.firstName = firstName;
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(final int id) {
        this.id = id;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(final String lastName) {
        this.lastName = lastName;
    }
 
    public char getMiddleInitial() {
        return middleInitial;
    }
 
    public void setMiddleInitial(final char middleInitial) {
        this.middleInitial = middleInitial;
    }
 
    public final Address getAddress() {
        return address;
    }
 
    public final void setAddress(final Address address) {
        this.address = address;
    }
 
    public Company getJob() {
        return job;
    }
 
    public void setJob(Company job) {
        this.job = job;
    }
 
    public boolean equals(final Object rhs) {
        if (rhs instanceof Person) {
            final Person other = (Person) rhs;
            return other.getLastName().equals(getLastName())
                    && other.getFirstName().equals(getFirstName())
                    && other.getMiddleInitial() == getMiddleInitial();
        }
 
        return false;
    }
 
    public int hashCode() {
        return getLastName().hashCode() * getFirstName().hashCode()
                * getMiddleInitial();
    }
}

Company.java
package entity;
 
import java.util.ArrayList;
import java.util.Collection;
 
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
 
@Entity
public class Company {
    @Id
    @GeneratedValue
    int id;
    private String name;
    @Embedded
    private Address address;
    @OneToMany(mappedBy = "job")
    private Collection<Person> employees;
 
    public Company() {
    }
 
    public Company(final String name, final Address address,
            final Collection<Person> employees) {
        setName(name);
        setAddress(address);
        setEmployees(employees);
    }
 
    public Address getAddress() {
        return address;
    }
 
    public void setAddress(final Address address) {
        this.address = address;
    }
 
    public Collection<Person> getEmployees() {
        if (employees == null) {
            employees = new ArrayList<Person>();
        }
        return employees;
    }
 
    public void setEmployees(final Collection<Person> newStaff) {
        // fire everybody
        final Collection<Person> clone = new ArrayList<Person>(employees);
 
        for (Person p : clone) {
            fire(p);
        }
 
        for (Person p : newStaff) {
            hire(p);
        }
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(final int id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(final String name) {
        this.name = name;
    }
 
    public void hire(final Person p) {
        getEmployees().add(p);
        p.setJob(this);
    }
 
    public void fire(final Person p) {
        getEmployees().remove(p);
        p.setJob(null);
    }
}

TestBase.java
package entity;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
 
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
 
public class TestBase {
    protected EntityManagerFactory emf;
    protected EntityManager em;
 
    public TestBase() {
        super();
    }
 
    @Before
    public void initEmfAndEm() {
        BasicConfigurator.configure();
        Logger.getLogger("org").setLevel(Level.ERROR);
 
        emf = Persistence.createEntityManagerFactory("examplePersistenceUnit");
        em = emf.createEntityManager();
    }
 
    @After
    public void cleanup() {
        em.close();
    }
}

CompanyTest.java
package entity;
 
import static org.junit.Assert.assertEquals;
 
import java.util.List;
 
import javax.persistence.EntityManager;
 
import org.junit.Test;
 
public class CompanyTest extends TestBase {
    private Company findCompanyNamed(final EntityManager em, String name) {
        return (Company) em.createQuery(
                "select c from Company c where c.name=?1")
                .setParameter(1, name).getSingleResult();
    }
 
    @Test
    public void createCompany() {
        final Company c1 = new Company();
        c1.setName("The Company");
        c1.setAddress(new Address("D Rd.", "", "Paris", "TX", "77382"));
 
        em.getTransaction().begin();
        em.persist(c1);
        em.getTransaction().commit();
 
        final Company foundCompany = findCompanyNamed(em, "The Company");
 
        assertEquals("D Rd.", foundCompany.getAddress().getStreetAddress1());
        // Note, we do not need an assert. Why? the method getSingleResult()
        // will throw an exception if there is not exactly one
        // object found. We'll research that in the second JPA tutorial.
    }
 
    private Company createCompanyWithTwoEmployees() {
        final Company c1 = new Company();
        c1.setName("The Company");
        c1.setAddress(new Address("D Rd.", "", "Paris", "TX", "77382"));
 
        final List<Person> people = PersonTest.generatePersonObjects();
        for (Person p : people) {
            c1.hire(p);
        }
 
        em.getTransaction().begin();
        for (Person p : people) {
            em.persist(p);
        }
        em.persist(c1);
        em.getTransaction().commit();
 
        return c1;
    }
 
    @SuppressWarnings("unchecked")
    @Test
    public void createCompanyAndHirePeople() {
        createCompanyWithTwoEmployees();
 
        final List<Person> list = em.createQuery("select p from Person p")
                .getResultList();
        assertEquals(2, list.size());
 
        final Company foundCompany = (Company) em.createQuery(
                "select c from Company c where c.name=?1").setParameter(1,
                "The Company").getSingleResult();
        assertEquals(2, foundCompany.getEmployees().size());
    }
 
    @Test
    public void hireAndFire() {
        final Company c1 = createCompanyWithTwoEmployees();
        final List<Person> people = PersonTest.generatePersonObjects();
 
        em.getTransaction().begin();
        for (Person p : people) {
            c1.fire(p);
        }
        em.persist(c1);
        em.getTransaction().commit();
 
        final Company foundCompany = findCompanyNamed(em, "The Company");
        assertEquals(0, foundCompany.getEmployees().size());
    }
}

PersonTest.java
package entity;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
 
import java.util.ArrayList;
import java.util.List;
 
import org.junit.Test;
 
public class PersonTest extends TestBase {
    public static List<Person> generatePersonObjects() {
        final List<Person> people = new ArrayList<Person>();
        final Address a1 = new Address("A Rd.", "", "Dallas", "TX", "75001");
        final Person p1 = new Person("Brett", 'L', "Schuchert", a1);
 
        final Address a2 = new Address("B Rd.", "S2", "OkC", "OK", "73116");
        final Person p2 = new Person("FirstName", 'K', "LastName", a2);
 
        people.add(p1);
        people.add(p2);
 
        return people;
    }
 
    @SuppressWarnings("unchecked")
    @Test
    public void insertAndRetrieve() {
        final List<Person> people = generatePersonObjects();
 
        em.getTransaction().begin();
        for (Person p : people) {
            em.persist(p);
        }
        em.getTransaction().commit();
 
        final List<Person> list = em.createQuery("select p from Person p")
                .getResultList();
 
        assertEquals(2, list.size());
        for (Person current : list) {
            final String firstName = current.getFirstName();
            final String streetAddress1 = current.getAddress()
                    .getStreetAddress1();
 
            assertTrue(firstName.equals("Brett")
                    || firstName.equals("FirstName"));
            assertTrue(streetAddress1.equals("A Rd.")
                    || streetAddress1.equals("B Rd."));
        }
    }
}

FAQ

JPA Tutorial 1 - FAQ

* What's the entity Manager? Object cache which persists entities. Manages any objects with the @Entity annotation. Allows starting/stopping transactions, crud'ing database. "Your facade to working with the database"
  • Is hibernate using an in-memory database? Hibernate is one implementation of JPA. It connects to the database through a jdbc url. The database we use in the early exercises is Hypersonic SQL.
  • When do you need a default constructor? Spec says you always need it. If there are no other constructors, java provides the default constructor. If you add a single constructor (w/parms), keep in mind the default constructor is removed by java.
  • Annotations? Annotations are type-safe meta-data about a class. They can target fields/methods/classes. Reflection allows you to query the state of any annotation. Annotations have 'lifetimes' (compile-time, load-time, run-time).
  • Compared to toplink: How are isolation levels handled? What kind of caching concerns do we have to worry about? There will be similiar challenges in tuning.
  • JEE source? Download from JBoss (svn) or get from Brett.
  • Where does @Id go? (method or field?) If it's on an attribute, JPA will only look for annotations on fields, and use reflection to access fields (without getter's and setter's). If it's on a method (getId()), JPA will only look for annotations on methods, and will use getter's and setter's to access fields.
  • Is there a reason @id is not private? Brett forgets things as he gets older. (It should be private)
  • When creating the job class, do we need equals() and hashcode()? not yet
  • In Company.java there is a lazy-initialization in getEmployees() . Why? When you retrieve from the db, hibernate creates an object with newInstance (reflection). Lazily initializing the employees collections could decrease unnecessary garbase collection when retrieving large result sets. This is just a consideration (not always the 'best' way).
  • What's with the equals() method on Person? You don't want an equals() who's value changes over time. (pre-insert, the id might be null, post-insert, it would have a value)
  • How many times does it go to the database when you have a one-to-many and you retrieve the 'one' side. By default, once for the root ('one') object, and then one or more times (up to the container's discretion) to retrieve the 'many' side -- as you iterate through the collection. You can override with fetch=FetchType.EAGER (to fetch with a join and retrieve everything up front). See the actual sql with hibernate.show_sql=true.
  • Can you turn off the 'caching' part of JPA? You can define transactional boundries to limit the scope of the cache. Look up 'report queries' for some examples.
  • How do you know what you set 'mappedBy' to? Use the name of the field that defines the relationship
  • What happens if you manipulate your objects outside of a transaction? In a JSE environment, objects are still managed by the EntityManager. So if you make a change to an object, then later start a transaction and commit it, the changes made outside of the transaction are also sent to the database. If, on the other hand, you close the EntityManager or flush it, the changes are lost.

Brett: What have you learned in this tutorial?

Class responses:
//
  • Eclipse is good
  • JPA hides a lot of JDBC complexity.
  • How to define relationships of entities
  • Using entity notations
//

Responses to questions in the exercises

  • What does @Entity do?
//
  • defines a class as being an entity bean.
  • Tells container what behavior to add through bytecode instrumentation or dynamic proxies
//
  • What does the @Id do ?
//
  • defines primary key
  • required for entities
  • only @Id fields can be passed into the em.find(class, id) method
//
  • What does the 'generatedValue' do ?
//
  • automatically assigns id based on the defined strategy
//
  • What's embeddable vs. embedded?
//
  • Defines how the data is stored in the db. (serialization vs fields being persisted to columns)
//


Day 1 Review

What we learned today . . .

  • JUnit
  • many-to-one & vice versa (bi-directional)
  • Annotations & JUnit
  • Eclipse
  • Entity Managers
  • hibernate
  • refactoring
  • eclipse shortcuts, embeddable stuff, understanding the relationships
  • eclipse shortcuts (!)
  • identifying annotation and attributes/properties
  • static imports
  • persisting all the entities (without using cascade attributes)

feedback so far on the class format

  • "better than just hearing somebody talk"
  • hands-on is good, maybe should have an overview at the beginning though. "pedagogical"

What we'll do tommorrow

  • more tdd style approach
  • tutorial 3 (big project)
  • polymorphic queries

Links to the individual pages:
JPA Tutorial 1 - Background
JPA Tutorial 1 - Initial Setup
JPA Tutorial 1 - Eclipse Project Setup
JPA Tutorial 1 - Persistence Unit
JPA Tutorial 1 - First Entity
JPA Tutorial 1 - Embedded Entity
JPA Tutorial 1 - Entity with One to Many Relationship
JPA Tutorial 1 - Make Relationship Bi-directional
JPA Tutorial 1 - Exercises
JPA Tutorial 1 - Entire Source Base
JPA Tutorial 1 - FAQ

<--Back