The Source for Java Technology Collaboration
User: Password:



 Sahoo

Sahoo's Blog

Using Java Persistence API in Java EE Platform - Part II

Posted by ss141213 on December 15, 2005 at 08:59 AM | Comments (17)

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.

Software Requirements
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:

lib/entities.jar
lib/ejb-interfaces.jar
ejbs.jar
web-app2.war
appclient.jar

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.


Detailed Steps

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 node in persistence.xml file. Points to note about this persistence.xml are:

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 because we are defining only one PU in this persistence.xml and we will be packaging the entity classes along with this persistence.xml in a jar file, so container can discover all the entity beans.

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:

@Stateless @Remote

public class UserCredentialManagerBean implements UserCredentialManager {...

@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:

em.persist(uc);

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

em.remove(uc);

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:

ucm.authenticate(name, password);

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.

More blogs about


Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Great introduction to Java EE 5. I ran into a very minor problem when I tried to launch the client via Java Webstart. It seems that appclient.jar is a reserved name in Glassfish so the app couldn't be started. Changing the name of the client jar file to client.jar in build.xml allowed me to run the client from JWS with the following command line


    javaws http://localhost:8080/blog5/client?arg=user

    Posted by: djcarson on December 21, 2005 at 11:58 AM

  • It's great that you are using the Java Web Start support. Sorry about the inconvenience with the client jar file name.

    I'll want to take a closer look into this, but for the moment there is a second workaround in addition to the one you found.

    You could also specify an explicit path for launching with Java Web Start in the sun-application-client.xml descriptor for the app client. As the last subelement of <sun-application-client>, you can add the <java-web-start-access> element. It can have as a child the element <context-root>...</context-root> which you can use to specify the path which users will use to launch the app client.

    For example, if you added this
    <sun-application-client>
    ...
    <java-web-start-access>
    <context-root>myBlog/blogClient</context-root>
    </java-web-start-access>
    </sun-application-client>

    then users could launch using the URL


    http://localhost:8080/myBlog/blogClient?arg=user


    This and the technique you described are certainly a workarounds at best, and ideally GlassFish would not restrict the name of the app client jar file this way.

    Posted by: tjquinn on December 21, 2005 at 11:03 PM

  • Java Persistence API spec is still changing. My original posting was written before the proposed final draft spec came out. In the proposed final draft, there were quite a few changes and I have now updated this blog accordingly. The only change needed was removal of AccessMode fron @Entity. Now this example works with latest build of GlassFish. I don't anticipate any further changes in the spec that can affect this example.

    Secondly, earlier I was using properties in persistence.xml to specify Java2DB mode. Now I am using --createtable options in deploy targetin build.xml.

    Thanks,
    Sahoo

    Posted by: ss141213 on February 03, 2006 at 07:46 AM

  • The build.xml is still using --password option during asadmin deploy. This option has been removed. I have not got a chance to fix my blog yet. In case, you encounter this error, please change build.xml to use --passwordfile option.

    Sahoo

    Posted by: ss141213 on February 27, 2006 at 11:15 PM

