In the second step, we create the model classes for our app, using a
separate Java source code file (with extension .java
)
for each model class. In the information design model shown in Figure 4.1 above, there is only
one class, representing the object type Book
. So, we create a
file Book.java
in the folder
src/pl/m
with the following code:
package pl.m; @Entity @Table( name="books") public class Book { @Id private String isbn; private String title; private int year; // default constructor (required for entity classes) public Book() {} // constructor public Book( String isbn, String title, int year) { this.setIsbn( isbn); this.setTitle( title); this.setYear( year); } // getter and setter methods ... }
Notice that the model class Book
is coded as a
JPA entity
class, which is a JavaBean class enriched with the
following JPA annotations:
The annotation @Entity
designates a class as an
entity class implying that the instances of this class will be
stored persistently.
The annotation @Table( name="books")
specifies
the name of the database table to be used for storing the
Book
entities. This annotation is optional and defaults
to a table name being the same as the class name but in lower case
(that is, it would be book
in our case).
The @Id
annotation marks the standard identifier
attribute, implying that the corresponding column of the underlying
SQL database table is designated as the PRIMARY KEY. In our example,
isbn
is used as the standard identifier attribute, and
the corresponding isbn
column of the books
table stores the primary key values.
In the entity class Book
, we also define the following
static (class-level) methods:
Book.create
for creating a new Book
instance.
Book.retrieveAll
for retrieving all
Book
instances from the persistent data store.
Book.retrieve
for retrieving a specific
Book
instance from the persistent data store by means
of its standard identifier.
Book.update
for updating an existing
Book
instance.
Book.delete
for deleting a Book
instance.
Book.generateTestData
for creating a few example
book records to be used as test data.
Book.clearData
for clearing the book database
table.
The signatures of these methods, which are discussed in more detail in the following subsections, are shown in the following program listing:.
// getter and setter methods public String getIsbn() {return isbn;} public void setIsbn( String isbn) {this.isbn = isbn;} public String getTitle() {return title;} public void setTitle( String title) {this.title = title;} public int getYear() {return year;} public void setYear( int year) {this.year = year;} // CRUD data management methods public static void create(...) {...} public static List<Book> retrieveAll(...) {...} public static Book retrieve(...) {...} public static void update(...) {...} public static void delete(...) {...} public static void clearData(...) {...} public static void generateTestData(...) {...}
The JPA architecture for data management and object-to-storage
mapping is based on the concept of an entity
manager, which provides the data management methods
persist
for saving a newly created or updated entity, find
for retrieving an entity, and remove
for deleting an entity.
Since the database access operations of an entity manager are
executed in the context of a transaction, our data management
methods have a parameter ut
of type
UserTransaction
. Before the entity manager can invoke the
database write method persist
, a transaction needs to be
started with ut.begin()
. After all write (and state change)
operations have been performed, the transaction is completed (and all
changes are committed) with ut.commit()
.
The instances of our entity class Book
are Java
objects representing "entities" (or business
objects), which can be serialized, or, in other words, converted to
records (or rows) of a database table, as shown in Table 4.1.
The data storage technology used in our example app is MySQL, and the SQL code used to create the schema for the database table
books
is the following:
CREATE TABLE IF NOT EXISTS books ( isbn VARCHAR(10) NOT NULL PRIMARY KEY, title VARCHAR(128), year SMALLINT );
While it is also possible to create the database schema manually (with the help of CREATE TABLE statements such as the one above), we show below how the database schema can be automatically generated by JPA. In both cases, the database setup, including a user account and the associated rights (create, update, etc), must be done manually before the JPA application can connect to it.
The Book.create
method takes care of creating a new
Book
instance and saving it to a database with the help of
an 'entity manager':
public static void create( EntityManager em, UserTransaction ut, String isbn, String title, int year) throws Exception { ut.begin(); Book book = new Book( isbn, title, year); em.persist( book); ut.commit(); }
To store the new object, the persist
method of the
given 'entity manager' is invoked. It is responsible for creating the
corresponding SQL INSERT
statement and executing it.
The instances of an entity class, such as Book
, are
retrieved from the database with the help of a corresponding query
expressed in the Java Persistence Query Language (JPQL). These queries are similar to SQL queries. They use
class names Instead of table names, property names instead of column
names, and object variables instead of row variables.
In the Book.retrieveAll
method, first a
query
asking for all Book
instances is
created, and then this query is executed with
query.getResultList()
assigning its answer set to the list
variable books
:
public static List<Book> retrieveAll( EntityManager em) {
Query query = em.createQuery( "SELECT b FROM Book b", Book.class);
List<Book> books = query.getResultList();
return books;
}
To update an existing Book
instance, first we need to
retrieve it from the database, by using em.find
, and then
set those attributes the value of which has changed:
public static void update( EntityManager em, UserTransaction ut, String isbn, String title, int year) throws Exception { ut.begin(); Book book = em.find( Book.class, isbn); if (!title.equals( book.getTitle())) book.setTitle( title); if (year != book.getYear()) book.setYear( year); ut.commit(); }
Notice that, when invoking the find
method for
retrieving an entity, the first argument must be a reference to the
entity class concerned (here: Book.class
), so the JPA
runtime environment can identify the database table from which to
retrieve the entity's data. The second argument must be the value of the
entity's primary key.
Notice that in the update case, we do not have to use
persist
for saving the changes. Saving is automatically
managed by the JPA runtime environment when we complete the transaction
with ut.commit()
.
A book entity can be deleted from the database as shown in the following example code:
public static void delete( EntityManager em, UserTransaction ut, String isbn) throws Exception { ut.begin(); Book book = em.find( Book.class, isbn); em.remove( book); ut.commit(); }
To delete an entity from the database, we first need to retrieve
it with the help of the find
method as in the update case.
Then, the remove
method has to be invoked by the 'entity
manager', and finally the transaction is completed with
ut.commit()
.
For being able to test our code, we may create some test data and save it in our database. We can use the following procedure for this:
public static void generateTestData( EntityManager em,
UserTransaction ut) throws Exception {
Book book = null;
Book.clearData( em, ut); // first clear the books table
ut.begin();
book = new Book("006251587X","Weaving the Web", 2000);
em.persist( book);
book = new Book("0465026567","Gödel, Escher, Bach", 1999);
em.persist( book);
book = new Book("0465030793","I Am A Strange Loop", 2008);
em.persist( book);
ut.commit();
}
After clearing the database, we successively create 3 instances of
the Book
entity class and save them with the help of
persist
.
The following procedure clears our database by deleting all rows:
public static void clearData( EntityManager em,
UserTransaction ut) throws Exception {
ut.begin();
Query deleteStatement = em.createQuery( "DELETE FROM Book");
deleteStatement.executeUpdate();
ut.commit();
}
JPA does not provide a direct method to drop the entire population
of a specific class from the database. However, this can be easily
obtained by using a JPQL statement as shown in the above code. The JPQL
code can be read as: delete all rows from the
database table associated with the entity class
Book
.