viernes, 3 de abril de 2009

JPA - Java Persistence API

Java Persistence API (JPA) proporciona un modelo de persistencia basado en POJO's para mapear bases de datos relacionales en Java. El Java Persistence API fue desarrollado por el grupo de expertos de EJB 3.0 como parte de JSR 220, aunque su uso no se limita a los componentes software EJB. También puede utilizarse directamente en aplicaciones web y aplicaciones clientes; incluso fuera de la plataforma Java EE, por ejemplo, en aplicaciones Java SE.


En su definición, se han combinado ideas y conceptos de los principales frameworks de persistencia como Hibernate, Toplink y JDO, y de las versiones anteriores de EJB. Todos estos cuentan actualmente con una implementación JPA.


El mapeo objeto/relacional, es decir, la relación entre entidades Java y tablas de la base de datos, se realiza mediante anotaciones en las propias clases de entidad, por lo que no se requieren ficheros descriptores XML. También pueden definirse transacciones como anotaciones JPA.


Java Persistence API consta de tres áreas:


El Java Persistence API
El lenguaje de query
El mapeo de los metadatos objeto/relacional A continuación se muestra un pequeño índice con la ficha descompuesta:

Características
Ventajas e inconvenientes
Versiones: No aplica
Requisitos e incompatibilidades
Interacción con otros subsistemas o componentes
Modo de empleo
Enlaces de interés
Recomendaciones de uso


Características

Una entidad es un objeto de dominio de persistencia. Normalmente, una entidad representa una tabla en el modelo de datos relacional y cada instancia de esta entidad corresponde a un registro en esa tabla.

El estado de persistencia de una entidad se representa a través de campos persistentes o propiedades persistentes. Estos campos o propiedades usan anotaciones para el mapeo de estos objetos en el modelo de base de datos.

El estado persistente de una entidad puede ser accesible a través de variables de instancia a la entidad o bien a través de las propiedades de estilo de JavaBean. Lo campos o propiedades pueden tener asociados los siguientes tipos Java:

Tipos primitivos de Java
java.lang.String
Otro tipo de objeto serializable, incluyendo:
Wrappers de tipos primitivos en Java
java.math.BigInteger
java.math.BigDecimal
java.util.Date
java.util.Calendar
java.sql.Date
java.sql.Time
java.sql.TimeStamp
User-defined serializable types
byte
Byte
char
Character
Tipos enumerados

Otras entidades y/o colecciones de entidades Las entidades podrán utilizar campos persistentes o propiedades. Si las anotaciones de mapeo se aplican a las instancias de las entidades, la entidad utiliza campos persistentes, En cambio, si se aplican a los métodos getters de la entidad, se utilizarán propiedades persistentes. Hay que tener en cuenta que no es posible aplicar anotaciones tanto a campos como a propiedades en una misma entidad.

Campos persistentes
Si la entidad utiliza campos persistentes, los accesos se realizan en tiempo de ejecución. Aquellos campos que no tienen anotaciones del tipo javax.persistence.Transient o no han sido marcados como Java transitorio serán persistentes para el almacenamiento de datos. Las anotaciones de mapeo objeto/relación deben aplicarse a los atributos de la instancia.

Propiedades persistentes
Si la entidad utiliza propiedades persistentes, la entidad debe seguir el método de los convenios de componentes JavaBeans. Las propiedades de JavaBean usan métodos getters y setters en cuyo nombre va incluido el atributo de la clase al cual hacen referencia. Si el atributo es booleano podrá utilizarse isProperty en lugar de getProperty. Por ejemplo, si una entidad Customer, utiliza las propiedades de persistencia, supongamos que tiene un atributo privado denominado firsName, la clase definirá los métodos getFirstName y setFirstName para recuperar y establecer el valor de la variable firstName.

Los métodos para la firma de un valor único de propiedades son los siguientes.

Tipo getProperty ()
void setProperty (Tipo tipo)

