martes, 23 de diciembre de 2008

Lecciones aprendidas en el uso simultáneo de GWT, Axis y JPA

En mi experiencia con las tres tecnologías que dan nombre al artículo, me he encontrado con algunos problemas bastante fáciles de resolver una vez sabes de qué se trata (como siempre). Por eso, y para evitar que otros se peguen con lo mismo, aquí van una serie de lecciones aprendidas que deben ser tenidas en cuenta a la hora de trabajar con el Google Web Toolkit, Axis y cualquiera de las implementaciones de JPA. Para una comparativa detallada entre diferentes implementaciones de JPA (Hibernate, Toplink, Eclipselink y Openjpa), tienes este otro artículo disponible.
Si quieres ver este artículo en inglés, lo tienes disponible aquí.

Pues bien, los hechos:
  • En primer lugar, así como el EntityManagerFactory puede ser compartido por diferentes objetos (e hilos), el EntityManager no puede ser compartido entre diferentes threads. Esto está especificado en JPA, pero no le das importancia hasta que empiezas a usar JPA desde la web (que será el caso más habitual).
  • En segundo lugar, Axis crea un nuevo objeto (e hilo) por cada petición que entra al servidor.
  • En tercero, y al contrario que con Axis, al utilizar uno de los llamados servicio remotos de servidor del GWT se crea un objeto (y thread) que es posteriormente reutilizado cada vez que se llama a dicho servicio. Es decir, no se crea uno nuevo por cada uno que entra.
Las consecuencias a las que dan lugar los hechos:
  1. Si utilizamos un objeto EntityManager estático en una llamada a Axis, el código fallará en el momento en que dos hilos se crucen (bastante normal en la web), al utilizar el mismo objeto los 2.
  2. En el caso de GWT, al contrario que en Axis, no es necesario que el EntityManager sea estático para que de problemas (que también los dará, claro); bastará con que sea un atributo global dentro del objeto que implementa el servicio remoto. Al ser un objeto único el que responde a las peticiones, 2 llamadas al servidor utilizarán el mismo EntityManager y esto hará que código falle.
  3. Un servicio de Axis deberá, por tanto, crear un nuevo EntityManager, y éste no podrá ser estático.
  4. Cada método de un servicio remoto de GWT tiene que crear un nuevo EntityManager para uso individual (no vale que lo cree el objeto a nivel global, ver punto 2. Si no lo hace, 2 llamadas simultáneas al mismo servicio remoto utilizarán a la vez el mismo EntityManager, dando inmediatamente lugar a excepciones.
Por desgracia, las excepciones de JPA son muy poco explícitas. Esto es común para casi todas las implementaciones de JPA: dan poca información concreta de errores (incluyendo a Hibernate).

El EntityManagerFactory sí puede ser compartido por los diferentes hilos.

En definitiva, si no queréis tener problemas y queréis usar un código que puedan utilizar tanto GWT como las llamadas a servicios web a través de Axis, podríais usar un código como el siguiente:

public class JPAManager {
private final static EntityManagerFactory emf =
Persistence.createEntityManagerFactory("JPAImplementationTest");
private EntityManager em = emf.createEntityManager();
public EntityManager getEntityManager () {
return em;
}
}

Cada método llamado en la invocación del web service o del servicio remoto de servidor de GWT deberá crear su propio JPAManager. De esta forma, os evitáis tener problemas entre los diferentes hilos creados en las invocaciones.

Se pueden usar métodos más sofisticados, como implementar un singleton o usar el ServletContextListener, como ha puesto alguno en los comentarios. Pero lo cierto es que este sencillo método funcionó y resistió algunas pruebas de carga.

No hay comentarios: