Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (15.81 MB, 597 trang )
Chapter 4 ■ Java Persistence API
This example follows the Maven directory structure, so classes and files described in Figure 4-4 have to be placed
in the following directories:
•
src/main/java: For the Book entity and the Main class;
•
src/main/resources: For the persistence.xml file used by the Main and BookIT classes as
well as the insert.sql database loading script;
•
src/test/java: For the BookIT class, which is used for integration testing; and
•
pom.xml: For the Maven POM, which describes the project and its dependencies on other
external modules and components.
Figure 4-4. Putting it all together
Writing the Book Entity
The Book entity, shown in Listing 4-7, needs to be developed under the src/main/java directory. It has several
attributes (a title, a price, etc.) of different data types (String, Float, Integer, and Boolean), some Bean Validation
annotations (@NotNull and @Size), as well as some JPA annotations.
•
@Entity informs the persistence provider that this class is an entity and that it should manage it.
•
The @NamedQueries and @NamedQuery annotations define two named-queries that use JPQL to
retrieve books from the database.
•
@Id defines the id attribute as being the primary key.
•
The @GeneratedValue annotation informs the persistence provider to autogenerate the
primary key using the underlying database id utility.
Listing 4-7. A Book Entity with a Named Query
package org.agoncal.book.javaee7.chapter04;
@Entity
@NamedQueries({
@NamedQuery(name = "findAllBooks", query = "SELECT b FROM Book b"),
@NamedQuery(name = "findBookH2G2", query = "SELECT b FROM Book b WHERE b.title ='H2G2'")
})
115
www.it-ebooks.info
Chapter 4 ■ Java Persistence API
public class Book {
@Id @GeneratedValue
private Long id;
@NotNull
private String title;
private Float price;
@Size(min = 10, max = 2000)
private String description;
private String isbn;
private Integer nbOfPage;
private Boolean illustrations;
// Constructors, getters, setters
}
Note that for better readability I’ve omitted the constructor, getters, and setters of this class. As you can see
in this code, except for a few annotations, Book is a simple POJO. Now let’s write a Main class that persists a book to
the database.
Writing the Main Class
The Main class, shown in Listing 4-8, is under the same package as the Book entity. It commences by creating a new
instance of Book (using the Java keyword new) and sets some values to its attributes. There is nothing special here,
just pure Java code. It then uses the Persistence class to get an instance of an EntityManagerFactory that refers
to a persistence unit called chapter04PU, which I’ll describe later in the section “Writing the Persistence Unit.”
This factory creates an instance of an EntityManager (em variable). As mentioned previously, the entity manager
is the central piece of JPA in that it is able to create a transaction, persist the book object using the EntityManager.
persist() method, and then commit the transaction. At the end of the main() method, both the EntityManager and
EntityManagerFactory are closed to release the provider’s resources
Listing 4-8. A Main Class Persisting the Book Entity
package org.agoncal.book.javaee7.chapter04;
public class Main {
public static void main(String[] args) {
// Creates an instance of book
Book book = new Book("H2G2", "The Hitchhiker's Guide to the Galaxy", 12.5F,
"1-84023-742-2", 354, false);
// Obtains an entity manager and a transaction
EntityManagerFactory emf = Persistence.createEntityManagerFactory("chapter04PU");
EntityManager em = emf.createEntityManager();
// Persists the book to the database
EntityTransaction tx = em.getTransaction();
tx.begin();
em.persist(book);
tx.commit();
116
www.it-ebooks.info
API
// Closes the entity manager and the factory
em.close();
emf.close();
}
}
Again, for readability I’ve omitted exception handling. If a persistence exception occurs, you would have to roll
back the transaction, log a message, and close the EntityManager in the finally block.
Writing the BookIT Integration Test
One complaint made about the previous versions of Entity CMP 2.x was the difficulty of integration testing persistent
components. One of the major selling points of JPA is that you can easily test entities without requiring a running
application server or live database. But what can you test? Entities themselves usually don’t need to be tested in
isolation. Most methods on entities are simple getters or setters with only a few business methods. Verifying that a
setter assigns a value to an attribute and that the corresponding getter retrieves the same value does not give any extra
value (unless a side effect is detected in the getters or the setters). So unit testing an entity has limited interest.
What about testing the database queries? Making sure that the findBookH2G2 query is correct? Or injecting data
into the database and testing complex queries bringing multiple values? These integration tests would need a real
database with real data, or you would unit test in isolation with mocks to simulate a query. Using an in-memory
database and JPA transactions is a good compromise. CRUD operations and JPQL queries can be tested with a very
lightweight database that doesn’t need to run in a separate process (just by adding a jar file to the class path). This is
how you will run our BookIT class, by using the embedded mode of Derby.
Maven uses two different directories, one to store the main application code and another for the test classes.
The BookIT class, shown in Listing 4-9, goes under the src/test/java directory and tests that the entity manager can
persist a book and retrieve it from the database and checks that Bean Validation constraints are raised.
Listing 4-9. Test Class That Creates and Retrieves Books from the Database
public class BookIT {
private static EntityManagerFactory emf =
Persistence.createEntityManagerFactory("chapter04TestPU");
private EntityManager em;
private EntityTransaction tx;
@Before
public void initEntityManager() throws Exception {
em = emf.createEntityManager();
tx = em.getTransaction();
}
@After
public void closeEntityManager() throws Exception {
if (em != null) em.close();
}
117
www.it-ebooks.info
Chapter 4 ■ Java Persistence API
@Test
public void shouldFindJavaEE7Book() throws Exception {
Book book = em.find(Book.class, 1001L);
assertEquals("Beginning Java EE 7", book.getTitle());
}
@Test
public void shouldCreateH2G2Book() throws Exception {
// Creates an instance of book
Book book = new Book("H2G2", "The Hitchhiker's Guide to the Galaxy", 12.5F,
"1-84023-742-2", 354, false);
// Persists the book to the database
tx.begin();
em.persist(book);
tx.commit();
assertNotNull("ID should not be null", book.getId());
// Retrieves all the books from the database
book = em.createNamedQuery("findBookH2G2", Book.class).getSingleResult();
assertEquals("The Hitchhiker's Guide to the Galaxy", book.getDescription());
}
@Test(expected = ConstraintViolationException.class)
public void shouldRaiseConstraintViolationCauseNullTitle() {
Book book = new Book(null, "Null title, should fail", 12.5F,
"1-84023-742-2", 354, false);
em.persist(book);
}
}
Like the Main class, BookIT in Listing 4-9 needs to create an EntityManager instance using an
EntityManagerFactory. To initialize these components, you can use the JUnit 4 fixtures. The @Before and @After
annotations allow executions of some code before and after a test is executed. That’s the perfect place to create and
close an EntityManager instance and get a transaction.
The shouldFindJavaEE7Book() test case relies on data already being present in the database (more on
insert.sql script later) as it finds the book with id 1001 and checks that the title is "Beginning Java EE 7". The
shouldCreateH2G2Book() method persists a book (using the EntityManager.persist() method) and checks whether
the id has been automatically generated by EclipseLink (with assertNotNull). If so, the findBookH2G2 named query
is executed and checks whether the returned book has "The Hitchhiker's Guide to the Galaxy" as its description.
The last test case creates a Book with a null title, persists it, and checks that a ConstraintViolationException has
been thrown.
Writing the Persistence Unit
As you can see in the Main class (Listing 4-8), the EntityManagerFactory needs a persistence unit called chapter04PU. And
the integration test BookIT (Listing 4-9) uses a different persistent unit (chapter04TestPU). These two persistence units
have to be defined in the persistence.xml file under the src/main/resources/META-INF directory (see Listing 4-10).
This file, required by the JPA specification, is important as it links the JPA provider (EclipseLink in our case) to the
118
www.it-ebooks.info
Chapter 4 ■ Java Persistence API
database (Derby). It contains all the necessary information to connect to the database (URL, JDBC driver, user, and
password) and informs the provider of the database schema-generation mode (drop-and-create means that tables
will be dropped and then created). The
The persistence units list all the entities that should be managed by the entity manager. Here, the
the Book entity.
The two persistent units differ in the sense that chapter04PU uses a running Derby database and
chapter04TestPU an in memory one. Notice that both use the load script insert.sql to insert data into the database
at runtime.
Listing 4-10. persistence.xml File
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
value="database-and-scripts"/>
value="org.apache.derby.jdbc.ClientDriver"/>
value="jdbc:derby://localhost:1527/chapter04DB;create=true"/>
value="org.apache.derby.jdbc.EmbeddedDriver"/>
value="jdbc:derby:memory:chapter04DB;create=true"/>
119
www.it-ebooks.info
Chapter 4 ■ Java Persistence API
Writing an SQL Script to Load Data
Both persistence units defined in Listing 4-10 load the insert.sql script (using the javax.persistence.sql-loadscript-source property). This means that the script in Listing 4-11 is executed for database initialization and inserts
three books.
Listing 4-11. insert.sql File
INSERT INTO BOOK(ID, TITLE, DESCRIPTION, ILLUSTRATIONS, ISBN, NBOFPAGE, PRICE) values
(1000, 'Beginning Java EE 6', 'Best Java EE book ever', 1, '1234-5678', 450, 49)
INSERT INTO BOOK(ID, TITLE, DESCRIPTION, ILLUSTRATIONS, ISBN, NBOFPAGE, PRICE) values
(1001, 'Beginning Java EE 7', 'No, this is the best ', 1, '5678-9012', 550, 53)
INSERT INTO BOOK(ID, TITLE, DESCRIPTION, ILLUSTRATIONS, ISBN, NBOFPAGE, PRICE) values
(1010, 'The Lord of the Rings', 'One ring to rule them all', 0, '9012-3456', 222, 23)
If you look carefully at the BookIT integration test (method shouldFindJavaEE7Book) you’ll see that the test expects
the book id 1001 to be in the database. Thanks to the database initialization, this is done before the tests are run.
Compiling and Testing with Maven
We have all the ingredients to compile and test the entity before running the Main application: the Book entity, the
BookIT integration test, and the persistence units binding the entity to the Derby database. To compile this code,
instead of using the javac compiler command directly, you will use Maven. You must first create a pom.xml file that
describes the project and its dependencies such as the JPA and Bean Validation API. You also need to inform Maven
that you are using Java SE 7 by configuring the maven-compiler-plugin as shown in Listing 4-12.
Listing 4-12. Maven pom.xml File to Compile, Test, and Execute the Application
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">
120
www.it-ebooks.info
Chapter 4 ■ Java Persistence API
121
www.it-ebooks.info
Chapter 4 ■ Java Persistence API
First, to be able to compile the code, you need the JPA API that defines all the annotations and classes that are
in the javax.persistence package. You will get these and the EclipseLink runtime (i.e., the persistence provider)
through the org.eclipse.persistence.jpa artifact ID. As seen in the previous chapter, the Bean Validation API is
in the hibernate-validator artifact. You then need the JDBC drivers to connect to Derby. The derbyclient artifact
ID refers to the jar that contains the JDBC driver to connect to Derby running in server mode (the database runs in
a separate process and listens to a port) and the derby artifact ID contains the classes to use Derby as an embedded
database. Note that this artifact ID is scoped for testing (
To compile the classes, open a command-line interpreter in the root directory that contains the pom.xml file and
enter the following Maven command:
$ mvn compile
You should see the BUILD SUCCESS message informing you that the compilation was successful. Maven creates
a target subdirectory with all the class files as well as the persistence.xml file. To run the integration tests you also
rely on Maven by entering the following command:
$ mvn integration-test
You should see some logs about Derby creating a database and tables in memory. The BookIT class is then
executed, and a Maven report should inform you that the three test cases are successful:
Results :
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO] -----------------------------------------------------------------------[INFO] BUILD SUCCESS
[INFO] -----------------------------------------------------------------------[INFO] Total time: 5.192s
[INFO] Finished
[INFO] Final Memory: 18M/221M
[INFO] -----------------------------------------------------------------------
122
www.it-ebooks.info
Chapter 4 ■ Java Persistence API
Running the Main Class with Derby
Before executing the Main class, you need to start Derby. The easiest way to do this is to go to the $DERBY_HOME/bin
directory and execute the startNetworkServer script. Derby starts and displays the following messages in
the console:
Security manager installed using the Basic server security policy.
Apache Derby Network Server - 10.9.1.0 - (802917) started and ready to accept
connections on port 1527
The Derby process is listening on port 1527 and waiting for the JDBC driver to send any SQL statement. To
execute the Main class, you can use the java interpreter command or use the exec-maven-plugin as follows:
$ mvn exec:java
When you run the Main class, several things occur. First, Derby will automatically create the chapter04DB
database once the Book entity is initialized. That is because in the persistence.xml file you’ve added the create=true
property to the JDBC URL.
value="jdbc:derby://localhost:1527/chapter04DB;create=true"/>
This shortcut is very useful when you are in development mode, as you do not need any SQL script to create the
database. Then, the javax.persistence.schema-generation-action property informs EclipseLink to automatically
drop and create the BOOK table. Finally, the book is inserted into the table (with an automatically generated ID).
Let’s use Derby commands to display the table structure: enter the ij command in a console (as explained in
Appendix A, the $DERBY_HOME/bin directory has to be in your PATH variable). This runs the Derby interpreter, and
you can execute commands to connect to the database, show the tables of the chapter04DB database (show tables),
check the structure of the BOOK table (describe book), and even show its content by entering SQL statements such as
SELECT * FROM BOOK.
$ ij
version 10.9.1.0
ij> connect 'jdbc:derby://localhost:1527/chapter04DB';
ij> show tables;
TABLE_SCHEM
|TABLE_NAME
|REMARKS
-----------------------------------------------------------------------APP
|BOOK
|
APP
|SEQUENCE
|
ij> describe book;
COLUMN_NAME
|TYPE_NAME|DEC&|NUM&|COLUM&|COLUMN_DEF|CHAR_OCTE&|IS_NULL&
-----------------------------------------------------------------------ID
|BIGINT
|0
|10 |19
|NULL
|NULL
|NO
TITLE
|VARCHAR |NULL|NULL|255
|NULL
|510
|YES
PRICE
|DOUBLE
|NULL|2
|52
|NULL
|NULL
|YES
ILLUSTRATIONS |SMALLINT |0
|10 |5
|0
|NULL
|YES
DESCRIPTION
|VARCHAR |NULL|NULL|255
|NULL
|510
|YES
ISBN
|VARCHAR |NULL|NULL|255
|NULL
|510
|YES
NBOFPAGE
|INTEGER |0
|10 |10
|NULL
|NULL
|YES
123
www.it-ebooks.info
Chapter 4 ■ Java Persistence API
Coming back to the code of the Book entity (Listing 4-7), because you’ve used the @GeneratedValue annotation
(to automatically generate an ID), EclipseLink has created a sequence table to store the numbering (the SEQUENCE
table). For the BOOK table structure, JPA has followed certain default conventions to name the table and the columns
after the entity name and attributes (e.g., Strings are mapped to VARCHAR(255)).
Checking the Generated Schema
In the persistence.xml file described in Listing 4-10 we have informed EclipseLink to generate the schema database
as well as creating the drop and create scripts, thanks to the following property:
value="drop-and-create"/>
value="drop-and-create"/>
By default the provider will generate two SQL scripts: createDDL.jdbc (Listing 4-13) with all the SQL statements
to create the entire database and the dropDDL.jdbc (Listing 4-14) to drop all the tables. This is useful when you need
to execute scripts to create a database in your continuous integration process.
Listing 4-13. The createDDL.jdbc Script
CREATE TABLE BOOK (ID BIGINT NOT NULL, DESCRIPTION VARCHAR(255),
ILLUSTRATIONS SMALLINT DEFAULT 0, ISBN VARCHAR(255), NBOFPAGE INTEGER,
PRICE FLOAT, TITLE VARCHAR(255), PRIMARY KEY (ID))
CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(15),
PRIMARY KEY (SEQ_NAME))
INSERT INTO SEQUENCE (SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 0)
Listing 4-14. The dropDDL.jdbc Script
DROP TABLE BOOK
DELETE FROM SEQUENCE WHERE SEQ_NAME = 'SEQ_GEN'
Summary
This chapter contained a quick overview of JPA 2.1. Like most of the other Java EE 7 specifications, JPA focuses on a
simple object architecture, leaving its ancestor, a heavyweight component model (a.k.a. EJB CMP 2.x), behind. The
chapter also covered entities, which are persistent objects that map metadata through annotations or XML.
Thanks to the “Putting It All Together” section, you have seen how to run a JPA application with EclipseLink and
Derby. Integration testing is an important topic in projects, and, with JPA and in memory databases such as Derby, it
is now very easy to test persistence.
In the following chapters, you will learn more about the main JPA components. Chapter 5 will show you how to
map entities, relationships, and inheritance to a database. Chapter 6 will focus on the entity manager API, the JPQL
syntax, and how to use queries and locking mechanisms as well as explaining the life cycle of entities and how to hook
business logic in callback methods in entities and listeners.
124
www.it-ebooks.info
Chapter 5
Object-Relational Mapping
In the previous chapter I went through the basics of object-relational mapping (ORM), which is basically mapping
entities to tables and attributes to columns. I also introduced configuration by exception which allows the JPA provider
to map an entity to a database table using all the defaults. But defaults are not always suitable, especially if you map
your domain model to an existing database. JPA comes with a rich set of metadata so you can customize the mapping.
In this chapter I cover elementary mapping, but I also concentrate on more complex mappings such as
relationships, composition, and inheritance. A domain model is made of objects interacting with each other.
Objects and databases have different ways to store relationship information (references in objects and foreign keys
in databases). Inheritance is not a feature that relational databases naturally have, and therefore the mapping is
not as obvious. In this chapter I go into some detail and show examples that demonstrate how to map attributes,
relationships, and inheritance from a domain model to a database.
Elementary Mapping
There are significant differences in the way Java data handles data compared to the way a relational database handles
data. In Java, we use classes to describe both attributes for holding data and methods to access and manipulate
that data. Once we define a class, we can create as many instances as we need with the new keyword. In a relational
database, data are stored in non-object structures (columns and rows), and dynamic behavior is stored functionally
as table triggers and stored procedures that are not bound tightly to the data structures, as they are with objects.
Sometimes mapping Java objects to the underlying database can be easy, and the default rules can be applied. At other
times, these rules do not meet your needs, and you must customize the mapping. Elementary mapping annotations
focus on customizing the table, the primary key, and the columns, and they let you modify certain naming conventions
or typing (not-null column, length, etc.).
Tables
Rules for configuration-by-exception mapping state that the entity and the table name are the same (a Book entity is
mapped to a BOOK table, an AncientBook entity is mapped to an ANCIENTBOOK table, etc.). This might suit you in most
cases, but you may want to map your data to a different table, or even map a single entity to several tables.
@Table
The @javax.persistence.Table annotation makes it possible to change the default values related to the table.
For example, you can specify the name of the table in which the data will be stored, the catalog, and the database schema.
You can also define unique constraints to the table using the @UniqueConstraint annotation in conjunction with
@Table. If the @Table annotation is omitted, the name of the table will be the name of the entity. If you want to change
the name to T_BOOK instead of BOOK, you would do as shown in Listing 5-1.
125
www.it-ebooks.info