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 3.1 above, there is only one
class, representing the object type Book
. So, we create a file
Book.java
in the folder src/pl/model
with the following code:
@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 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 add(...) {...} public static List<Book> retrieveAll(...) {...} public static Book retrieve(...) {...} public static void update(...) {...} public static void destroy(...) {...} public static void clearData(...) {...} public static void createTestData(...) {...} }
Notice
that the model class Book
is encoded 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.add
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.destroy
for deleting a Book
instance.
Book.createTestData
for creating a few example book records to be used as
test data.
Book.clearData
for clearing the book database table.
These methods are discussed in the following sections.
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
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 special Java objects representing
"entities" (or business objects), which can be serialized, or, in other words, turned into database records, or rows of a database
table. Consequently, they can be shown as a table like in Table 3.2.
Table 3.2. Book objects represented as a table
ISBN | Title | Year |
---|---|---|
006251587X | Weaving the Web | 2000 |
0465026567 | Gödel, Escher, Bach | 1999 |
0465030793 | I Am A Strange Loop | 2008 |
The data storage technology used in our example app is MySQL, and the (My)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'll show later in this tutorial 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.add
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 add( 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(); }
For storing 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;
}
For updating an existing Book
instance we first retrieve it from the database
with 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 destroy( 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 createTestData( 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
.