  • Wonderful sample! works just fine. But I have a question. You said the lib directory is intended to share jar files with other modules, right? I'm trying to write a Web Service (following some samples I've seen over the web, anotating a class with @Stateless and @WebService). If I have a method with a parameter or return types defined in other jar file (inside the lib directory) I can't deploy the application. Could you say what I'm doing wrong?
    Thanks a lot, samflores.

    Posted by: samflores on March 22, 2006 at 12:08 PM

  • Hi samflores,

    That's exactly how it is supposed to work.
    You put all the interface classes like return types, parameter types
    in a jar file in EAR lib directory so they are available to both the component
    as well its clients.

    You could be doing something wrong. Try running
    $glassfish_home/bin/verifier app.ear.
    That might help you diagnosing packaging issues.

    If that did not help and you want me to take a further look at your problem, please feel free to file a bug in issue tracker
    with a test case and assign it to me (i.e. ss141213).
    I will get back to you.

    Thanks,
    Sahoo

    Posted by: ss141213 on March 22, 2006 at 07:27 PM

  • IMPORTANT: Please Read

    When I wrote this sample, version attribute was optional and hence I had not
    specified it in the persistence.xml. Because of a recent change in spec,
    version is now mandatory as annouced here.

    So if you are using the latest GlassFish builds, then the sample may not
    deploy for you. It's a trivial fix, I have not got time to fix it(I will do it soon).
    So, in the mean while, after downloading the sample, please update
    persistence.xml with version="1.0" at the root tag.
    Sorry for any inconvenience,

    Thanks,
    Sahoo

    Posted by: ss141213 on March 22, 2006 at 07:38 PM

  • IMPORTANT

    On 20060323, I updated the sample. The changes are:
    1. it now uses version attribute in persistence.xml. (see my earlier comment on this regard)
    2. updated build.xml to use passwordfile option.
    3. fixed build.xml so that interfaces classes are no more packaged inside ejbs.jar.
    Thanks,
    Sahoo

    Posted by: ss141213 on March 23, 2006 at 07:25 AM

  • Sahoo - Great example; it helped me through some development issues I was having the last week or so. I now have my web->ejb->entity->mysql app running and just wanted to say thanks. Keep up the great work.

    Posted by: roccsolid on September 30, 2006 at 09:28 AM

  • Hi,
    I've got a question regarding the Persistence Context propagation.
    If I were to access a slsb using its local interface from a Servlet, one of its method handing me some entity, would I then be able to navigate its object graph lazily in that servlet?
    More interesting to me, is how the Persitence Context is propagated through out the request. Let say I'd get a instance of that slsb injected in some unmanaged pojo, a struts action, using Spring. While the RequestProcessor servlet would be living in a 2.5 spec container, what about the persistence context propagation?
    I've been using Hibernate for web application developement for quite some time and there are several approaches on how to keep the session (Persistence Context) open all though the request/response lifecycle. Having used EJB3 on a project, I know that the PersistenceContext gets propagated through out different slsb too. What about bringing Servlet in the picture or even a web framework not benifiting from the managed beans injection we'd get from JSF?
    Thanks,
    Alexander Snaps

    Posted by: greenhorn on October 24, 2006 at 07:40 PM

  • Hi Alexander,

    I just blogged about Persistence Context Propagation. Does that blog answer your question? Feel free to comment there...

    Thanks,
    Sahoo

    Posted by: ss141213 on October 27, 2006 at 11:05 AM

  • Hi, I haven't managed to user the @EJB injection in a servlet.

    I have an ear with 4 archives: common, entities, ejb, war. Naturally, the ejb is in the jar called ejb and the servlet is in the war file. In case this is relevant, I am using Maven to built a skinny WAR by setting its classpath to be all the other archives in the ear (using the WAR's MANIFEST.MF's Class-Path entry).
    I'm deploying this ear in JBoss 4.0.5 installed with ejb3, obviously.

    I annotated the local interface (AuthenticationLocal) with "@Local", and annotated with "@Stateless" the bean implementation called AuthenticationBean (which implements AuthenticationLocal).

    I declared an annotated private member in the servlet in the following way:
    @EJB private AuthenticationLocal authentication;
    This field stays null when I try using is (and I'm getting NullPointerException).
    Is there some kind of naming convention I'm not following? I tried changing the names to match the convention implicitly used here.
    I will emphasize that when I try using JBoss' specific annotation for local binding the following way: "@LocalBinding(jndiBinding = "ejb/AuthenticationBean")", I can access this ejb and invoke methods in it from the servlet using:
    new InitialContext().lookup("ejb/AuthenticationBean")
    This is the only place I managed to find an example of an @EJB injected servlet, just that I can't get this to work... Any help anyone?
    Thanks, Amit

    Posted by: amitkasher on November 23, 2006 at 01:03 AM

  • Hi Amit,
    I am no JBoss expert. It looks to me that the version of JBoss app server you are using may not be supporting @EJB in a Servlet. Have you tried deploying your application to GlassFish which is a fully compliant, production quality Java EE 5 application server? That will easily tell you whether your app is correct or not.
    Thanks,
    Sahoo

    Posted by: ss141213 on November 23, 2006 at 07:06 AM

  • Thanks Sahoo,
    After a lot of research it seems that your assumption is accurate. JBoss 4.0.5 actually doesn't support resource injection into servlets. JBoss 5Beta1 does, but it has a bugs so I'll just wait for its GA.
    I'm using some hibernate stuff (validator, schema generation annotations) so this will make it more difficult to use GlassFish. I'll give it a try anyway (hopefully I can just put the hibernate annotations jars in GlassFish's classpath).

    Thanks again,
    Amit Kasher

    Posted by: amitkasher on December 12, 2006 at 09:00 AM

  • Hi Amit,
    Take a look at the following blog that shows how easy it is to use Hibernate JPA persistence provider in GlassFish:
    Using Hibernate JPA provider in GlassFish

    It is as simple as copying necessary Hibernate jar files into $GF/domains/domain1/lib and restarting the jar. I suggest you copy all the Hibernate jar files, i.e Hibernate entity manager jars, Hibernate anotations jar and Hibernate core jar files as well as their dependencies. Hope this helps. Any problems? use GlassFish forum.
    Thanks,
    Sahoo

    Posted by: ss141213 on December 12, 2006 at 07:09 PM

  • I am trying to run an ejb3 application using javaws from the command line. Application is deployed on jboss4.2.1. However I recieve an exception. This exception is thrown when a session bean is looked up and casted into corresponding session bean class.
    However when i run the application directly from command line using java.exe(as i have code on my machine), the application runs perfectly fine. Can you tell me how to resolve this issue. Is it because of class loader or some policy? If so then please tell me the way out.
    The exception stack trace is :

    class javax.naming.Reference
    java.lang.ClassCastException: javax.naming.Reference
    at com.nwi.asa.common.communication.CommDelegate.createSession(CommDelegate.java:54)
    at com.nwi.asa.common.communication.CommDelegate.(CommDelegate.java:42)
    at com.nwi.asa.common.login.LoginManager.login(LoginManager.java:163)
    at com.nwi.asa.common.login.LoginManagerActionListener$1.actionPerformed(LoginManagerActionListener.java:40)
    at javax.swing.Timer.fireActionPerformed(Unknown Source)
    at javax.swing.Timer$DoPostEvent.run(Unknown Source)
    at java.awt.event.InvocationEvent.dispatch(Unknown Source)
    at java.awt.EventQueue.dispatchEvent(Unknown Source)
    at java.awt.EventDispatchThread.pumpOneEventForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
    at java.awt.EventDispatchThread.run(Unknown Source)

    Posted by: awssul on August 24, 2007 at 03:49 PM

  • It's great that you are using the Java Web Start support. Sorry about the inconvenience with the client jar file name.

    I'll want to take a closer look into this, but for the moment there is a second workaround in addition to the one you found. 搬屋公司-交友-迷你倉

    Posted by: winrelocation on October 30, 2007 at 12:45 AM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds