Skip to content

4. JPA : An Overview

We aim to introduce JPA (Java Persistence API) with a few examples. JPA is covered in the course:

4.1. The Role of JPA in a Layered Architecture

Readers are encouraged to review the beginning of this document (paragraph 2), which explains the role of the JPA layer in a layered architecture. The JPA layer is part of the data access layers:

The [DAO] layer interacts with the JPA specification. Regardless of the product that implements it, the interface of the JPA layer presented to the [DAO] layer remains the same. Below, we present a few examples from [ref1] that will help us build our own JPA layer.

4.2. JPA - Examples

4.2.1. Example 1 - Object representation of a single table

4.2.1.1. The [person] table

Consider a database with a single [person] table whose role is to store some information about individuals:

 
ID
primary key of the table
VERSION
version of the row in the table. Each time the person is modified, their version number is incremented.
NAME
person's last name
FIRST_NAME
first name
DATE OF BIRTH
their date of birth
MARRIED
integer 0 (unmarried) or 1 (married)
NBENFANTS
number of children

4.2.1.2. The [Person] entity

We are in the following runtime environment:

The JPA layer [5] must bridge the relational world of the database [7] and the object world [4] manipulated by Java programs [3]. This bridge is established through configuration, and there are two ways to do this:

  1. using XML files. This was virtually the only way to do it until the advent of JDK 1.5
  2. using Java annotations since JDK 1.5

In this document, we will use exclusively the second method.

The [Person] object representing the [person] table presented earlier could be as follows:


...

@SuppressWarnings("unused")
@Entity
@Table(name="Person")
public class Person implements Serializable{

    @Id
    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Column(name = "VERSION", nullable = false)
    @Version
    private int version;

    @Column(name = "NAME", length = 30, nullable = false, unique = true)
    private String lastName;

    @Column(name = "FIRST_NAME", length = 30, nullable = false)
    private String firstName;

    @Column(name = "DATENAISSANCE", nullable = false)
    @Temporal(TemporalType.DATE)
    private Date birthdate;

    @Column(name = "MARRIED", nullable = false)
    private boolean married;

    @Column(name = "NUMBEROFCHILDREN", nullable = false)
    private int numberOfChildren;

    // constructors
    public Person() {
    }

    public Person(String lastName, String firstName, Date birthDate, boolean married,
            int numberOfChildren) {
        setLastName(lastName);
        setFirstName(firstName);
        setBirthDate(birthDate);
        setMarried(married);
        setNumberOfChildren(numberOfChildren);
    }

    // toString
    public String toString() {
...
    }

    // getters and setters
...
}

Configuration is performed using Java annotations (@Annotation). Java annotations are either processed by the compiler or by specialized tools at runtime. Apart from the annotation on line 3 intended for the compiler, all annotations here are intended for the JPA implementation being used, Hibernate or Toplink. They will therefore be processed at runtime. In the absence of tools capable of interpreting them, these annotations are ignored. Thus, the [Person] class above could be used in a non-JPA context.

There are two distinct cases for using JPA annotations in a class C associated with a table T:

  1. the table T already exists: the JPA annotations must then replicate the existing structure (column names and definitions, integrity constraints, foreign keys, primary keys, etc.)
  2. Table T does not exist and will be created based on the annotations found in class C.

Case 2 is the easiest to handle. Using JPA annotations, we specify the structure of the table T we want. Case 1 is often more complex. Table T may have been created a long time ago outside of any JPA context. Its structure may therefore be ill-suited to JPA’s relational-to-object bridge. To simplify matters, we’ll focus on Case 2, where the table T associated with class C will be created based on the JPA annotations in class C.

Let’s examine the JPA annotations of the [Person] class:

  • line 4: the @Entity annotation is the first essential annotation. It is placed before the line declaring the class and indicates that the class in question must be managed by the JPA persistence layer. Without this annotation, all other JPA annotations would be ignored.
  • line 5: the @Table annotation designates the database table that the class represents. Its main argument is name, which specifies the table’s name. Without this argument, the table will be named after the class, in this case [Person]. In our example, the @Table annotation is therefore unnecessary.
  • Line 8: The @Id annotation is used to designate the field in the class that corresponds to the table’s primary key. This annotation is mandatory. Here, it indicates that the id field on line 11 corresponds to the table’s primary key.
  • Line 9: The @Column annotation is used to link a class field to the table column that the field represents. The name attribute specifies the name of the column in the table. If this attribute is omitted, the column is given the same name as the field. In our example, the name argument was therefore optional. The nullable=false argument specifies that the column associated with the field cannot have a NULL value and that the field must therefore have a value.
  • Line 10: The @GeneratedValue annotation specifies how the primary key is generated when it is automatically generated by the DBMS. This will be the case in all our examples. It is not mandatory. Thus, our Person could have a student ID that serves as the primary key and is not generated by the DBMS but set by the application. In this case, the @GeneratedValue annotation would be omitted. The strategy argument specifies how the primary key is generated when generated by the DBMS. Not all DBMSs use the same technique for generating primary key values. For example:
Firebird
uses a value generator called before each insertion
SQL Server
the primary key field is defined as having the Identity type. The result is similar to Firebird’s value generator, except that the key value is not known until after the row is inserted.
Oracle
uses an object called SEQUENCE, which again acts as a value generator

The JPA layer must generate different SQL statements depending on the DBMS to create the value generator. It is configured to specify the type of DBMS it must handle. As a result, it can determine the standard strategy for generating primary key values for that DBMS. The argument strategy = GenerationType.*****AUTO* instructs the JPA layer to use this standard strategy. This technique has worked in all examples in this document for the seven DBMSs used.

  • Line 14: The @Version annotation designates the field used to manage concurrent access to the same row in the table.

To understand this issue of concurrent access to the same row in the [person] table, let’s assume a web application allows a person’s information to be updated and consider the following scenario:

At time T1, user U1 begins editing a person P. At this moment, the number of children is 0. He changes this number to 1, but before he submits his changes, user U2 begins editing the same person P. Since U1 has not yet submitted his changes, U2 sees the number of children as 0 on his screen. U2 changes the name of person P to uppercase. Then U1 and U2 save their changes in that order. U2’s change will take precedence: in the database, the name will be in uppercase and the number of children will remain at zero, even though U1 believes they changed it to 1.

The concept of a person’s version helps us solve this problem. Let’s revisit the same use case:

At time T1, a user U1 begins editing a person P. At this point, the number of children is 0 and the version is V1. They change the number of children to 1, but before they commit their change, a user U2 begins editing the same person P. Since U1 has not yet committed their change, U2 sees the number of children as 0 and the version as V1. U2 changes the name of person P to uppercase. Then U1 and U2 commit their changes in that order. Before committing a change, we verify that the user modifying person P holds the same version as the currently saved version of person P. This will be the case for user U1. Their change is therefore accepted, and we then change the version of the modified person from V1 to V2 to indicate that the person has undergone a change. When validating U2’s modification, we will notice that U2 has version V1 of person P, whereas the current version is V2. We can then inform user U2 that someone else acted before them and that they must start with the new version of person P. They will do so, retrieve a version V2 of person P who now has a child, capitalize the name, and validate. Their modification will be accepted if the registered person P is still version V2. Ultimately, the modifications made by U1 and U2 will be taken into account, whereas in the use case without versions, one of the modifications would have been lost.

The [DAO] layer of the client application can manage the version of the [Person] class itself. Every time an object P is modified, the version of that object will be incremented by 1 in the table. The @Version annotation allows this management to be transferred to the JPA layer. The field in question does not need to be named "version" as in the example. It can have any name.

The fields corresponding to the @Id and @Version annotations are present for persistence purposes. They would not be needed if the [Person] class did not need to be persisted. We can see, therefore, that an object is represented differently depending on whether or not it needs to be persisted.

  • Line 17: Once again, the @Column annotation provides information about the column in the [person] table associated with the name field of the Person class. Here we find two new arguments:
    • unique=true indicates that a person’s name must be unique. This will result in the addition of a uniqueness constraint on the NAME column of the [person] table in the database.
    • length=30 sets the number of characters in the NAME column to 30. This means that the type of this column will be VARCHAR(30).
  • Line 24: The @Temporal annotation is used to specify the SQL type for a date/time column or field. The type TemporalType.DATE denotes a date without an associated time. The other possible types are TemporalType.TIME for encoding a time and TemporalType.TIMESTAMP for encoding a date and time.

