Introduction to using Java Persistence API in a web application in Java EE environment
The Java Persistence API is the standard API for the management of persistence and object/relational mapping in Java EE 5 platform. Every Java EE 5 compatible application server will support this API. In this entry I will show how to use this API from a web application in Java EE environment.
Since the public review version of Java Persistence API specification, there have been a number of significant changes about packaging of applications that use Java Persistence API. So I will also show how to package such an application in a portable way. In this exercise, I will not use any kind of IDE because I don't want any magic! This article is going to be a long one because I am trying to explain the steps in detail as well as why certain things are done the way they are. But the steps are very simple as you can see from the README.
What is the example?
It's a web application which has a login page and a new user registration page. It talks to a database where user details are stored. The complete sample is available here. There is a README inside that zip file as well.
The Reference Implementation(RI) of the Java Persistence API is being done in glassfish project. You can download the latest promoted build and try out the sample yourself.
Although I am using the RI to build and run this sample, atmost you have to do some very minor changes to persistence.xml and build.xml to make this app build and run in any other application server that supports Java EE 5 specification.
Step #1: Write entity bean UserCredential.java
Points to note about this entity bean are:
1) This is a Plain Old Java Object (POJO): It does not extend any predefined class, nor does it implement any particular interface other than java.io.Serializable. An entity bean must implement java.io.Serializable interface if it is used in RMI-IIOP interface. Since in this example, RMI-IIOP is not used, technically this bean does not have to implement this interface. Never-the-less it is a good idea to make an entity bean RMI-IIOP ready, so I have done so.
2) There is no deployment descriptor needed to specify that it is an entity bean. Instead the class has been annotated as @Entity as shown below:
public class UserCredential implements java.io.Serializable.
The persistence provider automatically determines whether we are annotating FIELDs or PROPERTIEs. In this case, we have decided to annotate fields.
3) Every entity bean must have an identity. In our case, it is specified using @Id as below:
@Id private String name;
I have chosen to use @Id because we have a single field primary key. Other annotations like @IdClass, @EmbeddedId are typcally used for composite primary key.
4) Also note that we have not used @GeneratedValue along with @Id. When an id field is not annotated with @GeneratedValue, it means that user is responsible for setting the identity. Provider will not set the id field.
5) Although it is possible to specify exact table name and column names for the entity bean, we can rely on the default mapping that the specification defines. Because of the default mapping rules, UserCredential bean gets mapped to a table called USERCREDENTIAL, name & password fiels get mapped to NAME and PASSWORD columns respectively.
Step #2: Define a persistence unit
A Persistence Unit (PU) is a logical grouping of a set of related entity beans. A PU also contains configuration details about the entity managers that are going to manage these entity beans. Actually the ocnfiguration is applied to an EntityManagerFactory which in turn creates homogenious entity manager instances. To define a PU we need to write a
1) One persistence.xml can be used to define multiple PUs, but in this case we have defined only one PU by name em1.
2) We need not specify any other elements/attributes, as the default values are just fine for most applications. e.g. by default the entity manager's transaction type is JTA.
3) There is no need to enumerate all the entity bean class names inside
Step #3: Write LoginServlet.java
Points to note about this servlet are:
1) It talks to database using EntityManager API. It declares dependency on an EntityManagerFactory using @PersistenceUnit annotation:
@PersistenceUnit private EntityManagerFactory emf;
Since there is only one Persistence Unit(PU) defined in the scope of the web-app, there is no need to specify the unitName in @PersistenceUnit. Please also note that an injected variable in a servlet or ejb must not be declared static or final. In our example, emf follows this rule as well.
2) Also note that the servlet does not have an instance field of type EntityManager. This is because EntityManager is not thread safe. Since this servlet is not denoted as a SingleThreadModel servlet, one instance of servlet gets shared by mutiple clients and service method of servlet can be called by multiple threads concurrently. So we can't directly inject an EntityManager. Instead we inject an EntityManagerFactory which is thread safe.
3) In the service(), we create an EntityManager using the following code:
EntityManager em = emf.createEntityManager();
We also close the EntityManager in the finally block.
4) When user tries to login using a user name and password, it uses
UserCredential credential = em.find(UserCredential.class, name);
to find a matching UserCredential entity in the database.
5) Since EntityManager.find does not require a transaction to be started, the servlet does not have to begin a transaction before calling em.find().
Step #4: Write RegistrationServlet.java
1) It uses an injected EntityManagerFactory.
2) It uses
to create a new entity in the in the database .
3) Since EntityManager.persist() needs to be called in the context of a transaction, this servlet begins a transaction by calling
and commits the tx before returning.
4) Also note that it uses an injected UserTransaction object as follows:
@Resource private UserTransaction utx;
Unlike EntityManager, UserTransaction is thread safe, so it is OK to inject it into a servlet.
5) Also see how we ensure that when we close the EntityManager there is no active transaction context, otherwise EntityManager.close() will throw an exception.
Step #5: Write web.xml
We are having to write an web.xml only because we have to define a couple of request path mappings to servlets.
Step #6: Write a couple of html files
Refer to login.html and registration.html inside the sample zip file. They are used to call the servlets.
Step #7: Build using build.xml
This is a very simple build.xml just to demonstrate the compilation and packaging process.
The build targets are:
build -- This builds the war file.
clean -- cleans
verify -- verify uses a tool called verifier that checks compliance of the application against Java EE spec.
deploy -- deploys the war file
undeploy -- undeploys the war file
The last three targets are specific to Java EE 5 Reference Implementation (a.k.a. Sun Java System Application Server 9 PE).
As you can see, to compile only library needed is javaee.jar which contains the Java EE 5 platform APIs.
Packaging is done in two steps:
a) build-entities target which compiles only entity beans, copies persistence.xml to output directory and make a jar file called entities.jar. See entities.jar contains persistence.xml in META-INF dir. This jar file is used during compilation of servlets. More over this is also bundled inside the war file's WEB-INF/lib directory.
b) build-web-app1 target which compiles the servlets, copies web.xml and html files to output directories amd makes a war file called web-app1.war. A few points worth noting here are:
in addition to javaee.jar, entities.jar is also used while compiling the servlets.
servlet classes are bundled in WEB-INF/classes directory.
entities.jar is bundled inside WEB-INF/lib directory
Another packaging option is to package META-INF/persistence.xml and entity classes in WEB-INF/classes. But I just feel having a separate jar file with entities in it keeps things clean. It improves reusability.
Step #8: Set up a data source
By default entity manager uses the default pre-configured data source with JNDI name jdbc/__default that glassfish comes with. This data source talks to a Derby database called sun-appserv-samples. Refer to the README where I have listed the command needed to create the tables in Derby. Glassfish has a feature called Java2DB which can autocreate the database schema during deployment, but because of a bug this feature is not currently supported for Derby. Very soon (in a week or so), this bug is going to be fixed. Watch out glassfish EJB 3.0 persistence project page.
Step #9: Run the web-app
Type http://localhost:8080/web-app1/login.html in the browser. Replace localhost & 8080 by host and port as appropriate in your env.
Hope this entry is useful. In the next entry, we will take a step further and write an enterprise application where a web application talks to database using session beans.