Tanto los campos persistentes como las propiedades deben utilizar las interfaces de Java independientemente de que la entidad utilice campos o propiedades. Las colecciones posibles son:

java.util.Collection
java.util.Set
java.util.List
java.util.Map
Si la entidad utiliza campos persistentes, el tipo en el método anterior debe ser uno de estos tipos de collection. Las variables genéricas de estos tipos también pueden ser utilizadas. Por ejemplo, si la entidad Customer tiene un atributo que contiene un conjunto de números de tlfno, tendrá que tener los siguientes métodos:

Set getPhoneNumbers() {}
void setPhoneNumbers(Set) {}

Las anotaciones del mapeo objeto/relacional deben aplicarse a los métodos getter. El mapeo de las anotaciones no puede aplicarse a los campos o propiedades anotadas como @Transient o marcadas como transient.

Clases con claves primarias
Una clase con clave primaria debe cumplir los siguientes requerimientos:
El modificador de control de acceso de la clase debe ser público
Las propiedades de la clave primaria deben ser públicas o protected si se utiliza el acceso a la base de la propiedad.

La clase debe tener un constructor público por defecto.
La clase debe implementar los métodos hashCode() y equals(Object other)
La clase debe ser serializable.

Una clave primaria debe representarse y mapearse por campos múltiples o propiedades de la clase de la entidad, o debe representarse y mapearse como una clase embebida.
Si la clave primaria está compuesta por varios campos o propiedades, los nombres y tipos de campos de la clave primaria o propiedades en la clave primaria debe coincidir con las de la entidad.

Relaciones múltiples de la entidad
Hay cuatro tipo de relaciones: uno a uno, uno a muchos, muchos a uno, y muchos a muchos.
Uno a uno: Cada entidad se relaciona con una sola instancia de otra entidad. Por ejemplo, al modelo físico de almacén en el que cada almacén contiene un único artilugio, StorageBin y Widget, deberían tener una relación uno a uno. Las relaciones uno a uno utilizan anotaciones javax.persistence.OneToOne.

Uno a muchos: Una entidad, puede estar relacionada con varias instancias de otras entidades. Una orden de venta (Order), por ejemplo, puede tener varias partidas (LineItem). En la aplicación de la orden, La orden (Order) tendrá una relación uno a muchos con las partidas (LineItem). Las relaciones uno a muchos utilizan anotaciones javax.persistence.OneToMany en los campos o propiedades persistentes.

Muchos a uno: Múltiples instancias de una entidad pueden estar relacionadas con una sola instancia de otra entidad. Esta multiplicidad es lo contrario a la relación uno a muchos. En el ejemplo anterior, desde la perspectiva de la orden de venta (LineItem) la relación con la Orden (Order) es de muchos a uno. Las relaciones muchos a uno utilizan anotaciones javax.persistence.ManyToOne en los campos o propiedades persistentes.

Muchos a muchos: En este caso varias instancias de una entidad pueden relacionarse con múltiples instancias de otras entidades. Por ejemplo, cada curso de una universidad tiene muchos estudiantes, y cada estudiante puede tener varios cursos. Por lo tanto, en una solicitud de inscripción, los cursos y los estudiantes tendrían una relación muchos a muchos. Este tipo de relación utiliza anotaciones javax.persistence.ManyToMany en los campos o propiedades persistentes.

Relaciones y borrado en cascada
Existen entidades que utilizan relaciones con dependencias de relaciones de otra entidad. Por ejemplo, una línea es parte de una orden, y si la orden es eliminada, entonces la línea también debe eliminarse. Esto se llama borrado en cascada. Las relaciones de borrado en cascada se especifican utilizando cascade=REMOVE, elemento que viene en la especificación de las relaciones @OneToOne y @OneToMany. Por ejemplo:

@OneToMany(cascade=REMOVE, mappedBy="customer")
public Set getOrders() { return orders; }