Let’s now comment on the rest of the code in the [Person] class:

  • Line 6: The class implements the Serializable interface. Serialization of an object involves converting it into a sequence of bits. Deserialization is the reverse operation. Serialization/deserialization is particularly used in client/server applications where objects are exchanged over the network. Client or server applications are unaware of this operation, which is performed transparently by the JVMs. For this to be possible, however, the classes of the exchanged objects must be "tagged" with the Serializable keyword.
  • Line 37: a constructor for the class. Note that the id and version fields are not included among the parameters. This is because these two fields are managed by the JPA layer and not by the application.
  • Lines 51 and beyond: the get and set methods for each of the class’s fields. Note that JPA annotations can be placed on the fields’ get methods instead of on the fields themselves. The placement of the annotations indicates the mode JPA should use to access the fields:
    • if the annotations are placed at the field level, JPA will access the fields directly to read or write them
    • if the annotations are placed at the get level, JPA will access the fields via the get/set methods to read or write them

The position of the @Id annotation determines the placement of JPA annotations in a class. When placed at the field level, it indicates direct access to the fields; when placed at the get level, it indicates access to the fields via the get and set methods. The other annotations must then be placed in the same manner as the @Id annotation.

4.2.2. Configuring the JPA Layer

Tests of the JPA layer can be performed using the following architecture:

  • in [7]: the database that will be generated from the annotations of the [Person] entity as well as additional configurations made in a file called [persistence.xml]
  • in [5, 6]: a JPA layer implemented by Hibernate
  • in [4]: the [Person] entity
  • in [3]: a console-based test program

The JPA layer is configured via the [META-INF/persistence.xml] file:

At runtime, the [META-INF/persistence.xml] file is searched for in the application’s classpath.

Let’s examine the JPA layer configuration in our project’s [persistence.xml] file:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
        <!--  provider -->
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <properties>
            <!-- Persistent classes -->
            <property name="hibernate.archive.autodetection" value="class, hbm" />
            <!-- SQL logs
                <property name="hibernate.show_sql" value="true"/>
                <property name="hibernate.format_sql" value="true"/>
                <property name="use_sql_comments" value="true"/>
            -->
            <!-- JDBC connection -->
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
            <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/jpa" />
            <property name="hibernate.connection.username" value="jpa" />
            <property name="hibernate.connection.password" value="jpa" />
            <!--  automatic schema creation -->
            <property name="hibernate.hbm2ddl.auto" value="create" />
            <!-- Dialect -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
            <!--  c3p0 DataSource properties -->
            <property name="hibernate.c3p0.min_size" value="5" />
            <property name="hibernate.c3p0.max_size" value="20" />
            <property name="hibernate.c3p0.timeout" value="300" />
            <property name="hibernate.c3p0.max_statements" value="50" />
            <property name="hibernate.c3p0.idle_test_period" value="3000" />
        </properties>
    </persistence-unit>
</persistence>

To understand this configuration, we need to revisit the data access architecture of our application:

  • the [persistence.xml] file configures layers [4, 5, 6]
  • [4]: Hibernate implementation of JPA
  • [5]: Hibernate accesses the database via a connection pool. A connection pool is a pool of open connections to the DBMS. A DBMS is accessed by multiple users, yet for performance reasons, it cannot exceed a limit N of open connections simultaneously. Well-written code opens a connection to the DBMS for the minimum amount of time: it executes SQL commands and closes the connection. It will do this repeatedly, every time it needs to work with the database. The cost of opening and closing a connection is not negligible, and this is where the connection pool comes in. When the application starts, the connection pool opens N1 connections to the DBMS. The application requests an open connection from the pool whenever it needs one. The connection is returned to the pool as soon as the application no longer needs it, preferably as quickly as possible. The connection is not closed and remains available for the next user. A connection pool is therefore a system for sharing open connections.
  • [6]: the JDBC driver for the DBMS being used

Now let’s see how the [persistence.xml] file configures the layers [4, 5, 6] above:

  • line 2: the root tag of the XML file is <persistence>.
  • line 3: <persistence-unit> is used to define a persistence unit. There can be multiple persistence units. Each one has a name (name attribute) and a transaction type (transaction-type attribute). The application will access the persistence unit via its name, in this case jpa. The transaction type RESOURCE_LOCAL indicates that the application manages transactions with the DBMS itself. This will be the case here. When the application runs in an EJB3 container, it can use the container’s transaction service. In this case, we will set transaction-type=JTA (Java Transaction API). JTA is the default value when the transaction-type attribute is omitted.
  • Line 5: The <provider> tag is used to define a class that implements the [javax.persistence.spi.PersistenceProvider] interface, which allows the application to initialize the persistence layer. Because we are using a JPA/Hibernate implementation, the class used here is a Hibernate class.
  • Line 6: The <properties> tag introduces properties specific to the chosen provider. Thus, depending on whether you have chosen Hibernate, TopLink, Kodo, etc., you will have different properties. The following are specific to Hibernate.
  • Line 8: Instructs Hibernate to scan the project’s classpath to find classes annotated with @Entity so that they can be managed. @Entity classes can also be declared using <class>class_name</class> tags, directly under the <persistence-unit> tag. This is what we will do with the JPA/Toplink provider.
  • Lines 10–12, which are commented out here, configure Hibernate’s console logs:
    • Line 10: to enable or disable the display of SQL statements issued by Hibernate to the DBMS. This is very useful during the learning phase. Due to the relational/object bridge, the application works on persistent objects to which it applies operations such as [persist, merge, remove]. It is very helpful to know which SQL statements are actually issued for these operations. By studying them, you gradually learn to anticipate the SQL statements Hibernate will generate when performing such operations on persistent objects, and the relational/object bridge begins to take shape in your mind.
    • Line 11: The SQL statements displayed on the console can be formatted neatly to make them easier to read
    • Line 12: The displayed SQL statements will also be annotated
  • Lines 15–19 define the JDBC layer (layer [6] in the architecture):
    • line 15: the JDBC driver class for the DBMS, here MySQL5
    • line 16: the URL of the database being used
    • Lines 17, 18: the connection username and password
  • line 22: Hibernate needs to know which DBMS it is working with. This is because all DBMSs have proprietary SQL extensions—such as their own methods for automatically generating primary key values—which means Hibernate must identify the specific DBMS to send SQL statements that it can understand. [MySQL5InnoDBDialect] refers to the MySQL5 DBMS with InnoDB tables that support transactions.
  • Lines 24–28 configure the c3p0 connection pool (layer [5] in the architecture):
    • Lines 24, 25: the minimum (default 3) and maximum number of connections (default 15) in the pool. The default initial number of connections is 3.
    • Line 26: maximum wait time in milliseconds for a connection request from the client. After this timeout, c3p0 will return an exception.
    • Line 27: To access the database, Hibernate uses prepared SQL statements (PreparedStatement) that c3p0 can cache. This means that if the application requests a prepared SQL statement that is already in the cache a second time, it will not need to be prepared (preparing an SQL statement incurs a cost), and the one in the cache will be used. Here, we specify the maximum number of prepared SQL statements that the cache can hold, across all connections (a prepared SQL statement belongs to a single connection).
    • Line 28: Frequency in milliseconds for checking the validity of connections. A connection in the pool can become invalid for various reasons (the JDBC driver invalidates the connection because it has been open too long, the JDBC driver has "bugs," etc.).
  • Line 20: Here, we specify that when the persistence layer is initialized, the database schema for @Entity objects should be generated. Hibernate now has all the tools to generate the SQL statements for creating the database tables:
    • the configuration of the @Entity objects allows it to determine which tables to generate
    • Lines 15–18 and 24–28 allow it to establish a connection with the DBMS
    • line 22 tells it which SQL dialect to use to generate the tables

Thus, the [persistence.xml] file used here recreates a new database each time the application is run. The tables are recreated (create table) after being dropped (drop table) if they existed. Note that this is obviously not something to do with a production database...

