martes, 7 de febrero de 2012

Eclipselink Join Fetch - Cargar Relaciones Jpa

En el post anterior les comentaba mi problema con un ejb remoto y la entidad jpa que este me devolvía, bueno al final les mencione que si tienen que acceder a las relaciones lazy deben inicializarlas, aquí les dejo como cargar las relaciones lazy con un query hint

Creamos el query

Query query = entityManager.createQuery("select p from Person p where p.id = :id");
query.setParameter("id",  10);

Para colocar el query hint hacemos los sigueinte

query.setHint("eclipselink.join-fetch", "p.telefonos" );

Person p = query.getSingleResult();

Ahora en el objeto p sus telefonos ya estan cargados, desafortunadamente si en telefonos su objeto tiene otra relación a la que queremos acceder tendremos que inicializarla también pero seria con código o bueno no encontré una forma de hacerlo con otro queryhint, si la encuentran pues  espero me la digan.

Esto solo tenemos que hacerlo cuando invocamos desde una aplicación un ejb remoto que devuelve una entidad jpa y usamos eclipselink de lo contrario al acceder a una relación no inicializada nos lanzara una excepción que nos indica que debemos inicializarla, si trabajamos las entidades en ejb locales no es necesario inicializar las relaciones, eclipselink lo hace en cuanto se intente acceder a ella

Glassfish, Eclipselink, Ejb Remoto Problema con Entidad

La semana pasada realizando una aplicación que se comunica con otra aplicación atraves de un ejb remoto el cual le devolvía una entidad jpa me resultaba en problemas de null pointer al querer acceder a las propiedades de dicha entidad. Viendo el modo debug me di cuenta de que estaba recibiendo del ejb remoto un objeto el cual tenia todas las propiedades nulas, comentando esto con otro compañero me comento que había tenido el mismo problema y la causa era que no tenían la misma versión de la clase de ese objeto en la aplicación que invoca al ejb remoto y la aplicación que contiene a este (Nota: en este caso  no se trata de una entidad jpa).

Verificando lo anterior revise que tuviera la misma versión de la entidad en ambas aplicaciones pero aun se mantenía el error, buscando en google me encontré que el problema era provocado por eclipselink (usamos eclipselink para jpa en esta aplicación), eclipselink tiene una optimización llamada weaving, esta optimización la realiza por default al cargar las clases en el classloader, hace modificaciones del bytecode por lo que al hacer esto ya no tenia la misma versión de las clases en la aplicaciones.

Para solucionarlo existen 2 opciones una es deshabilitar el weaving de eclipselink pero esta opción nos quita las optimizaciones por lo que puede impactar en el performance de la aplicación, y la otra opción es utilizar el weaving estático con esta opción se procesan las clases de nuestras entidades para utilizarlas en las aplicaciones por lo que tendríamos el mismo bytecode en cada aplicación.

Para deshabilitar el weaving dinámico podemos colocar la siguiente propiedad en nuestro persistence.xml

< property name="eclipselink.weaving" value="false" / >

Para habilitar el weaving estático son mas pasos, el primero colocar la propiedad de eclipse link con valor estático

< property name="eclipselink.weaving" value="static" / >

El segundo paso es "compilar" las classes entidad que tenemos, esto lo podemos hacer con ant, si tiene la ultima version (1.8.2) el build.xml es diferente al que esta en la pagina de eclipse link, seria así :

<project name="Weaving-Eclipselink" default="clean" basedir=".">
    <description>
        Build file to generate static weaving
    </description>
  <!-- set global properties for this build -->
  <property name="dist"  location="dist"/>
  <property name="jarToWeaving"  value="myjpa.jar"/>

  <target name="init">
    <!-- Create the time stamp -->
    <tstamp/>
    <mkdir dir="${dist}"/>
    <!--copy file="${jarToWeaving}" todir="${dist}"/-->
    <copy todir="${dist}" overwrite="true" >
         <fileset dir=".">
             <include name="${jarToWeaving}" />
         </fileset>
    </copy>
  </target>

  <taskdef name="weave" classname="org.eclipse.persistence.tools.weaving.jpa.StaticWeaveAntTask"/>
 
  <target name="weaving" description="perform weaving" >
    <weave source="${jarToWeaving}"
           target="${dist}/${jarToWeaving}"
           persistenceinfo="./persistence">
      <classpath>
        <pathelement path="./lib/javax.persistence.jar"/>
      </classpath>
    </weave>
  </target>

  <target name="clean" description="clean up" >
    <!-- Delete the ${build} and ${dist} directory trees -->
    <delete dir="${dist}"/>
  </target>

</project>

Donde myjpa.jar es el jar que contiene las clases que deseo procesar para generar el weaving estático, se crea una carpeta dist y ahí se genera el nuevo jar con el weaving estático, otro punto importante es persisteninfo ahí le indico el archivo persistence.xml en el que se basara para generar el weaving estático, en mi caso cree la carpeta persistence al mismo nivel del build.xml y dentro de ella tengo META-INF y dentro de META-INF se encuentra mi archivo persistence.xml (eclipselink requiere la carpeta META-INF). El classpath de la tarea weaving no me funciono muy bien por lo que tuve que pasar las lib en la ejecución de ant

ant -lib ./lib/eclipselink.jar:./lib/javax.persistence.jar weaving




Una vez que tenemos el jar con el weaving estatico podemos incluir este en las aplicaciones que usan las entidades y con esto al invocar el ejb remoto obtenemos correctamente la entidad, bueno hay que tener cuidado con las relaciones lazy hay que inicializarlas en el ejb remoto si las vamos a utilizar.

Mas informacion de eclipselink weaving

http://wiki.eclipse.org/Using_EclipseLink_JPA_Extensions_%28ELUG%29#Using_EclipseLink_JPA_Weaving