November 30th, 2011

JPA: Java Persistence API

Christian Marcusa

Hace muchos años que venimos desarrollando aplicaciones con frameworks de ORM, como Hibernate, TopLink, iBatis, para manejar la persistencia de las mismas. JPA es una especificación cada día mas utilizada, que intenta estandarizar el “acceso” a todos ellos.

 

Necesidad de estandarización

Existen muchos aspectos importantes en las aplicaciones que el lenguaje no nos provee, por ejemplo persistencia, transaccionalidad, seguridad, etc. Para lograr estos aspectos, se han hecho importantes implementaciones en contenedores de nuestro codigo, asi nuestro modelo queda limpio y alguien por afuera sería el encargado de manejar todos estas cuestiones transversales a nuestra aplicación. Así nace el concepto de servidor de aplicaciones, el cual intenta ser un manejador de todos los aspectos transversales a nuestra aplicación, por ejemplo jBoss y GlassFish. Un problema de estos es que no eran compatibles entre ellos, una aplicación desarrollada con Jboss nacia y moria con Jboss. Podriamos decir que este fue el incentivo de comenzar a estandarizar y hacer transparente nuestro codigo al servidor de aplicaciones, y de esta forma nace JavaEE y sus beans, tema en el que no profundizaremos mucho. De ahi en mas el gran trabajo de la comunidad java fue crear especificaciones que distintos “frameworks” o servidores de aplicaciones implementen y asi nuestra aplicación sea independiente del contexto donde va a ser ejecutada, o mejor dicho, de quien implemente esa especificación. Así nacen especificaciones como CDI (Context and Dependency Injection), JPA (Java Persistence API), JTA(Java Transactions API).

¿Qué es JPA?

JPA como dijimos, intenta especificar los accesos estándares a un contexto persistente en nuestra aplicación, de forma que si yo cambio el motor de persistencia subyacente no tenga que tocar el código. Por ahi aveces decimos (y es verdad) cosas como “Una aplicación que nace con Hibernate muere con Hibernate” o “No existe ningun contexto en el que me interesaría cambiar de ORM”, pero convengamos que todo lo que sea migrar a un estándar nunca puede ser malo, en primer lugar porque al utilizar un FW podemos utilizar todos, ya que la forma de utilización (como los xmls que configuro y/o las annotations que pongo en mi codigo) pasan a ser estándares, y por lo tanto, la curva de aprendizaje al conocer uno, baja considerablemente para los demás. Otra cosa importante, supongamos que los servidores de aplicaciones ahora manejan un contexto persistente. Con JPA nos ahorramos grandes temas de persistencia si quisieramos movernos a dicho server. Supongamos además, que empezamos a trabajar en Google App Engine, donde el esquema persistente no es relacional (BigTable), donde frameworks ORM no son necesarios porque la base subyacente posee una forma de uso distinta. No sería util utilizar JPA? No sería lo mismo a nivel código? Solo cambia la implementación del framework subyacente. Cómo todo, tiene limitaciones. La primera es que JPA al ser un estándar, se para en el medio de todos los frameworks. Al momento de su creación seguro trataron de abarcar los aspectos mas significativos de la mayoría de estos, pero en este intento se queda por debajo de los frameworks mas completos. Por ejemplo, se queda corto con algunas cosas que Hibernate ofrece. En JPA 1 por ejemplo, no hay estándar para delete-orphan ni para construir criterias, ya en JPA2 estos features fueron agregados, pero la api que ofrece JPA para construir ids para los objetos es mucho mas limitada que lo que Hibernate ofrece.

¿Cómo utilizarlo?

Bueno principalmente JPA utiliza annotations parecidas a las de Hibernate pero del paquete javax.persistence.* . Eso no va a cambiar, si bien cambian algunas pequeñeces como la forma de definir los cascades orphan deletion, las annotations importantes y la forma de mapear siguen siendo iguales (OneToMany, ManyToMany, JoinTable, MappedBy, etc). Luego JPA no utiliza los objetos de hibernate (Session y SessionFactory), sino que utiliza EntityManager y EntityManagerFactory, que intentan ser la especificación de los mismos. Tienen usos similares pero, al ser JPA una especificación transversal a todos los frameworks de persistencia, al mediar entre todos ellos puede resultar menos poderoso que utilizar directamente los objetos de (en este caso) Hibernate. Un ejemplo de esto es que en JPA1 no podíamos realizar queries por Criteria (Ya implementado un criteria builder en JPA2). Mas allá de todo esto, tiene un uso muy parecido a Hibernate, veamos un ejemplo de query en JPQL (Java Persistence Query Language, que vendría a ser el estándar de HQL):


@PersistenceContext
private EntityManager entityManager;

public List<User> findPerProfile(UserProfile userProfile) {
return (List<User>) entityManager
.createQuery("select i from User i where i.profile = ?1")
.setParameter(1, userProfile)
.getResultList();
}

Una aclaración muy importante es que si queremos utilizar alguna funcionalidad de hibernate que JPA no soporte aún, o no exista su estándar todavía, podemos utilizar las annotations de JPA, las configuraciones de JPA, pero aún así utilizar los beans de hibernate (Session y SessionFactory). El tradeoff de hacer esto es que estaríamos “perdiendo” el estándar, por lo que “cambiar” de ORM sería un trabajo mucho mas costoso.

Además los aspectos generales de la persistencia se definen en el archivo persistence.xml ubicado en resources/META-INF, y no en en el archivo hbm.cfg de hibernate. Para establecer una configuración definimos un persistence-unit (que sirve como una unidad de configuración que luego le pasaremos al EntityManager) y en él ponemos los tags de configuración de una manera muy similar a cómo lo hacemos en el bean de spring. Veamos un ejemplo de configuración sobre Hibernate y PostgreSQL:

<persistence-unit name="unit" transaction-type="RESOURCE_LOCAL">
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
<property name="hibernate.use_sql_comments" value="true"/>
<property name="hibernate.format_sql" value="false"/>
<property name="hibernate.max_fetch_depth" value="3"/>
<property name="hibernate.default_batch_fetch_size" value="10"/>
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.HashtableCacheProvider"/>
<property name="hibernate.cache.use_query_cache" value="true"/>
</properties>
</persistence-unit>

 

Integración con otras tecnologías

Por ultimo es bueno aclarar que los frameworks mas utilizados de DI (como Spring y Seam) se integran perfecto con JPA. Hablando de Spring, solamente tenémos que incluir el bean de EntityManagerFactory de spring en nuestro application context con nuestras configuraciones, una referencia al dataSource, y la ubicación del persistence.xml y sale igual que el SessionFactory. Veamos:


<bean id="entityManagerFactory">
<property name="dataSource" ref="dataSource"/>
<property name="persistenceXmlLocation" value="classpath:META-INF/persistence.xml"/>
<property name="jpaVendorAdapter">
<bean>
<property name="showSql" value="${hibernate.show_sql}"/>
<property name="databasePlatform" value="${hibernate.dialect}"/>
</bean>
</property>
</bean>

<bean id="transactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

En el caso de Seam,  además de integrarse perfectamente lo extiende implementando CDI (Context and Dependency Injection) y permite inyectar estado conversacional trabajando con objetos managed de JPA durante varios request del cliente (Sin convertirse esos objetos en detached).

Dejar un comentario