4.2.3. Example 2: One-to-many relationship

4.2.3.1. The database schema file [

 
1
2

    alter table jpa06_article
        drop
        foreign key FKFFBDD9D8ECCE8750;

    drop table if exists jpa06_article;

    drop table if exists jpa06_category;

    create table jpa06_article (
        id bigint not null auto_increment,
        version integer not null,
        name varchar(30),
        category_id bigint not null,
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa06_category (
        id bigint not null auto_increment,
        version integer not null,
        name varchar(30),
        primary key (id)
    ) ENGINE=InnoDB;

    ALTER TABLE jpa06_article
        add index FKFFBDD9D8ECCE8750 (category_id),
        add constraint FKFFBDD9D8ECCE8750
        foreign key (category_id)
references jpa06_categorie (id);
  • in [1], the database, and in [2], its DDL (MySQL5)

An article A(id, version, name) belongs to exactly one category C(id, version, name). A category C can contain 0, 1, or more articles. We have a one-to-many relationship (Category -> Article) and the inverse many-to-one relationship (Article -> Category). This relationship is represented by the foreign key that the [article] table has on the [category] table (lines 24–28 of the DDL).

4.2.3.2. The @Entity objects representing the database

An article is represented by the following @Entity [Article]:


package entities;

...
@Entity
@Table(name="jpa05_hb_article")
public class Article implements Serializable {

    // fields
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @SuppressWarnings("unused")
    @Version
    private int version;

    @Column(length = 30)
    private String name;

    // primary relationship Article (many) -> Category (one)
    // implemented via a foreign key (categorie_id) in Article
    // 1 Article must have 1 Category (nullable=false)
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "categorie_id", nullable = false)
    private Category category;

    // constructors
    public Article() {
    }

    // getters and setters
    ...
    // toString
    public String toString() {
        return String.format("Article[%d,%d,%s,%d]", id, version, name, category.getId());
    }

}
  • lines 9-11: primary key of the @Entity
  • lines 13-15: its version number
  • lines 17-18: name of the article
  • lines 20-25: many-to-one relationship linking the @Entity Article to the @Entity Category:
    • line 23: the ManyToOne annotation. The Many refers to the @Entity Article in which we are located, and the One refers to the @Entity Category (line 25). A category (One) can have multiple articles (Many).
    • line 24: the ManyToOne annotation defines the foreign key column in the [article] table. It will be named (name) categorie_id, and each row must have a value in this column (nullable=false).
    • Line 25: The category to which the article belongs. When an article is added to the persistence context, we request that its category not be added immediately (fetch=FetchType.LAZY, line 23). We don’t know if this request makes sense. We’ll see.

A category is represented by the following @Entity [Category]:


package entities;
...
@Entity
@Table(name="jpa05_hb_categorie")
public class Category implements Serializable {

    // fields
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @SuppressWarnings("unused")
    @Version
    private int version;

    @Column(length = 30)
    private String name;

    // reverse relationship Category (one) -> Article (many) of the relationship Article (many) -> Category (one)
    // cascade insertion Category -> insertion Articles
    // Category update cascade -> Article update
    // Category delete cascade -> Article delete
    @OneToMany(mappedBy = "category", cascade = { CascadeType.ALL })
    private Set<Article> articles = new HashSet<Article>();

    // constructors
    public Category() {
    }

    // getters and setters
...
    // toString
    public String toString() {
        return String.format("Category[%d,%d,%s]", id, version, name);
    }

    // bidirectional association Category <--> Article
    public void addArticle(Article article) {
        // the article is added to the collection of articles in the category
        articles.add(article);
        // the article changes categories
        article.setCategory(this);
    }
}
  • lines 8-11: the primary key of the @Entity
  • lines 12-14: its version
  • lines 16-17: the category name
  • lines 19-24: the set of articles in the category
    • line 23: the @OneToMany annotation denotes a one-to-many relationship. The "One" refers to the @Entity [Category] we are in, and the "Many" refers to the [Article] type on line 24: one (One) category has many (Many) articles.
    • line 23: the annotation is the inverse (mappedBy) of the ManyToOne annotation placed on the category field of the @Entity Article: mappedBy=category. The ManyToOne relationship placed on the category field of the @Entity Article is the primary relationship. It is essential. It implements the foreign key relationship that links the @Entity Article to the @Entity Category. The OneToMany relationship placed on the articles field of the @Entity Category is the inverse relationship. It is not essential. It is a convenience for retrieving the articles of a category. Without this convenience, these articles would be retrieved via a JPQL query.
    • Line 23: cascadeType.ALL ensures that operations (persist, merge, remove) performed on a @Entity Category are cascaded to its articles.
    • Line 24: The articles in a category will be placed in an object of type `Set<Article>`. The `Set` type does not allow duplicates. Thus, the same article cannot be added twice to the `Set<Article>` object. What does "the same article" mean? To indicate that article `a` is the same as article `b`, Java uses the expression `a.equals(b)`. In the Object class, the parent of all classes, a.equals(b) is true if a==b, i.e., if objects a and b have the same memory location. One might want to say that items a and b are the same if they have the same name. In this case, the developer must redefine two methods in the [Item] class:
  • equals: which must return true if the two items have the same name
  • hashCode: must return the same integer value for two objects [Article] that the equals method considers equal. Here, the value will therefore be constructed from the article's name. The value returned by hashCode can be any integer. It is used in various object containers, particularly dictionaries (Hashtable).

The OneToMany relationship can use types other than Set to store the Many, such as List objects. We will not cover these cases in this document. The reader can find them in [ref1].

  • Line 38: The [addArticle] method allows us to add an article to a category. The method ensures that both ends of the OneToMany relationship linking [Category] to [Article] are updated.

4.3. The JPA Layer API

Let’s clarify the runtime environment of a JPA client:

We know that the JPA layer [2] creates a bridge between the object [3] and relational [4] domains. The set of objects managed by the JPA layer within this object/relational bridge is called the "persistence context." To access data in the persistence context, a JPA client [1] must go through the JPA layer [2]:

  1. it can create an object and ask the JPA layer to make it persistent. The object then becomes part of the persistence context.
  2. it can request a reference to an existing persistent object from the [JPA] layer.
  3. it can modify a persistent object obtained from the JPA layer.
  4. it can ask the JPA layer to remove an object from the persistence context.

The JPA layer provides the client with an interface called [EntityManager] which, as its name suggests, allows for the management of @Entity objects in the persistence context. Below are the main methods of this interface:

void persist(Object entity)
Adds the entity to the persistence context
void remove(Object entity)
removes entity from the persistence context
<T> T merge(T entity)
merges an entity object from the client that is not managed by the persistence context
with the entity object in the persistence context that has the same primary key.
The result returned is the entity object from the persistence context.
<T> T find(Class<T> entityClass,
Object primaryKey)
places an object retrieved from the database into the persistence context
via its primary key. The type T of the object allows the JPA layer
to know which table to query. The persistent object thus created is returned to the client.
Query createQuery(String queryText)
creates a Query object from a JPQL (Java Persistence
Query Language). A JPQL query is analogous to an SQL query,
except that it queries objects rather than tables.
Query createNativeQuery(String queryText)
A method similar to the previous one, except that queryText is an
SQL query rather than a JPQL query.
Query createNamedQuery(String name)
A method identical to createQuery, except that the JPQL query queryText has
been externalized into a configuration file and associated with a name.
This name is the method’s parameter.

An EntityManager object has a lifecycle that is not necessarily the same as that of the application. It has a beginning and an end. Thus, a JPA client can work successively with different EntityManager objects. The persistence context associated with an EntityManager has the same lifecycle as the EntityManager itself. They are inseparable from one another. When an EntityManager object is closed, its persistence context is, if necessary, synchronized with the database and then ceases to exist. A new EntityManager must be created to obtain a new persistence context.

The JPA client can create an EntityManager and thus a persistence context with the following statement:


EntityManagerFactory emf = Persistence.createEntityManagerFactory("name of a persistence unit");
  • javax.persistence.Persistence is a static class used to obtain a factory for EntityManager objects. This factory is associated with a specific persistence unit. Recall that the configuration file [META-INF/persistence.xml] is used to define persistence units, and that these units have a name:

    <persistence-unit name="elections-dao-jpa-mysql-01PU" transaction-type="RESOURCE_LOCAL">

Above, the persistence unit is named elections-dao-jpa-mysql-01PU. It comes with its own specific configuration, including the DBMS it works with. The statement [Persistence.createEntityManagerFactory("elections-dao-jpa-mysql-01PU")] creates an EntityManagerFactory capable of providing EntityManager objects intended to manage persistence contexts associated with the persistence unit named elections-dao-jpa-mysql-01PU. An EntityManager object—and thus a persistence context—is obtained from the EntityManagerFactory object as follows:

        EntityManager em = emf.createEntityManager();

The following methods of the [EntityManager] interface allow you to manage the lifecycle of the persistence context:

void close()
The persistence context is closed. Forces synchronization of the persistence context with the database:
  • if an object in the context is not present in the database, it is inserted via an SQL INSERT operation)
  • if an object in the context is present in the database and has been modified since it was read, an SQL UPDATE operation is performed to persist the modification
  • if an object in the context has been marked as "deleted" following a remove operation on it, an SQL DELETE operation is performed to remove it from the database.