Entity Manager
Las entidades son gestionadas por el Entity Manager. Éste se representa por instancias javax.persistence.EntityManager. A cada instancia de EntityManager se le asocia con un contexto de persistencia. Un contexto de persistencia define el ámbito particular, bajo el cual se crean o se eliminan las instancias de la entidad.

El contexto de persistencia
Un contexto de persistencia es un conjunto de instancias que existen en un almacén de datos. El interfaz EntityManager define los métodos que se utilizan para interactuar con el contexto de persistencia.

Interfaz EntityManager
La API entityManager crea y elimina instancias de entidades persistentes, busca entidades a partir de su clave primaria y permite ejecutar queries.

Contenedor de Manager entity
Con un contenedor de entidades, una instancia de un EntityManager de un contexto persistente se propaga automáticamente por el contenedor de aplicación a todos los componentes que utilizan la instancia del EntityManager en una sola transacción Java (JTA).
Para obtener una instancia de un EntityManager, basta con incluir la entidad dentro del componente de la aplicación:

@ PersistenceContext

EntityManager em;Con entity managers por el contrario, el contexto de persistencia no se propaga a los componentes de la aplicación y el ciclo de vida de las instancias del EntityManager se gestionan por la aplicación.

El administrador de EntityManagers de la aplicación se usa cuando las aplicaciones necesitan acceso al contexto de persistencia cuando no se propaga con la transacción JTA a través de la instancia del EntityManager en una unidad de persistencia particular. En este caso, cada EntityManager crea un nuevo contexto de persistencia.El EntityManager, y sus contextos de persistencia asociados, se crean y se destruyen explícitamente por la aplicación.

Las aplicaciones crean instancias de EntityManager en esos casos mediante el método createEntityManager de javax.persistence.EntityManagerFactory.

Para obtener una instancia EntityManager, primero debe obtener una instancia EntityManagerFactory mediante la una anotación de javax.persistence.PersistenceUnit:

@ PersistenceUnit
EntityManagerFactory emf;

A continuación, se obtiene un EntityManager de la instancia EntityManagerFactory:

EntityManager em = emf.createEntityManager ();

Se buscan las entidades utilizando el EntityManager. El método EntityManager.find se utiliza para buscar entidades en el almacén de datos a partir de la clave primaria de la entidad.

