Using Java Persistence API in Java EE Platform - Part II
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. Earlier I had written about how to use Java Persistence API in a web application. This time I shall extend the example to include EJBs and application client so that we have a multi-tier (web->ejb->db and appclient->ejb->db) Java EE app. The focus of this exercise is to show how simple it is to develop such complex multi-tier Java EE application in a portable way. We will see how to package such an application so that it is not only portable, but also efficient. It uses the library directory facility to package common classes. In the process we shall see various injections like @EJB and @PersistenceContext in use. Like last time, I will not use any kind of IDE because Java EE 5 is Easy to Use. More over IDEs tend to do things behind the screen and that hampers learning. Because of the walkthrough nature of this article, it's to be a long one, but don't get overwhelmed by the size! At each critical step , I shall try to explain why certain things are done the way they are. If you are impatient, you can download the complete sample, unzip and run 'ant deploy' to see the sample in action. The actual steps are very simple as you can see from this README.
What is the example?
The ear file has three modules: viz: a web module, an ejb module and an appclient module. The web module has a login page and a new user registration page. They internally use two servlets. The servlets talk to the ejb which uses Java Persistence API to access user details that are stored in a database. The appclient module also talks to the ejb module. The complete sample is available here.
An implementation 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 glassfish 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. This sample also uses a proprietary feature called Java2DB. But that does not make this a non-portable application as it uses vendor extension section of persistence.xml to do this.
Structure of the ear file :
The final ear looks like this:
Point to note here are:
1) There is no application.xml in this ear file, as in Java EE 5, application.xml has become optional.
2) For sake of clarity, entity beans are packaged in entities.jar and EJB interface classes are packaged in ejb-interfaces.jar. All other modules depend on these two jar files. So sharing them is a challenge.
3) It uses lib directory to share entities.jar and ejb-interfaces.jar with other modules. lib directory is a special directory introduced in Java EE 5. By default its name is lib, but it can be overridden by using application.xml. The intended use is to place library jar files in this directory so that they can be made available to all other modules in the ear file without having to use Class-Path manifest attribute as explained in Bundled Optional Package Support in Java EE 5 platform spec chapter #8. So our entities.jar and ejb-interfaces.jar are automatically available to ejbs.jar, web-app2.war and appclient.jar. This way there is no duplication of classes in the ear.
Since entities.jar is placed in lib directory, not only is it available to all other module's class loader, but also the Persistence Unit defined in this jar file is visible to all other modules.
4) ejbs.jar only contains the session bean classes. There is no ejb-jar.xml inside it. Also note that ejbs.jar does not bundle entities.jar, yet it uses the persistence unit which is defined in lib/entities.jar. This is allowed as per the sharing rules of persistence units.
5) web-app2.war contains the servlet classes and html files.
6) In addition to example.client.Main.class appclient.jar contains a META-INF/MANIFEST.MF file.
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 (EJB) interface. Since in this example it is being used in the EJB interface, we have to do 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:
@Entity public class UserCredential implements 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. 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.
@Id private String name;
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 value.
5) Although it is possible to specify exact table name using @Table and column names using @Column for an entity bean, we can rely on the default mapping that the specification defines and yet expect the application to be portable because this default mapping rule is defined by the spec itself. UserCredential bean gets mapped to a table called USERCREDENTIAL, name & password fields get mapped to NAME and PASSWORD columns respectively.
Step #2: Define a persistence unit in persistence.xml
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 configuration is applied to an EntityManagerFactory which in turn creates homogeneous EntityManager instances. But this is all taken care of by the container to persistence provider interaction. As a user to define a PU, we just 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 pu1.
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 UserCredentialManagerBean.java
Points to note about this EJB 3 style stateless session bean are:
1) It is a Plain Old Java Object (POJO): It does not extend any predefined class, nor does it implement any predefined interface. It only implements its own business interface UserCredentialManager.
2) There is no deployment descriptor needed to specify that it is a stateless session bean. Instead the class has been annotated as shown below:
@Remote annotation is required b'cos the business interface UserCredentialManager is neither annotated as @Local nor as @Remote. So by default our session bean would have exposed a local business interface. Since we want the EJB to be accessible from an application client, having only local interface would not work. So we are using @Remote annotation. The other option would have been to have both local and remote interface, but let's postpone that discussion to some other time.
3) It talks to database using EntityManager API. It declares dependency on an EntityManager using @PersistenceContext annotation:
@PersistenceContext private EntityManager em;
Since there is only one Persistence Unit(PU) defined in the scope of this ejb, there is no need to specify the unitName in @PersistenceContext. Please also note that an injected variable in a servlet or ejb must not be declared static or final. In our example, em follows this rule as well.
4) The business method "createUser" creates a new object in the database by one line code:
The business method "authenticate" uses the following code:
UserCredential uc = em.find(UserCredential.class, name);
to locates a matching UserCredential object in the database.
The business method "removeUser" first locates the named user credential and then calls
to remove it from the database.
Unlike em.find(), em.remove() and em.persist() require an active transaction context. But as you can see none of the EJB methods starts a transaction. That is because by default the transaction management type is CONTAINER and the transaction attribute of business method is REQUIRED. So ejb container takes care of transaction management! This is unlike web container as discussed here.
There is no need to write an ejb-jar.xml because a Java EE 5 compatible container can identify ejbs.jar as an EJB module because it contains a class that is annotated as Stateless.
Step #4: Write LoginServlet.java
Points to note about this servlet are:
1) It declares dependency on the EJB using @EJB annotation:
@EJB private UserCredentialManager ucm;
Since there is only one bean that implements this business interface, there is no need to specify any of the attributes of @EJB.
2) When user tries to login using a user name and password, it calls the EJB method to authenticate as shown below:
Step #5: Write RegistrationServlet.java
Note it also uses an injected EJB. It also calls the createUser EJB method to create a new user.
Both the servlets do not do any transaction management, because that is handled by the EJB layer. They also do not explicitly depend on the Java Persistence API.
Step #6: 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 #7: 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 #8: Write an appclient Main.java
Points to note about this appclient are that:
1) It declares dependency on the EJB using @EJB as shown below:
@EJB private static UserCredentialManager ucm;
Also note that, the field ucm is static. That's because unlike EJB or Servlets, injected fields in an application client must be static as Application Client Container (ACC) does not instantiate the class, instead it calls the static main().
Since in this ear file, there is only one session bean that implements UserCredentialManager interface, there is no need to specify any other attributes of the EJB.
2) It must be a public class because it will be used by ACC which does not belong to the package of this class.
3) We also need to write a manifest.mf file which must contain the name of the class containing the main() so that ACC knows which is the main class to look for injected fields and main(). Thsi file gets bundled in appclient.jar as META-INF/MANIFEST.MF file.
There is no need to write application-client.xml because it is optional. Java EE 5 compatible platform can discover appclient.jar's module type because it contains META-INF/MANIFEST.MF with a Main-Class attribute.
Step #9: 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 an ear file called blog5.ear.
clean -- cleans
verify -- verify uses a tool called verifier that checks compliance of the application against Java EE spec.
deploy -- deploys the ear file
undeploy -- undeploys the ear file
The last three targets are specific to Sun Java System Application Server 9 PE which is implementing Java EE 5 spec.
As you can see, to compile the sources, only library needed is javaee.jar which contains the Java EE 5 platform APIs.
The deploy target in build.xml uses a feature called Java2DB which can automatically create the tables during deployment. This is specific to Sun's app server, but such features are supported in many other commercial applications servers as well.
Hope you found this example useful.