void clear()
The persistence context is cleared of all its objects but not closed.
void flush()
The persistence context is synchronized with the database as described for close()

The JPA client can force synchronization of the persistence context with the database using the [EntityManager].flush method. Synchronization can be explicit or implicit. In the first case, it is up to the client to perform flush operations when it wants to synchronize; otherwise, synchronization occurs at specific times that we will specify. The synchronization mode is managed by the following methods of the [EntityManager] interface:

void setFlushMode(FlushModeType
flushMode)
There are two possible values for flushMode:
FlushModeType.AUTO (default): synchronization occurs before
every SELECT query made on the database.
FlushModeType.COMMIT: synchronization occurs only at
the end of transactions on the database.
FlushModeType getFlushMode()
returns the current synchronization mode

To summarize: In FlushModeType.AUTO mode, which is the default, the persistence context will be synchronized with the database at the following times:

  1. before each SELECT operation on the database
  2. at the end of a transaction on the database
  3. following a flush or close operation on the persistence context

In FlushModeType.COMMIT mode, the same applies except for operation 1, which does not occur. The normal mode of interaction with the JPA layer is transactional mode. The client performs various operations on the persistence context within a transaction. In this case, the synchronization points between the persistence context and the database are cases 1 and 2 above in AUTO mode, and case 2 only in COMMIT mode.

Let’s conclude with the Query interface API, which allows you to issue JPQL commands on the persistence context or SQL commands directly on the database to retrieve data. The Query interface is as follows:

  • 1 - The getResultList method executes a SELECT query that returns multiple objects. These are returned in a List object. This object is an interface. It provides an Iterator object that allows you to iterate through the elements of the list L as follows:

        Iterator iterator = L.iterator();
        while (iterator.hasNext()) {
            // use the iterator.next() method, which represents the current element in the list
...
}

The list L can also be iterated over using a for loop:


        for (Object o : L) {
            // process object o
}
  • 2 - The getSingleResult method executes a JPQL/SQL SELECT statement that returns a single object.
  • 3 - The executeUpdate method executes an SQL UPDATE or DELETE statement and returns the number of rows affected by the operation.
  • 4 - The setParameter(String, Object) method allows you to assign a value to a named parameter of a parameterized JPQL query
  • 5 - The setParameter(int, Object) method sets the parameter, but the parameter is identified not by its name but by its position in the JPQL query.

4.4. s (JPQL)

JPQL (Java Persistence Query Language) is the query language of the JPA layer. The JPQL language is similar to the SQL language used in databases. While SQL works with tables, JPQL works with the objects representing those tables. We will examine an example within the following architecture:

The database, which we will call [ dbrdvmedecins2], is a MySQL5 database with four tables:

  

It collects information used to manage the appointments of a group of doctors.

4.4.1. The [MEDECINS] table

It contains information about the doctors.

  • ID: the doctor’s ID number—the table’s primary key
  • VERSION: a number identifying the version of the row in the table. This number is incremented by 1 each time a change is made to the row.
  • LAST_NAME: the doctor's last name
  • FIRST NAME: the doctor's first name
  • TITLE: their title (Ms., Mrs., Mr.)

4.4.2. The [CLIENTS] table

The clients of the various doctors are stored in the [CLIENTS] table:

  • ID: ID number identifying the client - primary key of the table
  • VERSION: number identifying the version of the row in the table. This number is incremented by 1 each time a change is made to the row.
  • LAST NAME: the client's last name
  • FIRST NAME: the client’s first name
  • TITLE: their title (Ms., Mrs., Mr.)

4.4.3. The [SLOTS] table

It lists the time slots when appointments are available:

  • ID: ID number for the time slot - primary key of the table (row 8)
  • VERSION: number identifying the version of the row in the table. This number is incremented by 1 each time a change is made to the row.
  • DOCTOR_ID: ID number identifying the doctor to whom this time slot belongs – foreign key on the DOCTORS(ID) column.
  • START_TIME: start time of the time slot
  • MSTART: Start minutes of the time slot
  • HFIN: slot end time
  • MFIN: End minutes of the slot

The second row of the [SLOTS] table (see [1] above) indicates, for example, that slot #2 begins at 8:20 a.m. and ends at 8:40 a.m. and belongs to doctor #1 (Ms. Marie PELISSIER).

4.4.4. The [RV] table

It lists the appointments made for each doctor:

  • ID: unique identifier for the appointment – primary key
  • DAY: day of the appointment
  • SLOT_ID: appointment time slot – foreign key on the [ID] field of the [SLOTS] table – determines both the time slot and the doctor involved.
  • CLIENT_ID: ID of the client for whom the reservation is made – foreign key on the [ID] field of the [CLIENTS] table

This table has a uniqueness constraint on the values of the joined columns (DAY, SLOT_ID):

ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (DAY, SLOT_ID);

If a row in the [RV] table has the value (DAY1, SLOT_ID1) for the columns (DAY, SLOT_ID), this value cannot appear anywhere else. Otherwise, this would mean that two appointments were booked at the same time for the same doctor. From a Java programming perspective, the database’s JDBC driver throws an SQLException when this occurs.

The row with ID equal to 3 (see [1] above) means that an appointment was booked for slot #20 and client #4 on 08/23/2006. The [SLOTS] table tells us that slot no. 20 corresponds to the time slot 4:20 PM – 4:40 PM and belongs to doctor no. 1 (Ms. Marie PELISSIER). The [CLIENTS] table tells us that client no. 4 is Ms. Brigitte BISTROU.

4.4.5. Generating the database

To create the tables and populate them, you can use the script [dbrdvmedecins2.sql]. With [WampServer], you can proceed as follows:

  • In [1], click the [WampServer] icon and select the [PhpMyAdmin] option [2],
  • In [3], in the window that opens, select the [Databases] link,
  • In [2], create a database with the name [4] and encoding [5],
  • In [7], the database has been created. Click on its link,
  • in [8], import an SQL file,
  • which you select from the file system using the [9] button,
  • in [11], select the SQL script and in [12] execute it,
  • in [13], the four tables in the database have been created. Follow one of the links,
  • in [14], the table’s contents.

We will not return to this database again. However, the reader is invited to follow its evolution throughout the programs, especially when things don’t work.

4.4.6. The [JPA] layer

Let’s return to the architecture of the example:

We are now building the Maven project for the [JPA] layer.

4.4.7. The NetBeans project

Here is what it looks like:

  • In [1], we create a Maven project of type [Java Application] [2],
  • in [3], we name the project,
  • in [4], the generated project.

4.4.8. Generating the [JPA] layer

Let’s return to the architecture we need to build:

With NetBeans, it is possible to automatically generate the [JPA] layer. It is useful to be familiar with these automatic generation methods because the generated code provides valuable insights into how to write JPA entities.

