Referencia JPA

RECU-0176 (Recurso Referencia)

Descripción

Java Persistence API, más conocida por su sigla JPA, es la API de persistencia desarrollada para la plataforma Java EE e incluida en el estándar EJB3. Esta API busca unificar la manera en que funcionan las utilidades que proveen un mapeo objeto-relacional. El objetivo que persigue el diseño de esta API es no perder las ventajas de la orientación a objetos al interactuar con una base de datos, como pasaba con EJB2, y permitir usar objetos regulares (conocidos como POJOs).

JPA es un modelo basado en POJO para la persistencia estándar para el ORM. Es parte de la especificación de EJB 3 y sustituye a beans de entidad. Los beans de entidad se definen como parte de la especificación de EJB 2.1 y no habían logrado impresionar a la industria como una solución completa para la persistencia por varias razones:

  • Los beans de entidad son componentes pesados y están estrechamente unidos a un servidor Java EE. Esto hace que sean menos adecuados que POJOs ligeros, que son más convenientes para su reutilización.
  • Los beans de entidad son difíciles de desarrollar y desplegar.
  • Los beans de entidad BMP te obligan a utilizar JDBC, mientras que los beans de entidad CMP son altamente dependientes en el servidor de Java EE para su configuración y la declaración de ORM. Estas restricciones afectan el funcionamiento de la aplicación.

JPA toma las mejores ideas de las tecnologías de la persistencia de otros. Se define un modelo de persistencia estándar para todas las aplicaciones Java. JPA puede ser utilizado como la solución tanto para la persistencia de Java SE y Java EE.

JPA utiliza anotaciones de metadatos y/o archivos del descriptor XML para configurar el mapeo entre objetos Java en el ámbito de aplicación y las tablas de la base de datos relacional. JPA es una solución ORM completa y soporta la herencia y polimorfismo. También se define un lenguaje de consulta como SQL, JPQL (Java Persistence Query Language), que es diferente de EJB-QL (EJB Query Language), el lenguaje utilizado por los beans de entidad.

Con JPA, se puede conectar a cualquier proveedor de persistencia que implementa la especificación JPA en lugar de utilizar cualquier proveedor de persistencia que venga por defecto con el contenedor Java EE. Por ejemplo, el servidor de aplicaciones GlassFish, suministrado por Oracle, como proveedor que usa como motor predeterminado de persistencia TopLink Essentials. Pero podría usar Hibernate como proveedor de persistencia mediante la inclusión de todos los archivos JAR necesarios en su aplicación.

¿Que es una Entidad?

La entidad no es una cosa nueva en la gestión de datos. De hecho, las entidades han estado alrededor de muchos lenguajes de programación y, ciertamente, más de Java. Fueron introducidos por primera vez por Peter Chen en su artículo seminal sobre el modelado entidad-relación. Describió entidades como las cosas que tienen atributos y relaciones. La expectativa era que los atributos y las relaciones se persistían en una base de datos relacional.

Incluso ahora, la definición sigue siendo válida. Una entidad es esencialmente un sustantivo, o una agrupación de estado asociados juntos como una sola unidad. En el paradigma orientado a objetos, nos gustaría añadir un comportamiento a la misma y lo llaman un objeto. En JPA, cualquier objeto definido por la aplicación puede ser una entidad, así que la pregunta importante podría ser: ¿Cuáles son las características de un objeto que se ha convertido en una entidad?

Persistencia

La primera y más básica característica de las entidades es que son persistentes. En general, esto sólo significa que pueden hacerse persistentes. Más concretamente, significa que su estado se puede almacenar en una base de datos y se puede acceder en otro momento, tal vez mucho después del final del proceso que lo creó.

Podríamos llamarlos objetos persistentes, y muchas personas lo hacen, pero no es técnicamente correcto. Estrictamente hablando, un objeto persistente se vuelve persistente en el momento en que se crea una instancia en la memoria. Si un objeto persistente existe, entonces, por definición, ya es persistente.