@ PersistenceContext
EntityManager em;
public void enterOrder (int custID, Order newOrder) (
Customer cliente = em.find (Customer.class, custID);
cliente.getOrders (). add(newOrder);
newOrder.setCustomer (cliente);

Gestión del ciclo de vida de una instancia Entity
Las instancias de un entity pueden encontrarse en uno de estos cuatro estados: nuevo, gestionado, trasladado o eliminado.

Una nueva instancia no tiene identidad de persistencia e incluso no tiene asociado su contexto de persistencia. Una instancia gestionada tiene una identidad persistente y está asociada con un contexto de persistencia.

Una instancia trasladada tiene una identidad persistente y no se encuentra actualmente asociada con un contexto de persistencia.

Una instancia eliminada tiene una identidad persistente y se encuentra asociada con un contexto persistente. Se preve el borrado del almacén de datos.

Instancias de entidades persistentes
Una nueva instancia se gestiona y se actualiza invocando al método persist, o por una operación de actualización en cascada llamada desde una entidad relacionada a través de las anotaciones cascade=PERSIST o cascade=ALL. Esto significa que el dato de la entidad se almacena en base de datos cuando la transacción asociada con la operación persist se completa. Si la entidad ya se encuentra actualizada, la operación persist se ignora, aunque la operación persist se ejecutará en cascada con las entidades relacionadas. Si se llama al método persist o al método removed, se convierte en una instancia gestionada. Si la entidad se separa, el método persist elevará una excepción IllegalArgumentException, o la realización del commit fallará.

@PersistenceContext
EntityManager em;
...
public LineItem createLineItem(Order order, Product product,
int quantity) {
LineItem li = new LineItem(order, product, quantity);
order.getLineItems().add(li);
em.persist(li);
return li;
}

La operación persist se propaga a todas las entidades relacionadas con la entidad que tienen el elemento de cascada configurado con ALL o PERSIST

@OneToMany(cascade=ALL, mappedBy="order")
public Collection getLineItems() {
return lineItems;
}

Eliminar instancias entity
La instancias del entity managed se eliminan invocando al método remove, o por un borrado en cascada invocado desde una entidad relacionada. Si el método remove se invoca desde una entidad nueva, la operación de borrado se ignora, aunque se borrarán en cascada las entidades relacionadas que tienen configurado su borrado en cascada mediante las anotaciones REMOVE o ALL. Si el método remove se invoca en una entidad en estado trasladada se elevará una excepción IllegalArgumentException, la transacción de commit fallará. Si el método remove se invoca desde una entidad ya eliminada, se ignorará. El dato representado por la entidad se eliminará de la base de datos cuando la transacción se complete, o cuando se realice la operación de descarga.

public void removeOrder(Integer orderId) {
try {
Order order = em.find(Order.class, orderId);
em.remove(order);
}...

En este ejemplo, todos los LineItem, entidades asociadas con el objeto order, serán eliminados, ya que Order.getLineItems tiene configurado el cascade=ALL en la relación de anotación.

Sincronización entre entidades y base de datos
El estado de las entidades persistentes se sincroniza con la base de datos cuando la transacción con la que la entidad está asociada realice el commit. Para forzar la sincronización de la entidad con la base da datos, se invoca el método flush de la entidad. Si la entidad está relacionada con otra, y la relación es en cascada, los datos de la entidad se sincronizan con la base da datos cuando se llama al método flush. Si la entidad se borra, se llama al método flush, el cual borrará la entidad de la base da datos.

Creación de consultas
Los métodos EntityManager.createQuery y EntityManager.createNamedQuery se utilizan para realizar consultas usando lenguaje de consultas. El método createQuery se utiliza para crear queries dinámicas; queries que se definen directamente en la aplicación dentro de la lógica de negocio.

public List findWithName(String name) {
return em.createQuery(
"SELECT c FROM Customer c WHERE c.name LIKE :custName")
.setParameter("custName", name)
.setMaxResults(10)
.getResultList();
}

El método createNamedQuery se utiliza para crear queries estáticas, queries que se definen utilizando el javax.persistence.NamedQuery . La anotación @NamedQuery especifica el nombre de la consulta que se utilizará con el método createNamedQuery. El elemento de consulta @ NamedQuery es la query.

@NamedQuery(
name="findAllCustomersWithName",
query="SELECT c FROM Customer c WHERE c.name LIKE :custName"
)

En este ejemplo se utiliza la anotación @NamedQuery definida anteriormente.

@PersistenceContext
public EntityManager em;
...
customers = em.createNamedQuery("findAllCustomersWithName")
.setParameter("custName", "Smith")
.getResultList();

Paso de parámetros en las queries
Los parámetros de una consulta son predefinidos utilizando un signo de dos puntos (:). Los parámetros se especifican en una query mediante el método javax.persistence.Query.setParameter (String nombre, Object valor). En el siguiente ejemplo, el parámetro del método findWithName debe definirse como :custName especificado con el método Query.setParameter.

public List findWithName(String name) {
return em.createQuery(
"SELECT c FROM Customer c WHERE c.name LIKE :custName")
.setParameter("custName", name)
.getResultList();
}

El nombre de los parámetros es sensible a mayúsculas y minúsculas, y puede ser utilizado por queries estáticas y dinámicas.

Posicionamiento de parámetros en las queries
Dentro de las queries, los parámetros son predefinidos con un signo de interrogación (?) seguido del número que representa la posición del parámetro en la query. El método Query.setParameter (Integer posición, Object, value) se usa para fijar el valor del parámetro.

En el siguiente ejemplo, el método findWithName es reescribe para utilizando entrada de parámetros:

public List findWithName(String name) {
return em.createQuery(
“SELECT c FROM Customer c WHERE c.name LIKE ?1”)
.setParameter(1, name)
.getResultList();
}

La entrada de parámetros comienza enumerándose en la posición 1. Los parámetros son sensibles a mayúsculas y minúsculas, y pueden utilizarse tanto en queries dinámicas como estáticas.

Unidades de persistencia
La unidad de persistencia define un conjunto de todas las entidades (clases) que son gestionadas por la instancia del EntityManager en una aplicación. Este conjunto de clases de entidad representa los datos contenidos en un único almacén de datos.

Las unidades de persistencia se definen en el fichero de configuración persistence.xml. El fichero JAR cuyo directorio META-INF contiene persistence.xml se llama raíz de la unidad de persistencia. El ámbito de la unidad de persistencia se determina por la raíz de la unidad de persistencia.

Cada unidad de persistencia debe ser identificada con un nombre único en el ámbito de la unidad de persistencia.

Las unidades de persistencia pueden empaquetarse como parte de un WAR o un fichero EJB JAR, o pueden empaquetarse como un archivo JAR que pueda ser incluido en un fichero WAR o EAR.

El fichero persistence.xml
El fichero persistence.xml define una o más unidades de persistencia:


This unit manages orders and customers.
It does not rely on any vendor-specific features and can
therefore be deployed to any persistence provider.

jdbc/MyOrderDB
MyOrderApp.jar
com.widgets.Order
com.widgets.Customer


Este archivo define una unidad de persistencia llamada OrderManagement, la cual utiliza jdbc / MyOrderDB. El fichero JAR y los elementos de la clase especifican las clases de persistencia: clases de entidad, clases embebidas y superclases mapeadas.
El elemento jar-file especifica los ficheros JAR en los que se encuentran las clases persistentes, mientras que el elemento class indica el nombre de esas clases. El elemento jta-data-source especifica el nombre global JNDI de la fuente de datos que utiliza el contenedor.

Ventajas e inconvenientes
Algunas de las ventajas del Open JPA consisten en que objetos transitorios pueden ser almacenados en forma permanente en una base de datos relacional. Además, Open JPA pretende hacer más eficaz el proceso de desarrollo, permitiendo a los desarrolladores enfocarse a resolver los problemas de negocios a los que se enfrentan, en lugar de ocupar su tiempo con el código de infraestructura.

El Java Persistence API se basa en las mejores ideas de tecnologías como la persistencia Hibernate, TopLink, y JDO. Los clientes ya no tienen que enfrentarse a las incompatibilidades de modelos no estándar. Además, la Java Persistence API se puede usar tanto en entornos Java SE, como en Java EE.

No obstante, tiene la desventaja de que la implementación del proveedor se encuentra oculta a la aplicación lo cual la hace menos flexible.

Requisitos e incompatibilidades
JPA ha sido definida como parte de la especificación EJB 3.0 de Java EE 5, que supone una simplificación sobre versiones anteriores, por lo que ya no requiere de un contenedor EJB ni un servidor de aplicaciones Java EE. Esto implica que no podrá utilizarse en versiones anteriores de Java.

Interacción con otros subsistemas o componentes
Muchos de los desarrolladores de Java que han utilizado objetos persistente que proporcionaban los marcos de código abierto o DAO's en lugar de Entinty Beans e Enterprise Beans fue porque eran considerados demasiado pesados y complicados de utilizar. Además, estos últimos sólo podían utilizarse en servidores de aplicaciones Java EE. Muchas de las características de la persistencia de otros frameworks se incorporaron a la Java Persistence API, y proyectos como Hibernate y TopLink son ahora implementaciones de Java Persistence API (JPA).

Hibernate
La versión 3.2.0 Hibernate desarrolla la especificación JPA, con lo que ahora es posible desarrollar una capa de acceso a datos compatible con los estándares de Java en hibernate, y desplegarla en cualquier servidor de aplicaciones que soporte las especificaciones JEE5.

TopLink
TopLink es una implementación del EJB 3.0 JPA (Java Persistence API). Es una versión limitada del propietario del producto, por ejemplo, TopLink no proporciona sincronización de la caché entre la agrupación de las aplicaciones, la política de validaciones y la caché de consultas.

EJB
JPA se definió como parte de la especificación EJB 3.0 que es a su vez parte de la plataforma Java EE 5. No se utiliza un contenedor de EJB's, ni tampoco un servidor de aplicaciones Java EE con el fin de ejecutar aplicaciones que utilizan persistencia.

Modo de empleo (Aplicación Web)
Los datos compartidos entre los componentes web y las invocaciones de persistencia de una aplicación se mantienen normalmente en base de datos. Las aplicaciones web utilizan JPA para acceder a esas bases de datos relacionales.

Para gestionar la interacción de las entidades con la JPA, una aplicación utiliza la interfaz EntityManager. Esta interfaz proporciona métodos que realizan las funciones comunes de una base de datos, como consulta y actualización de la base de datos.

El conjunto de entidades que pueden ser gestionadas por un Entity Manager se definen en una unidad de persistencia. Ésta supervisa todas las operaciones de persistencia en la aplicación. La unidad de persistencia está configurada por un descriptor de archivo llamado persistence.xml. Este fichero también define el datasource, el tipo de transacciones utilizadas por la aplicación, junto con otra información adicional. Estos ficheros se deben empaquetar en un .jar y añadirlos a la aplicación.


Enlaces de interés
Java EE 5 Tutorial
http://en.wikipedia.org/wiki/Java_Persistence_API

Recomendaciones de Uso
Utilizar un framework de ORM simplifica enormemente la programación de la lógica de persistencia. Se trata de una idea completamente madura que cada vez se vuelve más popular. En aplicaciones donde la lógica de negocios trabaja contra un modelo de dominio completamente orientado a objetos la generación de código se reduce entre un 30% y un 40%. Además el código generado suele ser mucho más sencillo y mantenible.

Es fundamental conocer bien cómo funcionan las tecnologías que se utilizan ya que dependiendo de cómo se realicen las cosas podrá afectar directamente al rendimiento de la aplicación. Esto no quiere decir que el uso de JPA sea desaconsejable, sino todo lo contrario, proporciona grandes beneficios como es la independencia de la base de datos, bajo acoplamiento entre negocio y persistencia, y un desarrollo rápido. Esto permite centrar los esfuerzos en optimizar las consultas que realmente lo merecen.

Para obtener el mejor rendimiento de JPA, es importante entender los parámetros de tunning incluídos en la implementación de la JPA. La configuración de la caché es un parámetro importante de tunning. Ajustar el pool de conexiones también es importante para obtener el mejor rendimiento de cualquier implementación de JPA.

Si se utiliza JPA fuera del contenedor EJB, la caché y el pool de conexiones también juegan un papel importante en el rendimiento, aunque en este caso se configura el pool en el fichero persistence.xml.

Hay otros parámetros que pueden impactar en el rendimiento de JPA. Uno es el statement cache. Éste se debe especificar explícitamente para algunas bases de datos como Oracle. Además, la configuración de la Java Virtual Machine (JVM) puede jugar un rol importante en el ajuste de la persistencia. Para tener información detallada del ajuste de la JVM, véase el Java Tuning White Paper. Para la ejecución de la aplicación de este tip, las opciones de la JVM las pondremos así:

-server -XX:+AggressiveHeap -Xmx2500m -Xms2500m -Xss128k
-XX:+DisableExplicitGC

1 comentario:

Alfonso dijo...

Excelente artículo. Felicitaciones