4.4.9. Creating a NetBeans connection to the database

  • Start the MySQL 5 DBMS so that the database is available,
  • create a NetBeans connection to the [dbrdvmedecins2] database,
  • in the [Services] tab [1], under the [Databases] section [2], select the MySQL JDBC driver [3],
  • then select the [4] "Connect Using" option to create a connection to a MySQL database,
  • in [5], enter the requested information. In [6], the database name; in [7], the database user and password;
  • in [8], you can test the information you have provided,
  • in [9], the expected message if the information is correct,
  • in [10], the connection is established. You can see the four tables in the connected database.

4.4.10. Creating a persistence unit

Let’s return to the architecture we are building:

We are currently building the [JPA] layer. Its configuration is done in a [persistence.xml] file where persistence units are defined. Each one requires the following information:

  • the JDBC connection details (URL, username, password),
  • the classes that will represent the database tables,
  • the JPA implementation used. Indeed, JPA is a specification implemented by various products. Here, we will use Hibernate.

NetBeans can generate this persistence file using a wizard.

  • Right-click on the project and select "Create Persistence Unit" [1],
  • in [2], create a persistence unit,
  • in [3], name the persistence unit you are creating,
  • in [4], select the Hibernate JPA implementation (JPA 2.0),
  • in [5], indicate that the database tables already exist and therefore do not need to be created. Confirm the wizard,
  • in [6], the new project,
  • in [7], the [persistence.xml] file has been generated in the [META-INF] folder,
  • in [8], new dependencies have been added to the Maven project.

The generated [META-INF/persistence.xml] file is as follows:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" 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 http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
    </properties>
  </persistence-unit>
</persistence>

It includes the information provided in the wizard:

  • line 3: the name of the persistence unit,
  • line 3: the type of database transactions. Here, RESOURCE_LOCAL indicates that the application will manage its own transactions,
  • lines 6–9: the JDBC properties of the data source.

In the [Design] tab, you can see an overview of the [persistence.xml] file:

To enable Hibernate logging, we complete the [persistence.xml] file as follows:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" 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 http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>      
    </properties>
  </persistence-unit>
</persistence>
  • Line 11: We request to see the SQL statements issued by Hibernate,
  • line 12: this property enables a formatted display of these statements.

Dependencies have been added to the project. The [pom.xml] file is as follows:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>istia.st</groupId>
  <artifactId>mv-rdvmedecins-jpql-hibernate</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mv-rdvmedecins-jpql-hibernate</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.1.2</version>
    </dependency>
    <dependency>
      <groupId>org.jboss.logging</groupId>
      <artifactId>jboss-logging</artifactId>
      <version>3.1.0.GA</version>
    </dependency>
    <dependency>
      <groupId>org.jboss.spec.javax.transaction</groupId>
      <artifactId>jboss-transaction-api_1.1_spec</artifactId>
      <version>1.0.0.Final</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>4.1.2</version>
    </dependency>
    <dependency>
      <groupId>antlr</groupId>
      <artifactId>antlr</artifactId>
      <version>2.7.7</version>
    </dependency>
    <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.6.1</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate.javax.persistence</groupId>
      <artifactId>hibernate-jpa-2.0-api</artifactId>
      <version>1.0.1.Final</version>
    </dependency>
    <dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.15.0-GA</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate.common</groupId>
      <artifactId>hibernate-commons-annotations</artifactId>
      <version>4.0.1.Final</version>
    </dependency>
  </dependencies>
</project>

The added dependencies all relate to the Hibernate ORM. We will add the MySQL JDBC driver dependency:


    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>        

4.4.11. Generating JPA Entities

JPA entities can be generated using a NetBeans wizard:

  • In [1], create JPA entities from a database,
  • in [2], select the previously created connection [dbrdvmedecins2],
  • in [3], select all tables from the associated database,
  • in [4], name the Java classes associated with the four tables,
  • as well as a package name [5],
  • in [6], JPA groups rows from database tables into collections. We choose a list as the collection,
  • in [7], the Java classes created by the wizard.

4.4.12. The generated JPA entities

The [Medecin] entity mirrors the [medecins] table. The Java class is littered with annotations that make the code difficult to read at first glance. If we keep only what is essential to understanding the entity’s role, we get the following code:


package rdvmedecins.jpa;

...
@Entity
@Table(name = "medecins")
public class Doctor implements Serializable {
  
@Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;
  
  @Column(name = "TITLE")
  private String title;

  @Column(name = "LAST_NAME")
  private String name;

  @Column(name = "VERSION")
  private int version;

  @Column(name = "FIRST_NAME")
  private String firstName;

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "doctorId")
  private List<AppointmentSlot> appointmentSlotList;

// constructors
....

  // getters and setters
....

  @Override
  public int hashCode() {
  ...
  }

  @Override
  public boolean equals(Object object) {
  ...
  }

  @Override
  public String toString() {
    ...
  }
  
}
  • Line 4: The @Entity annotation makes the [Medecin] class a JPA entity, i.e., a class linked to a database table via the JPA API.
  • line 5, the name of the database table associated with the JPA entity. Each field in the table corresponds to a field in the Java class,
  • line 6: the class implements the Serializable interface. This is necessary in client/server applications, where entities are serialized between the client and the server.
  • lines 10–11: the id field of the [Doctor] class corresponds to the [ID] field (line 10) of the [doctors] table,
  • lines 13–14: the title field of the [Doctor] class corresponds to the [TITLE] field (line 13) of the [doctors] table,
  • lines 16–17: the `nom` field of the [Medecin] class corresponds to the `[NOM]` field (line 16) of the [medecins] table,
  • rows 19-20: the version field of the [Medecin] class corresponds to the [VERSION] field (row 19) of the [doctors] table. Here, the wizard does not recognize that the column is actually a version column that must be incremented each time the row to which it belongs is modified. To assign this role to it, you must add the @Version annotation. We will do this in a later step,
  • lines 22–23: the first_name field of the [Doctor] class corresponds to the [FIRST_NAME] field of the [doctors] table,
  • lines 10–11: the id field corresponds to the primary key [ID] of the table. The annotations on lines 8–9 clarify this point,
  • line 8: the @Id annotation indicates that the annotated field is associated with the table’s primary key,
  • line 9: the [JPA] layer will generate the primary key for the rows it inserts into the [Doctors] table. There are several possible strategies. Here, the GenerationType.IDENTITY strategy indicates that the JPA layer will use the auto_increment mode of the MySQL table,
  • lines 25–26: the [slots] table has a foreign key on the [doctors] table. A slot belongs to a doctor. Conversely, a doctor has several slots associated with them. We therefore have a one-to-many relationship (one doctor to many slots), a relationship qualified by the @OneToMany annotation in JPA (line 25). The field on line 26 will contain all of the doctor’s slots. This is achieved without any programming. To fully understand line 25, we need to introduce the [Creneau] class.

It is as follows:


package rdvmedecins.jpa;

import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotNull;

@Entity
@Table(name = "slots")
public class Slot implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;

  @Column(name = "START_TIME")
  private int mstart;

  @Column(name = "HFIN")
  private int hfin;

  @Column(name = "START_TIME")
  private int hstart;

  @Column(name = "MFIN")
  private int mfin;

  @Column(name = "VERSION")
  private int version;

  @JoinColumn(name = "DOCTOR_ID", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Doctor doctorId;

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "idCreneau")
  private List<Rv> appointmentList;

// constructors
...
// getters and setters
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    ...
  }
  
}

We only comment on the new annotations:

  • we have specified that the [slots] table has a foreign key to the [doctors] table: a slot is associated with a doctor. Multiple slots can be associated with the same doctor. We have a relationship from the [slots] table to the [doctors] table that is defined as many-to-one (slots to doctor). The @ManyToOne annotation on line 32 is used to define the foreign key,
  • line 31, with the @JoinColumn annotation, specifies the foreign key relationship: the [ID_MEDECIN] column in the [slots] table is a foreign key on the [ID] column in the [doctors] table,
  • Line 33: a reference to the doctor who owns the slot. This is achieved here as well without any coding.

The foreign key relationship between the [Creneau] entity and the [Medecin] entity is therefore implemented by two annotations:

  • in the [Creneau] entity:

@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
  @ManyToOne(optional = false)
