Ambition is the path to success. Persistence is the vehicle you arrive in. Bill Bradley (a politician).
The EJB 3.0 specification includes a persistence specification called the Java Persistence API (JPA). It is an API for creating, removing, and querying Java objects called entities that can be used within both, a compliant EJB 3.0 container and a standard Java SE 5 environment.
In this chapter, we introduce the AppStore application, which will be a central theme of this book. The application includes a persistence layer designed around JPA, a session bean facade, and a frontend delivered with JavaServer Faces(JSF) web framework (we will see this in the next chapter).
In this chapter, we will discuss the following topics in more detail:
The arrival of an Enterprise Java Persistence standard, based on a "POJO" development model, fills a substantial gap in the Java EE platform. The previous attempt of EJB 2.x specification missed the mark and created the stereotype of EJB entity beans as awkward to develop and too heavy for many applications. Therefore, it never reached the level of widespread adoption or general approval in many sectors of the industry.
Software developers knew what they wanted, but many could not find it in the existing standards, so they decided to look elsewhere. What they found was proprietary persistence frameworks, both in the commercial and open source domains.
In contrast to EJB 2.x entity beans, the EJB 3.0 Java Persistence API is a metadata-driven POJO technology. That is, to save data held in Java objects into a database, our objects are not required to implement an interface, extend a class, or fit into a framework pattern.
Another key feature of JPA is the query language, called the Java Persistence Query Language(JPQL) that gives you a way to specify the semantics of queries in a portable way, independent of the particular database you are using in an enterprise environment. JPA queries resemble SQL queries in syntax, but operate against entity objects rather than directly with database tables.
Inspired by object-relational mapping (ORM) frameworks, such as Hibernate, JPA uses annotations to map objects to a relational database. JPA entities are POJOs that neither extend a class nor implement an interface. You don't even need XML descriptors for your mapping. Actually, the Java Persistence API is made up of annotations and only a few classes and interfaces. For example, we would mark the class Company
as entity, as follows:
@Entity public class Company { public Company () { } @Id String companyName; }
The last code snippet shows the minimal requirements for a class to be persistent, which are:
@javax.persistence.Entity
annotation @javax.persistence.Id
annotationI guess you would learn better from an example, so in the next section we will show how to create and deploy a sample JPA application on JBoss 5.
Our sample application will be a small store application, which tracks orders from a list of customers. The application will be developed using MySQL database, which is freely downloadable from http://dev.mysql.com/downloads/.
We suggest that you download MySQL 5.x, as well as MySQL Connector/J, which is used for Java Database Connectivity (JDBC). Once the download is complete, extract the file mysql-connector-java.jar
and place it in the JBOSS_HOME/common/lib
, thus making it available to all your server configurations.
We will create a database named appstore
, and then we will add a user named jboss
assigning it all privileges on the schemas, as shown in the following code snippet:
CREATE DATABASE appstore; USE appstore; CREATE USER 'jboss'@'localhost' IDENTIFIED BY 'jboss'; GRANT ALL PRIVILEGES ON appstore.* TO 'jboss'@'localhost' WITH GRANT OPTION;
Our simple schema will be made up of two tables—the Customer table that contains the list of all customers and the Item table that holds the list of all orders. The two tables are in a 1-n relationship and the Item table hosts a Foreign key (customer_id) that relates to the id of the Customer table.
CREATE TABLE appstore.customer ( `ID` int(10) unsigned NOT NULL auto_increment, `NAME` varchar(45) NOT NULL, `COUNTRY` varchar(45) NOT NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB; CREATE TABLE appstore.item ( `ID` int(10) unsigned NOT NULL auto_increment, `PRODUCT` varchar(45) default NULL, `PRICE` int(11) default NULL, `QUANTITY` int(11) default NULL, `CUSTOMER_ID` int(10) unsigned NOT NULL, PRIMARY KEY (`ID`), KEY `FK_orders` (`CUSTOMER_ID`), CONSTRAINT `FK_orders` FOREIGN KEY (`CUSTOMER_ID`) REFERENCES `customer` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB;
Once we are done with the data layer, we will create our entities. A preamble is necessary before we begin. Depending on the release of your Eclipse and JBoss Tools, you can model your project in several ways. In this example, we will create our entities using a wizard, so you will not need to write mapping classes manually. However, if you don't have the latest JBoss Tools installation, you still have other options available. For example, you can use Hibernate Tools to automatically generate mapping classes (this will be covered in Chapter 8,Developing Applications with JBoss and Hibernate).
Here we go. This project will contain both session beans and entities. Therefore, the simplest way to start is by creating a new EJB project and adding a JPA nature to it.
From the File menu, select New | Other | EJB | EJB Project, and name it AppStore—an application for handling a store. (I have to admit sometimes IT guys don't have much imagination!)
Now we will add JPA capabilities to our project. Right-click on the project and select Properties. The property we are interested in is Project Facets, which is the collection of capabilities added to our project. Select Java Persistence, as indicated in the following screenshot, and click OK.
Now your AppStore has got some new interesting options. Let's see where you can find them. Move to the root of your project and right-click on AppStore. Select New | Entities From Tables (see the following screenshot):
The JPA wizard will start. The process of reverse engineering the database into Java entities can be roughly divided into two steps as follows:
These steps are discussed in more detail as follows:
As we don't have any configured connection, you have to create a new one by clicking on the shiny little button at the top-right corner of your wizard, which is shown in the following screenshot:
In the next window, you have to select the database that contains your planned entities. Select MySQL (or whichever database you are using) from the next wizard and choose an appropriate label for our connection; here we select MySQL Connection.
Hit Next. A new window will let you specify a driver and connection details. As we do not have any JDBC driver configured yet, you should first click on the little icon (+) in the top-right corner.
The New Driver Definition dialog appears. First, select the driver template from the list of available options; in our case select MySQL 5.0. Then in the Jar List tab, point to the MySQL connector, which we downloaded earlier.
Okay, we are almost done. The last thing you need to select is the JDBC Driver properties in the New Connection Profile, which should match with your appstore database.
Before clicking Finish, we suggest you verify the connection with the Test Connection button. If for some reason the test fails, verify the General Properties and, of course, that the database is up and running.
Once the connection configuration is complete, it's time to roll your entities. You will be taken back to the first JPA wizard window, where you can now select the Schema as appstore and the Tables as customer and item:
Selecting Synchronize classes listed in persistence.xml will insert the entity definition in the main JPA configuration file—persistence.xml. This is not a mandatory step; however, Eclipse will complain later if the entities are not synchronized with the configuration file.
Click Next. In the subsequent wizard you have to deal with Table Associations. As you can see in the following screenshot, the JPA facet correctly recognizes the 1-n association between customer and item. In terms of entities, this will mean that the Customer
entity will contain a list of Item
entities ordered and the Item
class will contain a reference to the Customer
.
Click on the association diagram and choose names for the entity fields that will describe the relation.
In the last screenshot, customerFK will be a reference to the Customer
(in the Item
class) and items will hold the list of items ordered by a single customer. You are just one step away from finishing. Click Next. In the next wizard, you have to customize your entities.
The options that override the defaults are framed in the following screenshot:
First, we want our entities to use an Eager fetching strategy. What does this mean? By default, when we have an association between two entities, the referenced objects are lazy loaded, that is, fetching and loading the data from the persistent storage is done only when it is needed. In contrast to lazy loading, eager loading loads the full object tree at once. Lazy loading contributes a lot to improving the performance of an application, by limiting the number of objects that will be needed. However, if you need to traverse the tree of objects from the client side (as in our example), you will need to use the eager fetching strategy.
Then, select the class java.util.List as Collection properties type, which will be just fine for returning a vector of items and customers.
Lastly, choose the target package for the entities, that is, com.packtpub.jpa.example3
. If you want to preview the entities that are going to be generated, have a look at the next windows. Otherwise, you can complete the JPA wizard by clicking on the Finish button.
From the Project Explorer window, let's explore what the wizard has created for you:
If you have successfully completed all the wizard steps, your entities will be correctly packaged in the com.packtpub.jpa.example3
folder.
Let's have a look at the Customer
entity:
package com.packtpub.jpa.example3; EJB projectcustomer entityimport java.io.Serializable; import javax.persistence.*; import java.util.List; import static javax.persistence.FetchType.EAGER; @Entity [1] @Table(name="customer") [2] public class Customer implements Serializable { private static final long serialVersionUID = 1L; @Id [3] @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="ID") [4] private int id; @Column(name="COUNTRY") private String country; @Column(name="NAME") private String name; //bi-directional many-to-one association to Item @OneToMany(mappedBy="customerFK", fetch = EAGER) [5] private List<Item> items; public Customer() { } public int getId() { return this.id; } public void setId(int id) { this.id = id; } public String getCountry() { return this.country; } public void setCountry(String country) { this.country = country; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public List<Item> getItems() { return this.items; } public void setItems(List<Item> items) { this.items = items; } }
The first meaningful annotation is @Entity
[1] that declares the class as an entity. The @Table
[2] annotation is used to map the bean class with a database table.
The @Id
annotation [3] is mandatory; it describes the primary key of the table. Along with @Id
, there's the @GeneratedValue
annotation. This is used to declare that the database is in charge of generating the value.
Moving along, the @Column
[4] annotation is used to map the Java field with the corresponding database column. You can leave out this annotation, if the two elements are equal.
The @OneToMany
annotation [5] defines an association with one-to-many multiplicity. Actually, the Customer
class has many orders. The corresponding orders are contained in a List
collection.
In the JPA wizard, we have chosen the EAGER
attribute to the @OneToMany
annotation so that all orders are populated at the same time when we issue a query on the Customer
entity.
At this point we have inspected the Customer
entity. Let's have a look at the Item
entity:
package com.packtpub.jpa.example3;
EJB projectitem entityimport java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name="item")
public class Item implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="ID")
private int id;
@Column(name="PRICE")
private int price;
@Column(name="PRODUCT")
private String product;
@Column(name="QUANTITY")
private int quantity;
//bi-directional many-to-one association to Customer
@ManyToOne [1]
@JoinColumn(name="CUSTOMER_ID") [2]
private Customer customerFK;
public Item() {
}
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
public int getPrice() {
return this.price;
}
public void setPrice(int price) {
this.price = price;
}
public String getProduct() {
return this.product;
}
public void setProduct(String product) {
this.product = product;
}
public int getQuantity() {
return this.quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public Customer getCustomerFK() {
return this.customerFK;
}
public void setCustomerFK(Customer customerFK) {
this.customerFK = customerFK;
}
}
As you can see, the Item
entity has the corresponding @ManyToOne
[1] annotation, which naturally complements the @OneToMany
relationship. The @JoinColumn
[2], which has the same syntax of the @Column
annotation, notifies the JPA engine that the customerFK
field is mapped through the foreign key of the database customer_id
.
The entity API looks great and very intuitive, but how does the server know which database is supposed to store/query the entity objects? The persistence.xml
file (located in the META-INF
folder of your project) is the standard JPA configuration file. Believe it, this is a huge leap towards application server compatibility. By configuring this file, you can easily switch from one persistence provider to another and thus, also from one application server to another.
At the beginning, the persistence.xml
file contains just the mapped entities we created. We have to specify the persistence provider and the underlying datasource used.
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.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_1_0.xsd"> <persistence-unit name="AppStore" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>java:/MySqlDS</jta-data-source> <class>com.packtpub.jpa.example3.Customer</class> <class>com.packtpub.jpa.example3.Item</class> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/> </properties> </persistence-unit> </persistence>
The highlighted attributes need to be added to persistence.xml
. The attribute name is a mandatory element, which will be used to reference the persistence unit from our Enterprise JavaBeans. Then, we have specified the provider
factory, which will be used (in our case, it's HibernatePersistence)
. Another key attribute is the jta-data-source
that needs to point to a datasource component. The last property, hibernate.dialect
, will specify the O/R dialect class.
The only thing we have missed out is adding a datasource to the JBoss configuration. Beginning with the templates in docsexamplesjca
folder of JBoss, we will create a MySQL datasource, which points to our appstore schema:
<?xml version="1.0" encoding="UTF-8"?> <datasources> <local-tx-datasource> <jndi-name>MySqlDS</jndi-name> <connection-url>jdbc:mysql://localhost:3306/ appstore</connection-url> <driver-class>com.mysql.jdbc.Driver</driver-class> <user-name>jboss</user-name> <password>jboss</password> <exception-sorter-class-name>org.jboss.resource.adapter. jdbc.vendor.MySQLExceptionSorter </exception-sorter-class-name> <metadata> <type-mapping>mySQL</type-mapping> </metadata> </local-tx-datasource> </datasources>
Save this file as mysql-ds.xml
in the deploy
folder of your JBoss configuration.
With the classes that we just saw, we have completed the entity layer but we have not finished with the EJB project. Actually, if you allow client applications to directly access the entity, then the client requires knowledge of the entity implementation that goes beyond what clients should have. For instance, manipulating an entity requires knowledge of the entity relationships (such as associations and inheritance) that are involved, inappropriately exposing the client to all of the details of the business model.
The best practice advocated by Java EE architects is to provide a business interface to the entity subsystem; the standard interfaces for the persistence entities are Stateless Session Beans.
In our example, we are going to create two interfaces for our EJB clients—one for remote clients and another for local clients. Java SE clients will connect to our session beans through the remote interface, whereas web clients (described in the next chapter) will conveniently use the local interface.
At this stage you should be comfortable with stateless bean. Create a new session EJB 3 from the File menu: New | Other | EJB | EJB 3 Session Bean. The suggested name for this example is com.packtpub.ejb.example3.StoreManager
. This will also create the implementing class com.packtpub.ejb.StoreManagerBean
. Here is its interface contract:
package com.packtpub.ejb.example3; import java.util.List; import javax.ejb.Local; import com.packtpub.jpa.example3.Customer; import com.packtpub.jpa.example3.Item; public interface StoreManager { public void createCustomer(String country,String name); public List<Customer> findAllCustomers(); public Customer findCustomerByName(String name); public Customer findCustomerById(int id); public void saveOrder(int idCustomer, int price, int quantity,String product); public List<Item> findAllItems(int customerId); }
We have defined one method createCustomer
that will be used to add new customers to our store. Then we have added some finder methods: findAllCustomers, findAllCustomerByName
, and findCustomerById
that can be used to retrieve the single Customer
or the whole list.
Items ordered can be persisted by means of the saveOrder
method and queried with the findAllItems
method.
The interface we just saw will be extended by the StoreManagerLocal
and StoreManagerRemote
interfaces, which will provide respectively the local and remote view of the EJB:
package com.packtpub.ejb.example3; import javax.ejb.Local; @Local public interface StoreManagerLocal extends StoreManager { }
And here's the StoreManagerRemote
interface:
package com.packtpub.ejb.example3; import javax.ejb.Remote; @Remote public interface StoreManagerRemote extends StoreManager{ }
The concrete implementation class is contained in the StoreManagerBean
class:
package com.packtpub.ejb.example3; import java.util.List; import javax.ejb.Stateless; import javax.persistence.*; import org.jboss.ejb3.annotation.LocalBinding; import org.jboss.ejb3.annotation.RemoteBinding; import com.packtpub.jpa.example3.*; @Stateless @RemoteBinding(jndiBinding="AppStoreEJB/remote") @LocalBinding(jndiBinding="AppStoreEJB/local") public class StoreManagerBean implements StoreManagerLocal, StoreManagerRemote { @PersistenceContext(unitName="AppStore") private EntityManager em; public void createCustomer(String country,String name) { Customer customer = new Customer(); customer.setCountry(country); customer.setName(name); em.persist(customer); } public void saveOrder(int idCustomer, int price, int quantity, String product) { Customer customer = findCustomerById(idCustomer); Item order = new Item(); order.setCustomerFK(customer); order.setPrice(price); order.setQuantity(quantity); order.setProduct(product); em.persist(order); } public List<Item> findAllItems(int customerId) { Query query = em.createQuery("FROM Customer where id=:id"); query.setParameter("id", customerId); Customer customer = (Customer)query.getSingleResult(); List <Item>customerOrders = customer.getItems(); return customerOrders; } public Customer findCustomerByName(String customerName) { Query query = em.createQuery("FROM Customer where name=:name"); query.setParameter("name", customerName); Customer customer = (Customer)query.getSingleResult(); return customer; } public Customer findCustomerById(int id) { Query query = em.createQuery("FROM Customer where id=:id"); query.setParameter("id", id); Customer customer = (Customer)query.getSingleResult(); return customer; } public List<Customer> findAllCustomers() { Query query = em.createQuery("FROM Customer"); List<Customer> customerList = query.getResultList(); return customerList; } }
The @PersistenceContext
[1] annotation added to the EntityManager
field, injects a container-managed persistence context. You might think of this as an object-oriented connection to the RDBMS. The following diagram illustrates the whole sequence:
As you can see, the injected resource AppStore references the persistence unit defined in persistence.xml
. This in turn points to the jta
datasource named MySqlDS. The datasource (defined in the mySQL-ds.xml
configuration file) contains the connection details of the MySQL appstore schema.
The first method, createCustomer
, illustrates how you can perform the equivalent of a CREATE
SQL statement using JPA. As you can see, it's all about creating object instances [2]. Until you persist [3] your objects, however, all changes are held in memory.
The method saveOrder
works quite the same. Moving to the finder methods, we meet the findAllItems
method. If you have already worked with Hibernate, this should sound very familiar to you. In fact, JPA also uses a database-independent language, Java Persistence Query Language, to issues queries. It is a rich language that allows you to query any complex object's model (associations, inheritance, abstract classes, and so on) using common built-in database functions. There are functions that deal with strings (LOWER, UPPER, TRIM, CONCAT, LENGTH
, and SUBSTR
), numbers (ABS, SQRT
, and MOD)
, or collections (COUNT, MIN, MAX
, and SUM
). Like SQL, you can also sort the results (ORDER BY
) or group them (GROUP BY)
.
In our sample method, we issue a query [4], which is filtered by the id
parameter [5]. The use of parameters here is quite similar to plain PreparedStatements
bound variables. The EJB contains additional finder methods (findCustomerById, findCustomerByName
, and findAllCustomers)
that are modeled using the same steps as in findAllItems
.
At this point, our EJB layer is completed. Here's a screenshot of the Project Explorer that depicts the complete AppStore project:
It's now time to deploy the EJB project to our application server. Follow the same steps described in the session bean examples, that is, from the JBoss AS perspective right-click on the server node and choose Add and remove Project; then in the same window choose Full Publish in order to deploy the project. Verify from the server console that your EJB has bound correctly in the JNDI tree:
AppStoreEJB/remote - EJB3.x Default Remote Business Interface AppStoreEJB/local - EJB3.x Default Local Business Interface
The next section is about creating the client interface for this application.
Clients for session beans can be simple J2SE classes or server-side components such as JSP-servlets. We will now create a very simple Java class for interacting with the session remote interface. Add a new Java class to the project from File | New | Class and choose a name for it. Here's the com.packtpub.client.example3.TestAppStore
class:
package com.packtpub.client.example3; import java.util.*; import javax.naming.*; import com.packtpub.ejb.example3.*; import com.packtpub.jpa.example3.*; public class TestAppStore { public static void main(String[] args) throws Exception { Hashtable hash = new Hashtable(); hash.put("java.naming.factory.initial"," org.jnp.interfaces.NamingContextFactory"); hash.put("java.naming.provider.url","jnp://localhost:1099"); hash.put("java.naming.factory.url.pkgs"," org.jnp.interfaces"); Context ctx = new InitialContext(hash); StoreManager storeManager = (StoreManager)ctx. lookup("AppStoreEJB/remote"); // Create a Customer [1] storeManager.createCustomer("Usa","Clint Eastwood"); // Retrieve the Customer [2] Customer customer = storeManager.findCustomerByName ("Clint Eastwood"); // Save an order for an Item storeManager.saveOrder(customer.getId(), 1000,5, "Bycycle"); // Find all Items ordered by the Customer [3] List<Item> items = storeManager.findAllItems (customer.getId()); System.out.println("Listing orders for " +customer.getName()); Iterator <Item> iter = items.iterator(); while (iter.hasNext()) { Item item = iter.next(); System.out.println("----------------"); System.out.println("id #" +item.getId()); System.out.println("product #" +item.getProduct()); System.out.println("qty #" +item.getQuantity()); System.out.println("$ #" +item.getPrice()); } } }
The Java class should be self-explanatory at this stage. We have created a customer from Usa
[1] who places an order for an item [2]. The list of pending items ordered is queried by the findCustomerByName
method [3]. That's all folks!
3.139.233.94