1. Trang chủ >
  2. Công Nghệ Thông Tin >
  3. Kỹ thuật lập trình >

What’s New in JPA 2.1?

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 element defines the persistence provider, in our case, EclipseLink.

The persistence units list all the entities that should be managed by the entity manager. Here, the tag refers to

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">





org.eclipse.persistence.jpa.PersistenceProvider

org.agoncal.book.javaee7.chapter04.Book






value="database-and-scripts"/>


value="org.apache.derby.jdbc.ClientDriver"/>


value="jdbc:derby://localhost:1527/chapter04DB;create=true"/>















org.eclipse.persistence.jpa.PersistenceProvider

org.agoncal.book.javaee7.chapter04.Book








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">

4.0.0





parent

org.agoncal.book.javaee7

1.0





org.agoncal.book.javaee7

chapter04

1.0







org.eclipse.persistence

org.eclipse.persistence.jpa

2.5.0





120

www.it-ebooks.info



Chapter 4 ■ Java Persistence API





org.hibernate

hibernate-validator

5.0.0





org.apache.derby

derbyclient

10.9.1.0







org.apache.derby

derby

10.9.1.0

test





junit

junit

4.11

test













org.apache.maven.plugins

maven-compiler-plugin

2.5.1



1.7

1.7







org.apache.maven.plugins

maven-failsafe-plugin

2.12.4





integration-test



integration-test

verify













121

www.it-ebooks.info



Chapter 4 ■ Java Persistence API





org.codehaus.mojo

exec-maven-plugin

1.2.1







java









org.agoncal.book.javaee7.chapter04.Main













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 (test) and as well as the artifact for JUnit 4.

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



Xem Thêm
Tải bản đầy đủ (.pdf) (597 trang)

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×