private Doctor idDoctor;
  • in the [Doctor] entity:

@OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
private List<Slot> slotList;

Both annotations reflect the same relationship: that of the foreign key from the [appointments] table to the [doctors] table. They are said to be inverses of each other. Only the @ManyToOne relationship is essential. It unambiguously defines the foreign key relationship. The @OneToMany relationship is optional. If present, it simply references the @ManyToOne relationship with which it is associated. This is the meaning of the mappedBy attribute on line 1 of the [Medecin] entity. The value of this attribute is the name of the field in the [Creneau] entity that has the @ManyToOne annotation specifying the foreign key. Also on line 1 of the [Medecin] entity, the cascade=CascadeType.ALL attribute defines the behavior of the [Medecin] entity with respect to the [Creneau] entity:

  • if a new [Doctor] entity is inserted into the database, then the [TimeSlot] entities in the field on line 2 must also be inserted,
  • if a [Doctor] entity is modified in the database, then the [Slot] entities in the field on line 2 must also be modified,
  • if an [Doctor] entity is deleted from the database, then the [Slot] entities in the field on line 2 must also be deleted.

We provide the code for the other two entities without specific comments since they do not introduce any new notation.

The [Client] entity


package rdvmedecins.jpa;

...
@Entity
@Table(name = "clients")
public class Client implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;

  @Column(name = "TITLE")
  private String title;

  @Column(name = "NAME")
  private String name;

  @Column(name = "VERSION")
  private int version;

  @Column(name = "FIRST_NAME")
  private String firstName;

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "idClient")
  private List<Rv> rvList;

// constructors
...
// getters and setters
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    ...
  }
  
}
  • Lines 24–25 reflect the foreign key relationship between the [rv] table and the [clients] table.

The [Rv] entity:


package rdvmedecins.jpa;

...
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;

  @Column(name = "DAY")
  @Temporal(TemporalType.DATE)
  private Date day;

  @JoinColumn(name = "SLOT_ID", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Slot slotId;

  @JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Client clientId;

  // constructors
...

  // getters and setters
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    ...
  }
  
}
  • Line 13 defines the `jour` field as a Java Date type. It specifies that in the [rv] table, the [JOUR] column (line 12) is of type date (without time),
  • Lines 16–18: define the foreign key relationship from the [rv] table to the [slots] table,
  • Lines 20–22: define the foreign key relationship from the [rv] table to the [clients] table.

The automatic generation of JPA entities provides us with a working foundation. Sometimes this is sufficient, sometimes it is not. This is the case here:

  • we need to add the @Version annotation to the various version fields of the entities,
  • we need to write toString methods that are more explicit than the generated ones,
  • the [Medecin] and [Client] entities are analogous. We will have them derive from a [Person] class,
  • we will remove the inverse @OneToMany relationships from the @ManyToOne relationships. They are not essential and introduce programming complications,
  • we remove the @NotNull validation on the primary keys. When persisting a JPA entity with MySQL, the entity initially has a null primary key. It is only after persistence in the database that the primary key of the persisted entity has a value.

With these specifications, the various classes become as follows:

The Person class is used to represent doctors and clients:


package rdvmedecins.jpa;

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

@MappedSuperclass
public class Person implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;

  @Basic(optional = false)
  @Column(name = "TITLE")
  private String title;

  @Basic(optional = false)
  @Column(name = "LAST_NAME")
  private String name;

  @Basic(optional = false)
  @Column(name = "VERSION")
  @Version
  private int version;
  
  @Basic(optional = false)
  @Column(name = "FIRST_NAME")
  private String firstName;
// constructors
...

// getters and setters
  ...

  @Override
  public String toString() {
    return String.format("[%s,%s,%s,%s,%s]", id, version, title, firstName, lastName);
  }
  
}
  • Line 6: Note that the [Person] class is not itself an entity (@Entity). It will serve as the parent class for entities. The @MappedSuperClass annotation indicates this.

The [Client] entity encapsulates the rows of the [clients] table. It derives from the previous [Person] class:


package rdvmedecins.jpa;

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

@Entity
@Table(name = "clients")
public class Client extends Person implements Serializable {
  private static final long serialVersionUID = 1L;

// constructors
...

  @Override
  public int hashCode() {
...
  }

  @Override
  public boolean equals(Object object) {
  ...
  }

  @Override
  public String toString() {
    return String.format("Client[%s,%s,%s,%s]", getId(), getTitle(), getFirstName(), getLastName());
  }
  
}
  • Line 6: The [Client] class is a JPA entity,
  • line 7: it is associated with the [clients] table,
  • line 8: it derives from the [Person] class.

The [Doctor] entity, which encapsulates the rows of the [doctors] table, follows the same pattern:


package rdvmedecins.jpa;

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

@Entity
@Table(name = "doctors")
public class Doctor extends Person implements Serializable {
  private static final long serialVersionUID = 1L;

  // constructors
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    return String.format("Doctor[%s,%s,%s,%s]", getId(), getTitle(), getFirstName(), getLastName());
  }
  
}

The [Creneau] entity encapsulates the rows of the [creneaux] table:


package rdvmedecins.jpa;

import java.io.Serializable;
import java.util.List;
import javax.persistence.*;

@Entity
@Table(name = "slots")
public class Slot implements Serializable {

  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic(optional = false)
  @Column(name = "ID")
  private Long id;
  
  @Basic(optional = false)
  @Column(name = "START_DATE")
  private int mstart;
  
  @Basic(optional = false)
  @Column(name = "ENDTIME")
  private int end;
  
  @Basic(optional = false)
  @NotNull
  @Column(name = "START_TIME")
  private int startTime;
  
  @Basic(optional = false)
  @Column(name = "MFIN")
  private int endDate;
  
  @Basic(optional = false)
  @Column(name = "VERSION")
  @Version
  private int version;
  
  @JoinColumn(name = "DOCTOR_ID", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Doctor doctor;

  // constructors
  ...

  // getters and setters
  ...
 
  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    // TODO: Warning - this method won't work if the id fields are not set
    ...
  }

  @Override
  public String toString() {
    return String.format("Slot [%s, %s, %s:%s, %s:%s,%s]", id, version, startHour, startMinute, endHour, endMinute, doctor);
  }
}
  • Lines 40–42 model the "many-to-one" relationship between the [slots] table and the [doctors] table in the database: a doctor has multiple slots, and a slot belongs to a single doctor.

The [Rv] entity encapsulates the rows of the [rv] table:


package rdvmedecins.jpa;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;

@Entity
@Table(name = "rv")
public class Rv implements Serializable {

  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic(optional = false)
  @Column(name = "ID")
  private Long id;
  
  @Basic(optional = false)
  @Column(name = "DAY")
  @Temporal(TemporalType.DATE)
  private Date day;
  
  @JoinColumn(name = "SLOT_ID", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Slot slot;
  
  @JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Client client;

  // constructors
...

  // getters and setters
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    return String.format("Appointment[%s, %s, %s]", id, time slot, client);
  }
}
  • Lines 27–29 model the "many-to-one" relationship between the [rv] table and the [clients] table (a client can appear in multiple Rv entries) in the database, and lines 23–25 model the "many-to-one" relationship between the [rv] table and the [slots] table (a slot can appear in multiple Rv entries).

4.4.13. The data access code

We will now add the code for accessing data via the JPA layer to the project:

The [MainJpql] class is as follows:


package rdvmedecins.console;

import java.util.Scanner;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class MainJpql {

  public static void main(String[] args) {
    // EntityManagerFactory
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-rdvmedecins-jpql-hibernatePU");
    // EntityManager
    EntityManager em = emf.createEntityManager();
    // keyboard scanner
    KeyboardScanner = new Scanner(System.in);
    // loop for entering JPQL queries
    System.out.println("JPQL query on the dbrdvmedecins2 database (* to stop):");
    String query = keyboard.nextLine();
    while (!query.trim().equals("*")) {
      try {
        // Display query results
        for (Object o : em.createQuery(query).getResultList()) {
          System.out.println(o);
        }
      } catch (Exception e) {
        System.out.println("The following exception occurred: " + e);
      }
      // clear the persistence context
      em.clear();
      // new query
      System.out.println("---------------------------------------------");
      System.out.println("JPQL query on the dbrdvmedecins2 database (* to stop):");
      query = keyboard.nextLine();
    }
    // Close resources
    em.close();
    emf.close();
  }
}
  • Line 12: Creation of the EntityManagerFactory associated with the persistence unit we created earlier. The parameter of the `createEntityManagerFactory` method is the name of this persistence unit:

  <persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
    ...