Una entidad es persistente, ya que se puede crear en un almacén persistente. La diferencia es que no se persistió de forma automática y que, para que tenga una representación duradera en la aplicación activa, debe invocar un método de la API para iniciar el proceso. Esta distinción es importante porque deja el control sobre la persistencia en manos de la aplicación. Ésta cuenta con la flexibilidad para manipular los datos y realizar la lógica de negocio de la entidad, por lo que es persistente sólo cuando la aplicación decide que es el momento adecuado. La lección es que las entidades pueden ser manipuladas sin ser necesariamente persistentes, y es la aplicación la que decide si son o no.

Identidad

Al igual que cualquier otro objeto Java, una entidad tiene una identidad de objeto, pero cuando existe en la base de datos también posee una identidad persistente. Esta identidad persistente, o un identificador, es la clave que identifica de forma única una instancia de la entidad y la distingue de todas las otras instancias de la misma entidad.

Una entidad tiene un identidad persistente cuando existe una representación de él en la base de datos, es decir, una fila en una tabla de una base de datos . Si no está en la base de datos , a pesar de que la entidad en memoria puede tener su identidad en un campo, no tiene una identidad persistente. El identificador de la entidad, entonces, es equivalente a la clave principal en la tabla de base de datos que almacena el estado de la entidad.

Transaccionalidad

Las entidades son lo que podríamos llamar cuasi-transaccionales. Aunque se pueden crear, actualizar y eliminar en cualquier contexto, estas operaciones se realizan normalmente en el contexto de una transacción debido a que una transacción es necesaria para que los cambios que sean guardados en la base de datos. Los cambios son realizados en la base de datos ya sean éxito o fracaso, por lo que la visión persistente de una entidad de hecho debe ser transaccional.

En la memoria, es una historia ligeramente diferente en el sentido de que las entidades pueden ser modificadas sin que los cambios sean persistidos. Incluso cuando se listaron en una transacción, se puede dejar indefinido o en un estado inconsistente en el caso de un retroceso o el fracaso de la transacción. Las entidades que están en la memoria son simples objetos Java que obedecen todas las reglas y restricciones que se aplican por la Virtual Java Machine (JVM) a otros objetos de Java.

Granularidad

Por último, también podemos aprender algo acerca de lo que son las entidades al describir lo que no lo son. No son primitivos, envoltorios primitivos, u objetos integrados con el estado de una sola dimensión. Estos no son más que escalares y no tienen ningún significado semántico inherentes a una aplicación. Una cadena, por ejemplo, es demasiado fina para ser un objeto entidad, ya que no tiene ningún dominio específico. Por el contrario, una cadena esta bien adaptada y a menudo es muy utilizada como un tipo de un atributo de entidad y le da un significado de acuerdo con el atributo entidad que lo está escribiendo.

Las entidades que están destinadas a ser objetos de grano fino tienen un conjunto de estado global, el cual es normalmente almacenado en un solo lugar, como una fila de una tabla, y suelen tener relaciones con otras entidades. En el sentido más general, son objetos de dominio de negocio que tienen un significado específico en la aplicación que tiene acceso a ellos.

Si bien es cierto que las entidades pueden ser definidas de manera exagerada, a ser tan fina como almacenamiento de una sola cadena o de grano grueso suficiente para contener un valor de 500 columnas de datos. Las entidades de la JPA estaban destinadas a ser definitivamente el extremo más pequeño del espectro de la granularidad. Idealmente, las entidades deben ser diseñadas y definidas como objetos bastante ligeros, de un tamaño comparable al de la media del objeto de Java.

En JPA disponemos de las anotaciones @Embeddable y @Embedded haciendo más fino el modelado del diseño, lo que lo convierte en un diseño más expresivo. Esta anotación sirve para designar objetos persistentes que no tienen la necesidad de ser una entidad por sí mismas. Esto es porque los objetos @Embeddable son identificados por los objetos entidad, por lo que nunca serán mantenidos o accedidos por sí mismos, sino como parte del objeto entidad al que pertenecen.

