viernes, 26 de diciembre de 2008

Lessons learned using GWT, Axis and JPA simultaneously

Note: This article is solely focused on some issues related to the EntityManager that may appear working with GWT, Axis and JPA. If you are interested in the performance of JPA, this other post about a comparison among different JPA implementations (OpenJPA, Hibernate, OpenJPA and Eclipselink) may be of some interest for you.

In my experience with the three technologies which appear in the title of this article, I had to face a number of problems, actually quite easy to solve once you know what all is about.
So I decided to publish this, for others not to have to face these same problems.
Here you have some lessons learned that should be taken into account when working with the Google Web Toolkit, Axis and any of the JPA implementations out there.
You have this same article in Spanish here.

These are the facts:
  • EntityManager can not be shared among threads. This is specified in JPA, but you don't give it enough importance until you start using JPA from the web (which will probably be the usual case).
  • Second, Axis creates a new object (and thread) for each call to the server.
  • Third, contrary to Axis, when you use one of the so-called GWT server remote services, only one object and thread are created and this object is reused everytime the remote service is called. That is to say, GWT does not create a new object for every new call.
As a result of these facts, we have these consequences:
  • If we use a static EntityManager in a call to Axis, the code will fail as soon as two threads cross with each other (quite usual in a web application), given that they will be using the same EntityManager object.
  • Contrary to Axis, in the case of GWT it is not necessary that EntityManager is static to start generating problems; it will be enough that it is a global attribute belonging to the object that implements the GWT remote service. Being one only object that answers all petitions, two calls to the server will use the same EntityManager and this will make the code fail.
So, we can conclude two rules:
  • An Axis service must therefore create a new EntityManager and this can not be static.
  • Each method of a GWT remote service must create a new EntityManager for individual use (it is not enough that it creates it at a global level).
If any of these two rules are not followed, 2 simultaneous calls will use the same EntityManager at the same time, producing exceptions.
Unfortunately, JPA exceptions are not especially explicit. This is common to almost every implementation of JPA: they give almost no concrete information (including Hibernate).

The EntityManagerFactory can and should be shared among different threads, given that it is a quite expensive object to create.

In short, if you don't want to have problems and you would like to have a piece of code that can be used both by GWT and Axis, you could write something like this:
public class JPAManager {
private final static EntityManagerFactory emf =
Persistence.createEntityManagerFactory("JPAImplementationTest");
private EntityManager em = emf.createEntityManager();
public EntityManager getEntityManager () {
return em;
}
}
Each method called in an invocation to a web service or to a GWT server remote service should create its own JPAManager and get the EntityManager with the public method. This way you will avoid having problems among the different threads created in the invocations.

You could also use more sophisticated solutions like implementing a singleton (with the problems associated to them) or using the ServletContextListener, as someone suggested in the comments. But this simple solution worked for me and resisted some load tests.

14 comentarios:

Hasan Turksoy dijo...

Hi,

First of all, thanks for the tips..

one thing; you have created the EntityManager at class level.

But, you said; "Each method of the GWT remote service must create a new EntityManager" above..

i got confused a bit.. could you please clear it?

regards,

Unknown dijo...

Sure, Hasan. Sorry if it wasn't all that clear.
The idea is that every method executed when a GWT remote service or an Axis web service is called should create a JPAManager.
You are right, the EntityManager is a class level attribute, but it's not static, it's private. So every method would use a different EntityManager, given that each would create a new JPAManager.

The case of the EntityManagerFactory is different, because this one can and should be shared. That's why it's static.

Hope to have clarified a little bit.

Cédric Pineau dijo...

So you discovered the documentation was right ?
Sure this needs to be claimed !

Just a bit disappointed..

Sc

Anónimo dijo...

En Java, nada te garantiza que una clase con una variable static permanezca en memoria cuando no hay objetos instanciados. Por lo tanto, el JPAManager que propones está mal. Ten en cuenta que la máquina virtual puede cepillarse la clase (y, como consecuencta, el EntityManagerFactory) cada vez que no quede ningún objeto instanciado de JPAManager. Es un error muy grave el pensar que una variable static no puede desaparecer.