</persistence-unit>
  • line 14: creation of the EntityManager that manages the persistence layer,
  • Line 19: Enter a JPQL SELECT query,
  • lines 23–28: display the query result,
  • line 20: input stops when the user types *.

Question: Provide the JPQL queries to retrieve the following information:


  • list of doctors in descending order by last name
  • list of doctors whose title='Mr'
  • list of Ms. Pelissier's appointment slots
  • list of appointments in ascending order by date
  • list of clients (last name) who made appointments with Ms. Pelissier on 08/24/2006
  • Number of clients of Ms. Pelissier on 08/24/2006
  • clients who have not made an appointment
  • Doctors who do not have appointments

We will draw inspiration from the example in Section 2.7 of [ref1]. Here is an example of execution:

JPQL query on the dbrdvmedecins2 database (* to stop):
select c from Client c
Hibernate: 
    select
        client0_.ID as ID2_,
        client0_.LAST_NAME as LAST_NAME2_,
        client0_.FIRST_NAME as FIRST_NAME2_,
        client0_.TITLE as TITLE2_,
        client0_.version as version2_ 
    from
        clients client0_
Client[1,Mr,Jules,MARTIN]
Client[2,Ms.,Christine,GERMAN]
Client[3,Mr.,Jules,JACQUARD]
Client[4,Ms.,Brigitte,BISTROU]
  • line 2: the JPQL query,
  • lines 3–11: the corresponding SQL query,
  • lines 12–15: the result of the JPQL query.

4.5.1. The Person class

package entities;

...

@Entity
@Table(name = "jpa01_person")
public class Person {

  @Id
  @Column(name = "ID", nullable = false)
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Integer id;

  @Column(name = "VERSION", nullable = false)
  @Version
  private int version;

  @Column(name = "NAME", length = 30, nullable = false, unique = true)
  private String lastName;

  @Column(name = "LAST_NAME", length = 30, nullable = false)
  private String firstName;

  @Column(name = "BIRTHDATE", nullable = false)
  @Temporal(TemporalType.DATE)
  private Date birthdate;

  @Column(name = "MARRIED", nullable = false)
  private boolean married;

  @Column(name = "NUMBEROFCHILDREN", nullable = false)
  private int numberOfChildren;

  // constructors

  public Person() {
  }

  public Person(String lastName, String firstName, Date birthDate, boolean married, int numberOfChildren) {
    setLastName(lastName);
    setFirstName(firstName);
    setBirthDate(birthDate);
    setMarried(married);
    setNumberOfChildren(numberOfChildren);
  }

  // toString

  public String toString() {
    return String.format("[%d,%d,%s,%s,%s,%s,%d]", getId(), getVersion(), getLastName(), getFirstName(), 
                         new SimpleDateFormat("dd/MM/yyyy").format(getBirthDate()), isMarried(), getNumberOfChildren());
  }

  // getters and setters
...
}

4.5.2. The test program

package tests;

....
import entities.Person;

@SuppressWarnings("unchecked")
public class Test1 {

  // constants
  private final static String TABLE_NAME = "jpa01_personne";  // Persistence context
  private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
  private static Person p1;

  public static void main(String[] args) throws Exception {
    // Clean up database
    log("clean");
    clean();

    // dump
    log("dump");
    dump();

    // test1
    log("test1");
    test1();

    // test2
    log("test2");
    test2();

    // Close EntityManagerFactory
    emf.close();
  }

  // display table contents
  private static void dump() {
    // persistence context
    EntityManager em = emf.createEntityManager();
    // start transaction
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    // display people
    for (Object p : em.createQuery("select p from Person p order by p.name asc").getResultList()) {
      System.out.println(p);
    }
    // end transaction
    tx.commit();
    // end context
    em.close();
  }

  // clear database
  private static void clean() {
    // persistence context
    EntityManager em = emf.createEntityManager();
    // start transaction
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    // delete records from the PEOPLE table
    em.createNativeQuery("delete from " + TABLE_NAME).executeUpdate();
    // end transaction
    tx.commit();
    // end context
    em.close();
  }

  // logs
  private static void log(String message) {
    System.out.println("main : ----------- " + message);
  }

  // management of persistent objects
  public static void test1() throws ParseException {
    // persistence context
    EntityManager em = emf.createEntityManager();
    // create people
    p1 = new Person("Martin", "Paul", new SimpleDateFormat("dd/MM/yy").parse("01/31/2000"), true, 2);
    Person p2 = new Person("Durant", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("07/05/2001"), false, 0);
    // start transaction
    EntityTransaction tx = em.getTransaction();
    System.out.println("start transaction");
    tx.begin();
    // Persist people
    // The logs show that the SQL INSERT operation is generated immediately after the persist operation
    // probably to obtain the primary key
    System.out.println(String.format("Person p1 %s not persisted", p1));
    System.out.println("em.persist(p1)");
    em.persist(p1);
    System.out.println(String.format("Person p1 %s persisted", p1));
    // Person p2
    // INSERT is generated as soon as the persist operation is performed
    System.out.println(String.format("Person p2 %s not persisted", p2));
    System.out.println("em.persist(p2)");
    em.persist(p2);
    System.out.println(String.format("Person p2 %s persisted", p2));
    p2.setWife(true);
    System.out.println(String.format("Person p2 %s modified", p2));
    // The DELETE operation associated with the remove operation is only performed at the end of the transaction
    System.out.println("em.remove(p2)");
    em.remove(p2);
    System.out.println(String.format("Person p2 %s deleted", p2));
    // modify p1
    p1.setName("P1");
    // end transaction
    System.out.println("End of transaction");
    tx.commit();
    // end context
    em.close();
    // display the table
    dump();
  }

  // management of persistent objects
  public static void test2() throws ParseException {
    // persistence context
    EntityManager em = emf.createEntityManager();
    // start transaction
    EntityTransaction tx = em.getTransaction();
    System.out.println("start transaction");
    tx.begin();
    // Modify the currently detached person p1
    System.out.println(String.format("Person p1 %s is currently not persisted", p1));
    p1.setMarried(false);
    System.out.println(String.format("Person p1 %s new, not persisted", p1));
    // Reattach person P1
    System.out.println("em.merge(p1)");
    Person p1b = em.merge(p1);
    System.out.println(String.format("Person p1b %s attached", p1b));
    // end transaction
    System.out.println("end transaction");
    tx.commit();
      // end context
    em.close();
  // display the table
    dump();
  }
}

4.5.3. Hibernate Configuration

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
  <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
        <!--  provider -->
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
            <!-- Persistent classes -->
      <property name="hibernate.archive.autodetection" value="class, hbm" />
      <property name="hibernate.show_sql" value="true"/>
....
            <!-- Automatic schema creation -->
      <property name="hibernate.hbm2ddl.auto" value="create" />
....
    </properties>
  </persistence-unit>
</persistence>

4.5.4. The log4j.properties configuration

# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

# Root logger option
log4j.rootLogger=ERROR, stdout

# Hibernate logging options (INFO only shows startup messages)
#log4j.logger.org.hibernate=DEBUG

# Log JDBC bind parameter runtime arguments
log4j.logger.org.hibernate.type=DEBUG

4.5.5. Results

