How EJB 3 and JPA Programming Model speed up and simplify N-tier Web Application Development
Pinaki Poddar's Blog |
May 25, 2006 1:06 AM
|
Comments (5)
EJB 3 JPA Programming Model
Simplicity is one of the most important drivers of new EJB 3 specification,
in particular, and Java EE 5 platform, in general. New POJO based EJB 3 and
delegation of persistence services to a distinct Java Persistence API (JPA)
interface open up
excellent opportunities for the bean developer to simplify and quicken
the entire code-test-debug cycle.
New technology innovations demand new thinking, new development practices.
This blog explores a service-oriented development pattern to
speed up code-test-debug cycle to build multi-tier
applications.
Let us consider a typical business method for illustrative purpose
public interface ReviewService
{
/**
* Creates a new Review by the given reviewer for the given item.
*
* @param reviewerName name of the reviewer. The named reviewer must exist.
* @param itemId unique identifier of the item being reviewed. The item must
* exist.
* @param rating an integer between BEST_RATING and WORST_RATING
* @param comment a free form text as a comment
*
* @return newly created Review
* @exception IllegalArgumentException if the named reviewer or item does not
* exist. Or if the rating has non-permissible value.
* @exception NullPointerException if comment is null or empty
*
**/
Review newReview (String reviewerName, String itemId,
int rating, String comment);
We want a Stateless EJB 3 Stateless Session Bean to implement this method.
So we go cranking on our keyboard
01 @Stateless
02 @Remote(ReviewService.class)
03 public class ReviewSessionBean implements ReviewService
04 {
05 @TransactionAttribute(REQUIRED)
06 public Review newReview (String reviewerName, String itemId,
07 int rating, String comment)
08 {
09 Item item = em.find(Item.class,itemId);
10 Reviewer reviewer = em.find(Reviewer.class, reviewerName);
11 if (item==null)
12 throw new IllegalArgumentException("No item with id [" + itemId + "]");
13 if (reviewer==null)
14 throw new IllegalArgumentException("No reviewer named [" + reviewerName + "]");
15 if (rating < WORST_RATING || rating > BEST_RATING)
16 throw new IllegalArgumentException ("Illegal rating " + rating +
17 ". Must be between " + WORST_RATING + " and " + BEST_RATING);
18 if (comment==null || comment.trim().length()==0)
19 throw new NullPointerException("Null or empty comment is not allowed");
20
21 Review review = reviewer.review(item, rating, comment);
22 em.persist(review);
23
24 return review;
25 }
26 @PersistenceContext(unitName="ejax.session")
27 private EntityManager em;
Few points to note on above code lines:
A. What is em?
That is javax.persistence.EntityManager injected by the EJB container.
The instruction for the container to inject is in the following two lines
of code
26 @PersistenceContext(unitName="ejax.session")
27 private EntityManager em;
This is dependency injection or inversion of control design
pattern at work for you in Java EE 5 (if you care to impress with words).
B. No explicit transaction begin or commit.
The method-level annotation
05 @TransactionAttribute(REQUIRED)
tells the container, before invoking the session bean's method, not only to
inject an
EntityManager to the method, but also to ensure that the
EntityManager is in a active transaction.
And, after the bean's method completes, the container commits the transaction.
C. What kind of transaction?
Depends on what was specified as the value of
transaction-type attribute in
persistence unit named ejax.session.
The choices are JTA and RESOURCE_LOCAL
and for the given example, the persistence unit is specified in
META-INF/persistence.xml
as
<persistence-unit name="ejax.session" transaction-type="JTA">
New Technology demands new thinking
No doubt, this Session Bean is written using new EJB 3 technology.
But with old thinking, with old development pattern. Why do I say that?
Before further explanation, let us note some observations on these typical
lines of code
Observation 1: A business method is an interplay
between business logic and infrastructure support services.
The correct execution of the business function depends on business
semantics, e.g.
-- does Reviewer.review() method works correctly?
-- does the method throw a exception if a negative rating is passed?
as well as infrastuctural services
-- does the correct EntityManager get injected with an active transaction?
-- does the new Review instance committed to the datastore?
Observation 2: The correctness of the first category is the bean developer's concern,
while correctness of the second category is that of the infrastucture provider's.
Of course, The bean developer/deployer must verify that the infrastructure
services are specified correctly. For example, persistence-unit name
in META-INF/persistence.xml
"ejax.session" is consistent with the annotation for the
injected EntityManager
26 @PersistenceContext(unitName="ejax.session")
which when writing this example, I goofed up as follows
@PersistenceContext(name="ejax.session")
The qualifier name on @PersistenceContext annotation
implies JNDI name of the instance,
whereas unitName refers to persistence unit name to look for.
The point is, if the deployment descriptors are correctly specified the
application server is expected to do its part -- the bean developer just
verifies that the descriptors are correct.
However, the way the bean method is coded it does not separate the
distinction between testing the business semantics versus verification
of deployment descriptions.
The implementation pattern followed leaves the only option to tested is by
deploying this session bean in application container and then
perhaps finding out that there is a silly error such as
if (rating > WORST_RATING || rating < BEST_RATING)
even if you detect the mistake in a moment once a lengthy stack trace is
dumped on your console, you then need to
edit, compile, package, deploy, test and verify that the error is eliminated.
Separate business logic and infrasturture service
The design goal is to separate the business logic implementation from
the application server dependency. New EJB 3 already took a major step towards
this simplicity by defining Entity Java Beans as POJO. Also persistence services
are identified as a separte service API that works with or without container
support as well.
The current design strategy extends the similar theme to
capture the business logic as POJO-based service. Once the POJO based service
is tested without a container in a quicker code-test-debug cycle, the tested
business service is stiched back into the original stateless Session Bean that
would act as a mediator between the application server's infrastructural services
such as dependency injection, transaction control, multi-threading, remoting.
The strategy is depicted in the following diagram:
The business interface
ReviewService is implemented in a Java class ReviewServiceImpl
independent of
infrastructural issues. This way, the business logic is available to
a Web Service, a Session Bean or a Swing GUI button's action handler -- as a
business service developer one makes absolutely no assumption. That is the hallmark of a
good service design -- being agnostic of the caller. This is also known as
selfish programming.
This new service-oriented, infrastructure-independent implementation of the same
business method looks like as follows
01 public class ReviewServiceImpl
02 extends PersistenceService
03 implements ReviewService
04 {
05 public Review newReview (String reviewerName, String itemId,
06 int rating, String comment)
07 {
08 EntityManager em = beginTransaction();
09 Item item = em.find(Item.class,itemId);
10 Reviewer reviewer = em.find(Reviewer.class, reviewerName);
11 if (item==null)
12 throw new IllegalArgumentException("No item with id [" + itemId + "]");
13 if (reviewer==null)
14 throw new IllegalArgumentException("No reviewer named [" + reviewerName + "]");
15 if (rating < WORST_RATING || rating > BEST_RATING)
16 throw new IllegalArgumentException ("Illegal rating " + rating +
17 ". Must be between " + WORST_RATING + " and " + BEST_RATING);
18 if (comment==null || comment.trim().length()==0)
19 throw new NullPointerException("Null or empty comment is not allowed");
20
21 Review review = reviewer.review(item, rating, comment);
22 em.persist(review);
23 commit();
24
25 return review;
26 }
If we compare this new implementation against the original stateless
Session Bean we see
- The class level annotations
@Stateless and
@Remote are gone.
- The method level annotations
@TransactionAttribute(REQUIRED)
is removed
- Depedency injection of
EntityManager is removed.
And, new explicit transaction demarcation is introduced through
beginTransaction() and commit().
Effectively, all the annotations that are effectively instructions to
the container are removed and a mechanics is provided to transaction
control that is critical for correct operation of the business method.
Advantage of separating business logic from infrastructure services
What does this new orientation towards separting the business logic from
infrasturucre services buy us? We can test the business logic (to certain
degree of certainity) outside a container. This, in turn, significantly
speeds up the code-test-debug cycle and minimizes resource requirements.
But the story is not over. We got rid of the container services from the
implementation. But have we thrown the baby away with the bath water?
How would we ensure that the business method has access to a EntityManager
to perform its duties?
First reaction is to get the same persistence unit, create a
EntityManagerFactory that would supply us the EntityManager
we need.
But we want more. Our original target is to make the service available
via a stateless Session Bean that is injected with EntityManager.
So what we need to satisfy the dual (and slightly confronting)
requirements of a) separting the business functions from infrasturucture for
rapid development cycle and b) creating a target solution that harnesses the
containers infrastructure with business logic. The solution of
being two things at the same time is realized in PersistenceService
- a class with dual personality.
This class can control the transaction explicitly or relinquish its transaction
responsibility to the container depending on its invocation context. The
ReviewServiceImpl extends from it and uses its dual transaction
control methods beginTransaction() and commit.
Here is the implementation of PersistenceService.commit() method
protected final void commit()
{
if (isManaged())
return;
EntityManager em = getEntityManager();
if (em.getTransaction().isActive())
em.getTransaction().commit();
}
The class is aware whether it is under managed environment. If it is, it
relinquishes its transaction control and its commit() effectively
becomes a no-op. Otherwise, it acts on its own and commits the transaction.
How does it detect whether it is in a managed transaction? If it is injected
with a EntityManager it considers itself managed otherwise it is
on its own.
It accomplishes this logic via the following methods
private final EntityManagerFactory _emf;
private final ThreadLocal<EntityManager> _thread;
private volatile boolean _isManaged;
protected synchronized EntityManager getEntityManager()
{
EntityManager em = _thread.get();
if (em!=null)
return em;
else if (_emf!=null)
{
em = _emf.createEntityManager();
_thread.set(em);
return em;
}
else
throw new NullPointerException("no-entity-manager");
}
protected synchronized void injectEntityManager (EntityManager em)
{
_isManaged = true;
_thread.set(em);
}
public final boolean isManaged()
{
return _isManaged;
}
It maintains a per-thread EntityManager in ThreadLocal
variable _thread. A call to its getEntityManager
method supplies a EntityManager that is either injected
earlier through injectEntityManager()
(i.e. it is in managed state when it relinquishes its transactional duties) or
a EntityManager created from the EntityManagerFactory
on a per-thread basis. Finally, its beginTransaction() method is
a combination of getEntityManager and
EntityManager.getTransaction().begin() methods.
protected final EntityManager beginTransaction()
{
EntityManager em = getEntityManager();
if (!isManaged() && !em.getTransaction().isActive())
em.getTransaction().begin();
return em;
}
Put the humpty-dumpty back gain
We have isolated the business contract of ReviewService
in ReviewServiceImpl
from original stateless Session Bean ReviewSessionBean.
We figured out a technique to supply EntityManager within and
outside a container through a single abstraction of PersistenceService.
Let us now reconstruct the original Session Bean in terms of these facilities
01 @Stateless
02 @Remote(ReviewService.class)
03 public class ReviewSessionBean
04 extends ReviewServiceImpl
05 implements ReviewService
06 {
07 @TransactionAttribute(REQUIRES_NEW)
08 public Review newReview (Reviewer reviewer, Item item,
09 int rating, String comment)
10 {
11 injectEntityManager(em);
12 return super.newReview(reviewer, item, rating, comment);
13 }
14 @PersistenceContext(unitName="ejax.session")
15 private EntityManager em;
16 }
What is done in this refactored version is
- the Session Bean extends the business service implementation
ReviewServiceImpl
which effectively implements core external contract of ReviewService.
- the infrastructure service
annotations (
@Stateless, @Remote,
@PersistenceContext) are reinstituted.
- the injected
EntityManager is reinjected into the super
class PersistenceService which signals to behave it in managed
context.
- finally, the business function is realized thorough the implementation in
the super class.
We realize our strategy through inheritence but it could as well have been
realized through delegation.
JUnit Test Cases
The JUnit Test cases align in the same inheritance hierarchy. Two JUnit Test cases
TestService and TestSession both test the same
interface ReviewService. But while the former invokes an instance of
ReviewServiceImpl through direct construction, the later connects
via JNDI lookup to a ReviewSessionBean. In fact, TestSession
only implements its setUp() to connect to the remote Session Bean,
its test methods are already implemented in its super class TestService.
Summary
The new EJB 3 and JPA bring simplicity to development. This discussion follows
the same theme to simplify code-test-debug cycle for Session Bean
through a dual transaction control design pattern and code examples.
In the next blog, I will discuss another practical issue: how to switch
databases in different testing environment?
Good Night and Good Luck
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
I'm new to EJB 3 and just testing it now. Here I have some concerns about it.
1. Can JPA decouple with session bean? Till now all the code I read is put JPA and session bean in one module (commonly is EAR). But from the aspect of EJB, the better idea is spitting them aside, entity bean in one EJB modue, and session bean in another one, and any entity bean can be accessed by JNDI. I don't know how EJB 3 can implement it. Now I tried to put JPA POJO in a optional package, and be accessed by standalone session bean module. It works well, but the accesser must hold persistance.xml for the JPA. Obviously this way is ugly, but I can not find a better practice.
2. How can JPA utilize data sources of WLS? Till now I find in all the example code, database detail should be included inside the persistance.xml. I think JPA must use JDBC driectly, without container data source. Sure I know JPA can run standalone, but inside WLS, the better idea is using existing common data source service. Do you have any idea of it?
Any way I'm glad to get your article. For any replay, please mail to mwang@bea.com. Thanks!
Posted by: goblinize on June 7, 2007 at 2:25 AM
-
Hi Pinaki,
I'm going through your blog on using EJB3/JPA programming model, in that
you've implemented the ReviewServiceImpl extends PersistenceService.
Instead of extending it if I make the PersistenceService as an
attribute in ReviewServiceImpl, would that break the pattern? Business
services has business logic in it and the PersistenceService has the
infrastructure logic in it. Not sure if this works without any issues
both inside and outside the EJB container? Also you've synchronized the
injectEntityManager and getEntityManager methods in PersistenceService,
does it really necessary? How would the performance affect in high
volume traffic?
Posted by: mani0001 on November 14, 2007 at 2:56 PM
-
1. could have reduced the granularity of the lock from method level to class variable level.
2. The main purpose of PersistenceService is to arrive at a pattern that allows business logic of a JPA application to decouple from infrastructure logic (i.e. where to get a EntityManagerFactory, or pass the same EntityManager along the thread of control) such that testing in JSE and JEE environment.
I doubt its contribution (positive or negative) in production (i.e. high-volume traffic) environment within application container because, in JEE environment it is designed to be essentially a no-op.
Posted by: pinaki.poddar on November 15, 2007 at 8:35 PM
-
> Can JPA decouple with session bean?
Yes. You can create a separate ejb module containing the domain classes and JPA configuration META-INF/persistence.xml file. In fact, one of blog entries do show an example (with source code and Ant script) of the same. Unfortunately, I do not recall exactly which one and one has to scan my ramblings that are accompanied by source code download.
2. How can JPA utilize data sources of WLS?
In META-INF/persistence.xml, tag accepts the JNDI name of a registered data source. In fact, there are multiple alternate ways of specifying datastore specifics as documented here:
http://openjpa.apache.org/docs/latest/manual/manual.html#ref_guide_dbsetup_thirdparty_enlist
Please note that when a pre-configured datasource is being used under JTA transaction and managed by the application server's transaction manager, there are scenarios when JPA provider will require to access and commit a transaction for its own internal purpose (e.g. when assigning auto-generated identity for a newly persistent object from a database sequence table). As the provider runtime can not commit the connection, one may have to specify as well.
Posted by: pinaki.poddar on November 15, 2007 at 8:45 PM
-
I doubt its contribution (positive or negative) in production (i.e. high-volume traffic) environment within application container because, in JEE environment it is designed to be essentially a no-op
I understand that the methods in PersistenceService are "no op" inside the container. My question is since the injectEntityManager and getEntityManager methods are marked as synchronized, does it work properly inside the EJB 3.0 container? Inside the container all the threads are managed by the container, by marking the two methods as synchronized , I hope it shouldn't have any side affects with the container threading mechanism.
My main goal is, I would like to deploy my business services outside the container (say in Spring framework, or Junit testing) if needed and also inside the EJB 3.0 container. The pattern you described in this blog nicely fits my purpose. I am using EJB 3.0 for the first time, so trying to understand the issues of production implementation of this pattern along with Hibernate, JPA in Weblogic 10.0 app server.
Posted by: mani001 on November 17, 2007 at 5:53 AM
|