El EntityManagerFactory debería permanecer en memoria gracias a algún objeto cuya existencia estuviese garantizada durante la vida de la aplicación, por ejemplo, un filtro.

Unknown dijo...

Juan, what I said is that the EntityManagerFactory should be static, so that there is only one for the whole application and it is shared by all the threads. In case the GC removes it if nobody has a reference to it (the fact that you are pointing out), the next time that is used it will be created again... but only once.
There's nothing wrong with that.

Anónimo dijo...

In case the GC removes it if nobody has a reference to it (the fact that you are pointing out), the next time that is used it will be created again... but only once.
There's nothing wrong with that.


Efficiency?

צְבִיקַ'ה dijo...

thanks for the tips,

some questions:
using static members in jee apps isn't a trivial nor usually recommended practice, also due to classloader behavior that might cause visibility problems or multiple instances (see http://www.javaspecialists.eu/archive/Issue052.html or http://www.objectsource.com/j2eechapters/Ch21-ClassLoaders_and_J2EE.htm, for example)
so,
why did you choose rhis solution instead of using a ThreadLocal object or, better off, a jndi accessible EntityManager, the recommended jee way (see: http://docs.jboss.org/ejb3/app-server/reference/build/reference/en/html/entityconfig.html, for jboss details, I'm sure other servers have it too)

Thanks,
Zvika

Unknown dijo...

Yes, you could use a Singleton, a Threadlocal or even a JNDI resource, but the simple static attribute did the trick. It didn't fail during the tests and it did not produce any memory leaks.
Tomcat created just one EntityMemoryFactory, which was shared by the threads. It gave no problem at all. That's why I did not use anything more sophisticated.

Anónimo dijo...

Zvika, I do not think that a static var might cause problems. There will be one static var in each web applicaction. That is OK.

In my opinion, the problem is how to assure that the class with the static var is not unloaded from memory.

By the way, I do not like singletons at all. They are very dangerous.

Juan

Anónimo dijo...

You can use a ServletContextListener:

SRV.10.5 Listener Instances and Threading
The container is required to complete instantiation of the listener classes in a Web
application prior to the start of execution of the first request into the application. The
container must maintain a reference to each listener instance until the last request is
serviced for the Web application.

Unknown dijo...

until the last request is
serviced for the Web application
??
Then once there are no requests being serviced the ServletContextListener object will stop being maintained (= subject to GC)

Anónimo dijo...

The ServletContextListener exists while the web application exists.

The phrase "until the last request is
serviced for the Web application" means that the ServletContextListener must not be unloaded (when the aplication is going to be stopped) until the last (pending) request is serviced.

The phrase DOES NOT mean that the listener can be unloaded in an active web application when the "last request" is serviced. When the web application is active you do not know which is the "last request"! ;-)

César dijo...

Muy interesante para los que trabajen con Java... para cuando una entrada sobre Oracle????

cesartiz
www.socioestusegovia.tk

Anónimo dijo...

We're using Gwt-Ext, OpenJPA, and Spring as follows:

public interface BaseDAO<T extends Serializable, PK extends Serializable> {
//various CRUD methods and generic finders
}

//JPA implementation of BaseDAO with Spring JpaDaoSupport
public class BaseJPADAO<T extends BaseEntity, PK extends Serializable> extends JpaDaoSupport implements BaseDAO<T, PK> {

}

public interface TblFndDAO extends BaseDAO<TblFnd,String> {
//various finders
}

/**
* @spring.bean id="tblFndDAO"
* @spring.property name="entityManagerFactory" ref="entityManagerFactory" //inject entityManagerFactory for Spring JpaDaoSupport
* @author Enrico Goosen
*/
public class TblFndJPADAO extends BaseJPADAO<TblFnd,String> implements TblFndDAO {

}

@Entity
@Table(name="TBL_FND",schema="EBSTATUS")
public class TblFnd extends BaseEntity {

}

/**
* Manage all the Fund Detail Business service requirements
* @spring.bean id="fundDetailsManager"
* @spring.property name="tblFndDAO" ref="tblFndDAO"
*/
@Transactional
public class FundDetailsManagerBean implements FundDetailsManager {

}

Regards,
Enrico Goosen