init:
deps-jar:
Compiling 1 source file to C:\data\work\2008-2009\netbeans\jpa\hibernate-people-entities\build\classes
compile-single:
run-single:
main: ----------- clean
Hibernate: delete from jpa01_person
main : ----------- dump
Hibernate: select person0_.ID as ID0_, person0_.DATE_OF_BIRTH as DATE_OF_BIRTH2_0_, person0_.MARRIED_NAME as MARRIED_NAME0_, person0_.NUMBER_OF_CHILDREN as NUMBER_OF_CHILDREN0_, person0_.LAST_NAME as LAST_NAME0_, person0_.FIRST_NAME as FIRST_NAME0_, person0_.VERSION as VERSION0_ from jpa01_person person0_ order by person0_.LAST_NAME asc
main : ----------- test1
start transaction
Person p1 [null,0,Martin,Paul,01/31/2000,true,2] not persisted
em.persist(p1)
Hibernate: insert into jpa01_person (BIRTHDATE, MARRIED, CHILDREN, LAST_NAME, FIRST_NAME, VERSION) values (?, ?, ?, ?, ?, ?)
17:57:26,312 DEBUG DateType:133 - binding 'January 31, 2000' to parameter: 1
17:57:26.312 DEBUG BooleanType:133 - binding 'true' to parameter: 2
17:57:26,312 DEBUG IntegerType:133 - binding '2' to parameter: 3
17:57:26,312 DEBUG StringType:133 - binding 'Martin' to parameter: 4
17:57:26.312 DEBUG StringType:133 - binding 'Paul' to parameter: 5
17:57:26.312 DEBUG IntegerType:133 - binding '0' to parameter: 6
Person p1 [1,0,Martin,Paul,01/31/2000,true,2] persisted
Person p2 [null,0,Durant,Sylvie,07/05/2001,false,0] not persisted
em.persist(p2)
Hibernate: insert into jpa01_person (BIRTHDATE, MARRIED, CHILDREN, LASTNAME, FIRSTNAME, VERSION) values (?, ?, ?, ?, ?, ?)
17:57:26,328 DEBUG DateType:133 - binding 'July 5, 2001' to parameter: 1
17:57:26.328 DEBUG BooleanType:133 - binding 'false' to parameter: 2
17:57:26.328 DEBUG IntegerType:133 - binding '0' to parameter: 3
17:57:26,328 DEBUG StringType:133 - binding 'Durant' to parameter: 4
17:57:26.328 DEBUG StringType:133 - binding 'Sylvie' to parameter: 5
17:57:26.328 DEBUG IntegerType:133 - binding '0' to parameter: 6
Person p2 [2,0,Durant,Sylvie,05/07/2001,false,0] persisted
Person p2 [2,0,Durant,Sylvie,05/07/2001,true,0] modified
em.remove(p2)
Person p2 [2,0,Durant,Sylvie,05/07/2001,true,0] deleted
end transaction
Hibernate: update jpa01_person set BIRTHDATE=?, LAST_NAME=?, CHILDREN=?, FIRST_NAME=?, VERSION=? where ID=? and VERSION=?
17:57:26.343 DEBUG DateType:133 - binding 'January 31, 2000' to parameter: 1
17:57:26.343 DEBUG BooleanType:133 - binding 'true' to parameter: 2
17:57:26,343 DEBUG IntegerType:133 - binding '2' to parameter: 3
17:57:26.343 DEBUG StringType:133 - binding 'P1' to parameter: 4
17:57:26.359 DEBUG StringType:133 - binding 'Paul' to parameter: 5
17:57:26.359 DEBUG IntegerType:133 - binding '1' to parameter: 6
17:57:26.359 DEBUG IntegerType:133 - binding '1' to parameter: 7
17:57:26.359 DEBUG IntegerType:133 - binding '0' to parameter: 8
Hibernate: delete from jpa01_person where ID=? and VERSION=?
17:57:26.359 DEBUG IntegerType:133 - binding '2' to parameter: 1
17:57:26.359 DEBUG IntegerType:133 - binding '0' to parameter: 2
Hibernate: select person0_.ID as ID0_, person0_.DATE_OF_BIRTH as DATE_OF_BIRTH2_0_, person0_.MARRIED_NAME as MARRIED_NAME0_, person0_.NUMBER_OF_CHILDREN as NUMBER_OF_CHILDREN0_, person0_.LAST_NAME as LAST_NAME0_, person0_.FIRST_NAME as FIRST_NAME0_, person0_.VERSION as VERSION0_ from jpa01_person person0_ order by person0_.LAST_NAME asc
17:57:26.375 DEBUG IntegerType:172 - returning '1' as column: ID0_
17:57:26.390 DEBUG DateType:172 - returning 'January 31, 2000' as column: DATENAIS2_0_
17:57:26.390 DEBUG BooleanType:172 - returning 'true' as column: MARIE0_
17:57:26.390 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_
17:57:26.390 DEBUG StringType:172 - returning 'P1' as column: NOM0_
17:57:26,390 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_
17:57:26.390 DEBUG IntegerType:172 - returning '1' as column: VERSION0_
[1,1,P1,Paul,01/31/2000,true,2]
main : ----------- test2
start transaction
Person p1 [1,1,P1,Paul,01/31/2000,true,2] current not persisted
Person p1 [1,1,P1,Paul,01/31/2000,false,2] new, not persisted
em.merge(p1)
Hibernate: select person0_.ID as ID0_0_, person0_.DATE_OF_BIRTH as DATE_OF_BIRTH2_0_0_, person0_.MARRIED_NAME as MARRIED_NAME0_0_, person0_.NUMBER_OF_CHILDREN as NUMBER_OF_CHILDREN0_0_, person0_.LAST_NAME as LAST_NAME0_0_, person0_.FIRST_NAME as FIRST_NAME0_0_, person0_.VERSION as VERSION0_0_ from jpa01_person person0_ where person0_.ID=?
17:57:26.406 DEBUG IntegerType:133 - binding '1' to parameter: 1
17:57:26.406 DEBUG DateType:172 - returning 'January 31, 2000' as column: DATENAIS2_0_0_
17:57:26.406 DEBUG BooleanType:172 - returning 'true' as column: MARIE0_0_
17:57:26.406 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_0_
17:57:26.406 DEBUG StringType:172 - returning 'P1' as column: NOM0_0_
17:57:26.406 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_0_
17:57:26.406 DEBUG IntegerType:172 - returning '1' as column: VERSION0_0_
Person p1b [1,1,P1,Paul,01/31/2000,false,2] attached
end transaction
Hibernate: update jpa01_person set BIRTHDATE=?, MARRIED=?, CHILDREN=?, LAST_NAME=?, FIRST_NAME=?, VERSION=? where ID=? and VERSION=?
17:57:26,406 DEBUG DateType:133 - binding 'January 31, 2000' to parameter: 1
17:57:26.406 DEBUG BooleanType:133 - binding 'false' to parameter: 2
17:57:26.406 DEBUG IntegerType:133 - binding '2' to parameter: 3
17:57:26.421 DEBUG StringType:133 - binding 'P1' to parameter: 4
17:57:26.421 DEBUG StringType:133 - binding 'Paul' to parameter: 5
17:57:26.421 DEBUG IntegerType:133 - binding '2' to parameter: 6
17:57:26.421 DEBUG IntegerType:133 - binding '1' to parameter: 7
17:57:26.421 DEBUG IntegerType:133 - binding '1' to parameter: 8
Hibernate: select person0_.ID as ID0_, person0_.DATE_OF_BIRTH as DATE_OF_BIRTH2_0_, person0_.MARRIED_TO as MARRIED_TO0_, person0_.NUMBER_OF_CHILDREN as NUMBER_OF_CHILDREN0_, person0_.LAST_NAME as LAST_NAME0_, person0_.FIRST_NAME as FIRST_NAME0_, person0_.VERSION as VERSION0_ from jpa01_person person0_ order by person0_.LAST_NAME asc
17:57:26.453 DEBUG IntegerType:172 - returning '1' as column: ID0_
17:57:26.453 DEBUG DateType:172 - returning 'January 31, 2000' as column: DATENAIS2_0_
17:57:26.453 DEBUG BooleanType:172 - returning 'false' as column: MARIE0_
17:57:26.453 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_
17:57:26.453 DEBUG StringType:172 - returning 'P1' as column: NOM0_
17:57:26,453 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_
17:57:26,453 DEBUG IntegerType:172 - returning '2' as column: VERSION0_
[1,2,P1,Paul,01/31/2000,false,2]
BUILD SUCCESSFUL (total time: 3 seconds)

Question: Explain the relationship between the Java code and the displayed results.