Un @Embeddable es un objeto de valor y como tal, deberá ser inmutable. Para ello, sólo pueden existir métodos getters y no setters en su clase. La identidad de un objeto de valor se basa en su estado más que su objeto de identificación. Esto significa que el @Embeddable no tendrá ninguna anotación @Id.

Como ejemplo tenemos:

@Embeddable
public class Address implements Serializable {
   ...
   private String houseNumber;
   private String street;

   @Transient
   public String getHouseNumber() { return houseNumber; }

   @Transient
   public String getStreet() { return street; }

   @Basic
   public String getAddress() { return street + " " + houseNumber; }

   // setter necesitado por JPA, pero protegido ya que el valor del objeto no varía en el dominio
   protected void setAddress(String address) {
       // hacer todo el análisis y la aplicación de las reglas aquí
   }
}

@Entity
public class Person implements Serializable {
   ...
   private Address address;

   @Embedded
   public Address getAddress() { return address; }
   public void setAddress(Address address) { this.address = address; }
}

Se utiliza esta anotación @Embedded, para indicar que se está usando un objeto integrado. Las propiedades de este objeto se vincularán con la tabla de la entidad donde esta siendo utilizado.

Modo de carga de las entidades

El modo de carga de entidades puede ser “LAZILY” o “EAGERLY”. Para explicar la diferencia supongamos que tenemos la siguiente entidad A:

@Entity
public class A{
@Id
private String id;
@Column(name="B)
private B b; // B es una entidad
@Column(name="C)
private C c; // C es una entidad
}

La diferencia entre los dos modos de carga consiste en que, al traer una entidad “A” al contexto JPA (al hacer una consulta), el modo “LAZILY” solo carga los campos de esa entidad “A” y no los campos de las entidades “B” y “C” a los que se hace referencia desde “A”, mientras que el modo “EAGERLY” trae al contexto de JPA todo. 

Entity Manager

Una llamada a la API específica necesita ser invocada ante un entidad que realmente persiste la base de datos. De hecho, son necesarias llamadas separadas a la API para realizar muchas de las operaciones en las entidades. Esta API está implementada por el administrador de la entidad y encapsulada casi totalmente dentro de una única interfaz llamada EntityManager.

Cuando un EntityManager obtiene la referencia a una entidad, ya sea porque se le pasa como argumento en la llamada al método o porque se lee de la base de datos, ese objeto se dice que esta manejado por el EntityManager. El conjunto de instancias manejadas por el EntityManager se considera el contexto de persistencia. Solo una instancia Java con la misma identidad de la persistencia puede existir en un contexto de persistencia al mismo tiempo.

Los EntityManager se configuran para ser capaces de persistir o administrar tipos específicos de objetos, leer y escribir en una base de datos, y ser implementados por un proveedor de persistencia en particular. Es el proveedor qiuen suministra el motor de aplicación para todo el respaldo de Java Persistence API, desde el EntityManager hasta la implementación de las clases de consulta y la generación SQL.

Todos los EntityManagers provienen de fábricas tipo EntityManagerFactory. La configuración de un EntityManager es formateado por la EntityManagerFactory que lo creó, sin embargo se define por separado como una unidad de persistencia.

Una unidad de persistencia dicta, ya sea implícita o explícitamente, los valores y clases de entidad que utilizan todos los EntityManagers obtenidos a partir de la instancia única del EntityManagerFactory asociada a esa unidad de persistencia. Existe, por tanto, una correspondencia uno a uno entre una unidad de persistencia y su EntityManagerFactory concreto.

Las unidades de persistencia permiten la diferenciación de un EntityManagerFactory de otro. Esto le da el control de la aplicación sobre cualquier configuración o unidad de persistencia que se utilizará para operar con una entidad en particular.

Obteniendo un EntityManager

Un EntityManager siempre se obtiene desde un EntityManagerFactory. La factoría desde la que se obtiene determina la configuración de los parámetros que gobiernan la operación. El método estático createEntityManagerFactory() en la clase de persistencia devuelve la unidad de persistenciapara el nombre especificado. Por ejemplo:

EntityManagerFactory emf = Persistence.createEntityManagerFactory("EmployeeService");

El nombre especifico de la unidad de persistencia “EmployeeService” se pasa al método createEntityManagerFactory() identificando la configuración de la unidad de persistencia dada que determina muchas cosas, como los parámetros de conexión. Los EntityManagers son generados desde esta factoría cuando se conectan a la base de datos. Ahora tenemos una factoría, puede fácilmente obtenerse un EntityManager a partir de ella. Como el siguiente ejemplo:

EntityManager em = emf.createEntityManager();

Persistiendo una Entidad

La persistencia de una entidad es la operación de tomar una entidad transitoria, o una que todavía no tiene ninguna representación persistente en la base de datos, y almacenar su estado para que pueda ser recuperada más tarde. Esto es realmente la base de la persistencia de creación de estado, que puede durar más que el proceso que lo creó. Vamos a comenzar usando el gestor de la entidad para que persista una instancia de Empleado. Aquí está un ejemplo de código que hace precisamente eso:

Employee emp = new Employee(158);
em.persist(emp);

La primera línea en este segmento de código es simplemente la creación de una instancia del empleado que queremos persistir. El empleado es sólo una instancia regular de objeto Java. La siguiente línea utiliza el administrador de la entidad para que persista la entidad. Llamar al método persist() es todo lo que se requiere para iniciar lo que se persiste en la base de datos. Si el administrador de la entidad se encuentra con un problema al hacer esto, entonces elevará una PersistenceException sin marcar. Cuando el método persiste() devuelve un valor de retorno, emp tendrá que convertirse en una entidad gestionada dentro del contexto de la persistencia del EntityManager

public Employee createEmployee(int id, String name, long salary) {
  Employee emp = new Employee(id);
  emp.setName(name);
  emp.setSalary(salary);
  em.persist(emp);
  return emp;
}

Buscando una entidad

Una vez que una entidad está en la base de datos, lo siguiente que uno normalmente quiere hacer es encontrarla de nuevo. En realidad sólo hay una línea que tenemos que mostrar, llamando al método find

Employee emp = em.find(Employee.class, 158);

Estamos pasando como parámetros la clase de la entidad que se busca (en este ejemplo, estamos buscando una instancia de los empleados) y la clave de identificación o primaria que identifica a la entidad en particular (en nuestro caso se quiere encontrar la entidad que acabamos de crear en el punto anterior). Ésta es toda la información necesaria para que el EntityManager pueda encontrar la instancia en la base de datos, y cuando la llamada termina, el empleado que se devuelve sea gestionado como una entidad, lo que significa que existirá en el contexto de persistencia actual asociado con el entidad gerente. ¿Qué sucede si el objeto se ha eliminado o si se suministra el identificador de mal por accidente? En el caso de que el objeto no se encuentre, entonces el método find() simplemente devuelve null.

Eliminando la entidad

La eliminación de una entidad de la base de datos no es tan común como usted podría pensar. Muchas aplicaciones nunca eliminan objetos, o si lo hacen sólo los datos que ya no son válidos . Para eliminar la entidad, la buscamos e invocamos un comando para eliminarla. Esto normalmente genera un problema porque la aplicación que acusó la llamada esta siendo manejada como parte del proceso para determinar que ese era el objeto que quería eliminarse . El ejemplo sería el siguiente:

Employee emp = em.find(Employee.class, 158);
em.remove(emp);

Transacciones

La situación típica cuando se ejecuta en el entorno del contenedor Java EE es que se utiliza el estándar Java Transaction API (JTA) para el manejo de transacciones. El modelo de transacción, cuando se ejecuta en el contenedor, asume que la aplicación asegura que un contexto transaccional estará presente cuando éste sea necesario. Si una transacción no está presente, entonces o bien la operación de modificación lanza una excepción o el cambio nunca será persistido en la base de datos.

em.getTransaction().begin();
createEmployee(158, "John Doe", 45000);
em.getTransaction().commit();

Java Persistence Query Language

Definiendo Consultas

JPA posee las interfaces Query y TypedQuery para configurar y ejecutar consultas. La interfaz Query se utiliza para los casos en los que los resultados se esperan que sean de tipo Objeto. La interfaz TypeQuery extiende a Query, por lo que una consulta con tipo puede ser tratada como si fuera una consulta sin tipo, pero no al revés. Se obtiene una implementación de la interfaz apropiada para una consulta dada a través de uno de los métodos factoría de la interfaz del EntityManager. La elección de un método depende del tipo de la consulta.

Consultas Dinámicas

Una consulta se define dinámicamente pasando la cadena de la consulta JPQL y el tipo de resultado esperado al método createQuery() de la interfaz EntityManager. El tipo de resultado puede ser omitido para obtener consultas sin tipo. No existen restricciones a la hora de definir consultas. La habilidad de construir una cadena en tiempo de ejecución y utilizarla para una definición de la consulta es útil, especialmente para aplicaciones donde el usuario puede especificar criterios complejos y la forma exacta de la consulta no puede ser conocida con anticipación. Como se señaló anteriormente, además de consultas de cadena dinámica, JPA también es compatible con una API de criterios para crear consultas dinámicas con objetos Java.

Una cuestión a considerar con cadenas de consultas dinámicas, sin embargo, es el costo de la traducción de JPQL en SQL para su ejecución. Un motor de consulta típica tendrá que analizar la cadena de JPQL en un árbol de sintaxis, obtener los metadatos de mapeo objeto-relacional para cada entidad en cada expresión, y luego generar el SQL equivalente. Para las aplicaciones de muchas consultas, el costo de procesamiento de la consulta dinámica puede ser un problema importante de rendimiento.

@Stateless
public class QueryServiceBean implements QueryService {
   @PersistenceContext(unitName="DynamicQueries")
   EntityManager em;

   public long queryEmpSalary(String deptName, String empName) {
      String query = "SELECT e.salary " +
      "FROM Employee e " +
      "WHERE e.department.name = '" + deptName +
      "' AND " +
      " e.name = '" + empName + "'";
      return em.createQuery(query, Long.class).getSingleResult();
   }
}
Consultas con Nombre

Las consultas con nombre son una poderosa herramienta para la organización de las definiciones de consulta y la mejora de rendimiento de la aplicación. Una consulta con nombre se define mediante la anotación @NamedQuery, que puede ser puesta en la definición de clase para cualquier entidad. La anotación define el nombre de la consulta, así como el texto de la consulta.

@NamedQuery(name="findSalaryForNameAndDepartment",
            query="SELECT e.salary " +
                  "FROM Employee e " +
                  "WHERE e.department.name = :deptName AND " +
                  " e.name = :empName")

El nombre de la consulta es del ámbito de toda la unidad de persistencia por lo que debe ser único dentro de ese ámbito de aplicación. Ésta es una restricción importante a tener en cuenta, como comúnmente se utilizan nombres de consulta como "FindAll" tendrá que ser calificado para cada entidad. Una práctica común es el prefijo en el nombre de la consulta con el nombre de la entidad. Por ejemplo, el "findAll" de consulta para los empleados de la entidad se denominará "Employee.findAll".

Debido a que la cadena de consulta se define en la anotación, no pueden ser alterados por la aplicación en tiempo de ejecución. Esto contribuye al rendimiento de la aplicación y ayuda a prevenir el tipo de problemas de seguridad que discutimos. Debido a la naturaleza estática de la cadena de consulta, cualquier criterio adicional que se requiere para la consulta debe ser especificado utilizando parámetros de consulta.

Tipos de Parámetros

JPA es compatible tanto con nombre y parámetros de posición de las consultas JPQL. La métodos de la factoría de consultas del EntityManager devuelven una implementación de la interfaz de consultas (Query interface). Los valores de los parámetros se establecen con el método setParameter() de la interfaz de consultas. Hay tres variantes de este método para ambos, parámetros con nombre y los parámetros de posición.

  • El primer argumento es siempre el nombre de parámetro o un número.
  • El segundo argumento es el objeto que se ha vinculado al parámetro con el nombre.
  • Date y Calendar también requieren un tercer argumento de que especifica si el tipo pasado a JDBC , si es una java.sql.Date, java.sql.Time o java.sql.TimeStamp .
@NamedQuery(name="findEmployeesAboveSal",
            query="SELECT e " +
                  "FROM Employee e " +
                  "WHERE e.department = :dept AND " +
                  " e.salary > :sal")

Esta consulta destaca una de las características interesantes de JPQL en que los tipos de entidad pueden ser utilizados como parámetros. Cuando la consulta se traduce a SQL, necesita las columnas de la clave principal que se insertarán en la expresión condicional y se vincularán con los valores de clave principal a partir del parámetro.

Otras Características Principales

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.

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 teléfono, tendrá que tener los siguientes métodos:

Set<PhoneNumber> getPhoneNumbers() {} 
void setPhoneNumbers(Set<PhoneNumber>) {}

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

A continuación se muestra un ejemplo de una clase con clave primaria que cumple los requerimientos exigidos:

public final class LineItemKey implements Serializable {
    public Integer orderId;
    public int itemId;

    public LineItemKey() {}

    public LineItemKey(Integer orderId, int itemId) {
        this.orderId = orderId;
        this.itemId = itemId;
    }

    public boolean equals(Object otherOb) {
        if (this == otherOb) {
            return true;
        }
        if (!(otherOb instanceof LineItemKey)) {
            return false;
        }
        LineItemKey other = (LineItemKey) otherOb;
        return (
                    (orderId==null?other.orderId==null:orderId.equals
                    (other.orderId)
                    )
                    &&
                    (itemId == other.itemId)
                );
    }

    public int hashCode() {
        return (
                    (orderId==null?0:orderId.hashCode())
                    ^
                    ((int) itemId)
                );
    }

    public String toString() {
        return "" + orderId + "-" + itemId;
    }
}

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<Order> getOrders() { return orders; }

Tipos de memoria caché

JPA dispone de dos tipos de memoria caché llamadas caché de nivel 1 y caché de nivel 2. El mecanismo de ambas cachés es el siguiente:

    La caché de nivel 1 está siempre disponible y es la zona de memoria que utiliza JPA para guardar los datos que vamos recuperando de base de datos y vamos modificando. Cuando queremos persistir en base de datos (haciendo un commit()), JPA realizará las operaciones oportunas en base de datos para que los datos de este nivel de caché acaben en base de datos. La única forma de vaciar esta caché es con un clear() o si se consume el ciclo de vida del contexto de JPA.

    La caché de nivel 2 se habilita en la configuración, y es la memoria que JPA pone a disposición de la aplicación para guardar objetos de uso frecuente y conseguir una mejora en el rendimiento. Los objetos que se pueden almacenar en caché son Entidades (Datos) y Querys (Consultas). Cada implementación de JPA tiene una forma de configurar este tipo de caché, pero lo que tienen más o menos en común todas las implementaciones es indicar el tamaño de la caché, el tiempo de vida en caché de las entidades (de los datos almacenados) y si la caché esta en un entorno distribuido o en un sola máquina (que siempre va a ser una sola máquina).

Un fallo muy común es pensar que, al activar esta caché, es JPA la que memoriza las Entidades y las Querys más usadas pero NO ES ASÍ. Cada implementación de JPA tiene sus propios objetos y mecanismos para que con el código de la aplicación que se ha programado se guarde en esta caché lo que se quiera.

Trabajar con la JPA

JPA utiliza muchas interfaces y tipos de anotaciones definidas en el paquete de javax.persistence disponible con la versión 5 de Java EE. JPA utiliza las clases de entidad que se asignan a las tablas de la base de datos. Estas clases de entidad se definen en las anotaciones de la JPA. A continuación se muestra la clase de entidad con nombre Employee que corresponde a la tabla Employee de la base de datos del ejemplo

@Entity
@Table(name = "employee")
@NamedQueries({@NamedQuery(name = "Employee.findByEmpId", query = "SELECT e FROM Employee e WHERE e.empId = :empId"), @NamedQuery(name = "Employee.findByEmpFirstname", query = "SELECT e FROM Employee e WHERE e.empFirstname = :empFirstname"), @NamedQuery(name = "Employee.findByEmpLastname", query = "SELECT e FROM Employee e WHERE e.empLastname = :empLastname")})
public class Employee implements Serializable {
        @Id
      @Column(name = "emp_id", nullable = false)
  private Integer empId;
  @Column(name = "emp_firstname", nullable = false)
  private String empFirstname;
  @Column(name = "emp_lastname", nullable = false)
  private String empLastname;

public Employee() {    }
public Employee(Integer empId) {
        this.empId = empId;
}
public Employee(Integer empId, String empFirstname, String empLastname) {
   this.empId = empId;
        this.empFirstname = empFirstname;
        this.empLastname = empLastname;
}
public Integer getEmpId() {
        return empId;
      }
      public void setEmpId(Integer empId) {
        this.empId = empId;
      }
      public String getEmpFirstname() {
        return empFirstname;
}
      public void setEmpFirstname(String empFirstname) {
        this.empFirstname = empFirstname;
}
      public String getEmpLastname() {
        return empLastname;
}
public void setEmpLastname(String empLastname) {
        this.empLastname = empLastname;
}
   /****
*override equals, hashcode and toString methods
*using @Override annotation
******/

Las características de una clase de entidad son los siguientes:

  • La clase de entidad está anotada con la anotación javax.persistence.Entity @Entity.
  • Se debe tener un constructor público o sin argumentos y protegido, también puede contener otros constructores.
  • No puede ser declarada final.
  • Las clases de entidad pueden extender de otras clases entidad y las clases de no entidad. También es posible la situación inversa.
  • No pueden tener variables de instancia public. Los miembros de la clase deben ser expuestos usando los métodos getter y setter, siguiendo el estilo JavaBean.
  • No es necesario aplicar interfaces especiales a las clases de entidad, POJOs. Sin embargo, si van a ser pasados como argumentos en la red, entonces se debe implementar la interfaz Serializable.

La anotación javax.persistence.Table especifica el nombre de la tabla a la que se asigna esta instancia de la entidad. Los miembros de la clase pueden ser tipos primitivos de Java, las envolturas de los primitivos de Java, tipos enumerados, o incluso otras clases. La asignación a cada columna de la tabla se especifica mediante la anotación javax.persistence.Column. Esta asignación se puede utilizar con campos persistentes, en cuyo caso la entidad utiliza los campos persistentes, así, o con métodos de getter/setter, en cuyo caso la entidad utiliza las propiedades persistentes. Sin embargo, se debe seguir  la misma convenciónpara una clase de entidad en particular. Los campos que están anotados usando javax.persistence.Transient o marcados mediante la anotación transient, no se conservarán en la base de datos.

Cada entidad tiene un identificador de objeto único. Este identificador se utiliza para diferenciar entre los diferentes casos de la entidad en el dominio de aplicación, que corresponde a una clave principal que se define en el cuadro correspondiente. Una clave principal puede ser simple o compuesta. Una clave principal se denota simple utilizando la anotación javax.persistence. Las claves primarias compuestas pueden ser una propiedad persistente individual o un campo persistente individual de un conjunto de campos o propiedades, que deben ser definidos en una clase de clave principal. Las claves primarias compuestas se indican mediante las anotaciones javax.persistence.EmbeddedId y javax.persistence.IdClass. Cualquier clase de clave primaria debe aplicar los métodos hashcode() y equals().

El ciclo de vida de las entidades de JPA es administrado por el "entity manager", que es una instancia de javax.persistence.EntityManager. Cada "entity manager" se asocia con un contexto de persistencia. Este contexto puede ser reproducido en todos los componentes de la aplicación o gestionados por la aplicación. El EntityManager pueden ser creado en la aplicación utilizando un EntityManagerFactory como se muestra en el ejemplo

public class Main {
  private EntityManagerFactory emf;
  private EntityManager em;
  private String PERSISTENCE_UNIT_NAME = "EmployeePU";
  public static void main(String[] args) {
      try      {
          Main main = new Main();
          main.initEntityManager();
          main.create();
          System.out.println("Employee successfully added");
          main.closeEntityManager();
      }
      catch(Exception ex)      {
          System.out.println("Error in adding employee");
          ex.printStackTrace();
      }
  }
 private void initEntityManager()  {
 emf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
         em = emf.createEntityManager();
  }
  private void closeEntityManager()   {
      em.close();    emf.close(); }
 private void create() {
      em.getTransaction().begin();
      Employee employee=new Employee(100);
      employee.setEmpFirstname("bob");
      employee.setEmpLastname("smith");
      em.persist(employee);
      em.getTransaction().commit();
    }
}

El PERSISTENCE_UNIT_NAME representa el nombre de la unidad de persistencia que se utiliza para crear la EntityManagerFactory. La EntityManagerFactory también se puede propagar a través de los componentes de aplicación que utiliza la anotación javax.persistence.PersistenceUnit

En el método create(), un nuevo registro de empleado se inserta en la tabla de empleados. Los datos representados por la instancia de la entidad se conservan en la base de datos una vez que la EntityTransaction, asociada con persist(), se ha completado. JPA también define las consultas estáticas y dinámicas para recuperar los datos de la base de datos. Las consultas estáticas son escritas utilizando la anotación javax.persistence.NamedQuery como se muestra en la clase de entidad Employee. Las consultas dinámicas se definen directamente en la aplicación mediante el método createQuery() de la EntityManager.

JPA utiliza una combinación de anotación y XML de configuración. El archivo XML utilizado para este propósito es persistence.xml, que se encuentra en el directorio META-INF de la aplicación. Este archivo define todas las unidades de persistencia que son utilizadas por esta aplicación. Cada unidad de persistencia define todas las clases de entidad que se asignan a una sola base de datos. El archivo persistence.xml para la aplicación del empleado es el siguiente:

<persistence>
 <persistence-unit name="EmployeePU" transaction-type="RESOURCE_LOCAL">   
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
    <class>com.trial.Employee</class>
    <properties>
      <property name="toplink.jdbc.url" value="jdbc:mysql://localhost:3306/projects"/>
      <property name="toplink.jdbc.user" value="root"/>
      <property name="toplink.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="toplink.jdbc.password" value="infosys"/>
    </properties>
  </persistence-unit>
</persistence>

El archivo persistence.xml define una unidad de persistencia llamada EmployeePU. La configuración de la base de datos correspondiente está incluida en la unidad de persistencia. Una aplicación puede tener múltiples unidades de persistencia que se refieren a diferentes bases de datos.

En resumen, JPA es una solución ORM que ofrece un estándar basado en POJO, tanto para Java y Java EE. Utiliza las clases de entidad, los administradores de la entidad, y las unidades de mapa y la persistencia para persistir los objetos de dominio y las tablas de la base de datos.

Cuándo utilizar la JPA

JPA se debe utilizar cuando se necesite una solución para la persistencia basada en un estándar de Java. JPA soporta herencia y polimorfismo, las principales características de la programación orientada a objetos. El lado negativo de la JPA es que requiere un proveedor que lo implemente. Estos proveedores de herramientas específicas también ofrecen otras características que no se definen como parte de la especificación de la JPA. Una de esas características es el soporte para el almacenamiento en caché, que no está claramente definida en la JPA, pero está bien soportado por Hibernate, uno de los marcos más populares que implementan la JPA. Asimismo, la JPA esta definida para trabajar con bases de datos relacionales solamente. Si su solución de persistencia debe ampliarse a otros tipos de bases de datos, como bases de datos XML, entonces, la JPA no es la respuesta a su problema de persistencia.