3 Java Persistence API (JPA)¶
Introducción¶
En nuestro entorno existen muchas aplicaciones que realizan operaciones con la base de almacenar y recuperar grandes cantidades de datos.
Tal y como hemos visto hasta ahora para poder realizar dichas operaciones hemos tenido que utilizar una gran cantidad de código, a pesar de haber mejorado la técnica haciendo uso de patrones.
Java Persistence API es un conjunto de clases y métodos que persistentemente almacena una gran cantidad de datos a una base de datos proporcionada.
Para poder reducir la carga en la gestión de los objetos en los modelos relaciones, se sigue el marco Proveedor JPA que nos permite de forma fácil una interacción con la instancia de la base de datos.
Arquitectura¶
JPA es una fuente para almacenar entidades relaciones y muestra cómo definir una clase POJO como una entidad y la forma de gestionar las relaciones con las entidades.
Unidad | Descripción |
---|---|
EntityManagerFactory | Esta es una clase de fábrica de EntityManager. Crea y gestiona múltiples instancias EntityManager. |
EntityManager | Es una interfaz, que gestiona la persistencia de objetos. Funciona como instancia de consulta. |
Entidad | Las entidades son los objetos de persistencia, tiendas como registros en la base de datos. |
EntityTransaction | Tiene una relación de uno a uno con EntityManager. Para cada método EntityManager, se mantienen las operaciones de la clase EntityTransaction. |
Persistence | Esta clase contiene métodos estáticos para obtener EntityManagerFactory. |
Query | Esta interfaz es implementada por cada proveedor JPA relacional para obtener objetos que cumplan los criterios. |
En la arquitectura, las relaciones entre las clases e interfaces pertenecen a la clase javax.persistence
paquete. El siguiente diagrama muestra la relación entre ellos.
- La relación entre EntityManagerFactory y EntityManager es de uno a varios. Se trata de una clase de fábrica a instancias EntityManager.
- La relación entre EntityManager y EntityTransaction es uno a uno. EntityManager para cada operación, hay un EntityTransaction.
- La relación entre EntityManager y Query es de uno a varios. Un número de consultas puede ser ejecutado mediante una instancia EntityManager.
- La relación entre Entidad y EntityManager es uno de muchos. Un EntityManager puede administrar varias entidades.
Object Relational Mapping (ORM)¶
Object Relational Mapping es una capacidad de programación para convertir un objeto relacional devuelto de una consulta a un objeto entidad. Su principal característica es enlazar un objeto a sus respectivos similares en la base de datos, así como sus relaciones con las entidades en cualquier otra tabla.
El uso de ORM tiene una serie de ventajas:
- Persistencias idiomáticas: le permite escribir persistencia utilizando las clases orientadas a objetos.
- Alto rendimiento: tiene un alto rendimiento a la ahora de realizar técnicas de bloqueo.
- Fiable: es muy estable y usado.
La arquitectura ORM es similar a la siguiente
Fase 1¶
La primera fase, denominada fases de datos del objeto, contiene las clases POJO, interfaces y el resto de clases. Es el principal componente de la capa empresarial que contiene operaciones y atributos de lógica de negocios. Por ejemplo, podemos tener la clase POJO, Employee con la información referente a almacenar de los empleados, así como su interfaz DAO que contendrá los métodos necesarios para trabajar con la base de datos.
Es importante que las clases POJOs sigan el principio Bean para poder trabajar con ellas a través del proveedor.
Fase 2¶
La fase 2, o fase de mapeo o persistencia, contiene el proveedor JPA, así como el archivo de configuración, el JPA loader, y el object Grid.
- JPA Provider: es el proveedor que contiene el JPA.
- Archivo de asignación: es el archivo que contiene la configuración de la asignación entre los datos de la clase POJO y los datos de una base de datos relacional.
- JPA Loader: Carga los datos de la base de datos en una especie de memoria caché, para así interactuar con las clases servicios de datos.
- Object Grid: es una ubicación temporal que puede almacenar una copia de los datos relacionales, como una memoria caché. Todas las consultas en la base de datos se efectuarán, primero en los datos del objeto grid. Sólo después de que se ha comprometido, afectará a la base de datos principal.
Fase 3¶
La tercera fase es la fase de datos relacionales, que contiene los datos relacionales que están conectados al componente empresarial. Como se ha indicado anteriormente, sólo cuando el componente empresarial se compromete con los datos, que se almacenan en la base de datos básicamente. Hasta entonces, los datos modificados se almacenan en una memoria caché como un formato de cuadrícula. El proceso de obtención de los datos es idéntico a la de almacenar los datos.
El mecanismo de la interacción mediante programación por encima de tres fases se denomina asignación objeto-relacional .
Mapeo.xml¶
La asignación del archivo.xml consiste en indicar al JPA Provider la relación entre las clases de entidad con las tablas de la base de datos relacional.
Para la clase Employee y la tabla empleado tenemos la siguiente asignación:
<? xml version="1.0" encoding="UTF-8" ?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm
http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
version="1.0">
<description> XML Mapping file</description>
<entity class="Employee">
<table name="empleado"/>
<attributes>
<id name="id">
<generated-value strategy="TABLE"/>
</id>
<basic name="name">
<column name="nombre" length="100"/>
</basic>
<basic name="salary">
</basic>
<basic name="deg">
</basic>
</attributes>
</entity>
</entity-mappings>
Del archivo anterior podemos destacar:
<entity-mappings>
: esta etiqueta define el esquema ORM que permitirá reconocer a las etiquetas siguientes.<description>
: ofrece una descripción acerca de la aplicación.<entity>
: define la clase entidad que se desea relacionar con la tabla de la base de datos. El atributoclass
define el nombre del la clase POJO. También se puede definir el atributoaccess
que indica el modo de acceso. El valorFIELD
accede directamente a la propiedad mientras que el valorPROPERTY
hace uso de los métodos getters and setters.<table>
: define el nombre de la tabla indicándola con el atributoname
. En caso de que el nombre de la clase POJO y el nombre de la tabla sean el mismo, no sería necesario indicar esta etiqueta.<attributes>
: define los atributos/campos de una clase/tabla.<id>
: define la clave principal de la tabla. Con el atributoname
se indica el nombre del atributo de la clase con la que se relaciona.-
<generated-value>
: define cómo se le asignará el valor a la clave principal. Siempre va en el interior de la etiquetaid
. Sus posibles valores son- IDENTITY: El valor se genera automáticamente por la base de datos. Este estrategia se usa para bases de datos que permitan la autoincrementación como MySQL.
- SEQUENCE: El valor se genera secuencialmente.Esta estrategia se usa en base de datos donde permita la asignación de valores secuenciales como en Oracle.
- TABLE: JPA crea una tabla adicional en la base de datos para generar valores de clave primaria. Es menos eficiente que IDENTITY o SEQUENCE.
- AUTO: Permite que el proveedor de persistencia elija la forma más adecuada según la base de datos. En la mayoría de los casos, esto se traducirá en IDENTITY o SEQUENCE, si son compatibles.
- NONE: Esta opción significa que no se generará ningún valor automáticamente para la clave primaria. Debes proporcionar el valor de la clave primaria manualmente en tu código.
Cada estrategia tiene sus propias ventajas y limitaciones, y la elección de la estrategia adecuada depende de la base de datos subyacente y los requisitos específicos de tu aplicación. Por ejemplo, si trabajas con una base de datos que admite autoincremento, IDENTITY es una elección común. Si estás en una base de datos que utiliza secuencias, entonces SEQUENCE es apropiado. La estrategia AUTO es una opción conveniente si deseas que el proveedor de JPA elija la estrategia más adecuada en función de la base de datos subyacente. Por otro lado, TABLE puede ser útil en situaciones donde ninguna de las otras estrategias es aplicable. La estrategia NONE se utiliza cuando deseas controlar completamente la generación de valores por ti mismo. -
<basic>
: se utiliza para indicar los atributos de la clase. -<column-name>
: se utiliza para indicar a que columna de la tabla hace referencia el atributo de la clase. Siempre van dentro de la etiquetabasic
. En caso de que el atributo de la clase y el campo de la tabla sea el mismo, no es necesario indicarlo. -<many-to-many>
: se utiliza para indicar un atributo foráneo que tiene relación con otra tabla. -<join-table>
: se utiliza para indicar con que tabla esta relacionada. -<join-column>
: se utiliza para indicar el nombre de la columna a la que se una.
Anotaciones¶
Por lo general, se utilizan los archivos XML para configurar los componentes específicos. Esto a veces supone un poco trillado, porque debemos conocer los atributos de la clase con los de la base de datos. Gracias a las anotaciones, podemos indicar dicha configuración en las clases y atributos en Java. Todas las anotaciones están en el paquete javax.persistence
.
@Entity
: Esta anotación se utiliza para marcar una clase como una entidad que se puede mapear a una tabla en la base de datos. Las instancias de esta clase se pueden administrar mediante JPA.@Table
: Se usa para especificar detalles específicos de la tabla de la base de datos que corresponde a una entidad. Puedes definir el nombre de la tabla, el esquema y otras propiedades relacionadas con la tabla.@Id
: Indica que un atributo es la clave primaria de la entidad. Este atributo se asocia con la columna de clave primaria en la tabla de la base de datos.@GeneratedValue
: Se utiliza junto con @Id para especificar la estrategia de generación de valores para la clave primaria. Puedes utilizar valores como IDENTITY, SEQUENCE, TABLE, AUTO, etc.@Column
: Se usa para personalizar la asignación de atributos de entidad a columnas de la tabla de la base de datos. Puedes especificar el nombre de la columna, su tipo de datos, su longitud, y más.@ManyToOne
y@OneToMany
: Estas anotaciones se utilizan para mapear relaciones entre entidades en una asociación muchos a uno o uno a muchos. Por ejemplo, si tienes una entidad Author y una entidad Book, puedes utilizar estas anotaciones para definir la relación entre autores y libros.@JoinColumn
: Esta anotación se utiliza junto con @ManyToOne y @OneToMany para personalizar la columna que se utiliza para realizar la asociación entre entidades.@OneToOne
y@ManyToMany
: Estas anotaciones se utilizan para definir relaciones de uno a uno y muchos a muchos entre entidades.@NamedQuery
y@NamedQueries
: Estas anotaciones se utilizan para definir consultas JPQL que se pueden reutilizar en varias partes de la aplicación.@NamedNativeQuery
y@NamedNativeQueries
: Similar a las anotaciones @NamedQuery, pero se utilizan para definir consultas SQL nativas en lugar de consultas JPQL.@Version
: Se usa para marcar un atributo que se utiliza para el control de versiones de una entidad. Ayuda a evitar problemas de concurrencia al realizar actualizaciones en registros.@Temporal
: Se utiliza para especificar el tipo de fecha y hora que se almacena en una columna, como DATE, TIME, o TIMESTAMP.@Lob
: Esta anotación se usa para marcar un atributo que almacena datos grandes (como objetos binarios largos, como imágenes o documentos) en la base de datos.@PrePersist
y@PreUpdate
: Estas anotaciones se utilizan para marcar métodos que se ejecutarán antes de la persistencia y antes de una actualización de una entidad, respectivamente.@PostLoad
,@PostPersist
,@PostUpdate
, y@PostRemove
: Estas anotaciones se utilizan para marcar métodos que se ejecutarán después de cargar una entidad, después de la persistencia, después de una actualización y después de eliminar una entidad, respectivamente.
@Entity
@Table(name = "empleados")
public class Employee{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name="nombre")
private String name;
@ManyToOne
@JoinColumn(name="department_id")
private Department department;
}
@Entity
public Department{
@OneToMany(mappedBy = "department")
private List<Employee> employee;
//...
}
Persistence.xml¶
El archivo persistence.xml
es un archivo de configuración esencial en el contexto de JPA. Se utiliza para definir la configuración de la unidad de persistencia de una aplicación Java que emplea JPA. Este archivo cumple varias funciones clave:
- Definición de la Unidad de Persistencia: El archivo persistence.xml define una o varias unidades de persistencia que representan las conexiones a bases de datos y las configuraciones de mapeo objeto-relacional (ORM) en la aplicación. Cada unidad de persistencia se identifica por un nombre único y agrupa configuraciones relacionadas.
- **Configuración del Proveedor de JP**A: En el archivo persistence.xml, puedes especificar el proveedor de JPA que deseas utilizar en tu aplicación. Los proveedores comunes incluyen Hibernate, EclipseLink y otros. La elección del proveedor depende de tus necesidades y preferencias.
- Configuración de la Fuente de Datos: Puedes definir la fuente de datos (generalmente una conexión a la base de datos) que la unidad de persistencia utilizará para interactuar con la base de datos. Esto incluye información como la URL de conexión, el nombre de usuario y la contraseña.
- Definición de Clases de Entidad: El archivo persistence.xml enumera todas las clases de entidad que se administrarán en la unidad de persistencia. Las clases de entidad son objetos Java que se mapean a tablas de bases de datos y se utilizan para realizar operaciones de persistencia.
- Configuración Adicional: Puedes proporcionar propiedades de configuración adicionales, como dialectos de SQL, estrategias de creación de tablas, configuración de caché, y más, según las necesidades de tu aplicación.
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
version="2.2">
<persistence-unit name="MyPersistenceUnit">
<!-- Configuración del proveedor de JPA -->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- Configuración de la fuente de datos -->
<jta-data-source>java:jboss/datasources/MyDataSource</jta-data-source>
<!-- Lista de clases de entidad que se gestionarán -->
<class>com.example.Product</class>
<class>com.example.Customer</class>
<!-- Configuración adicional de JPA -->
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
<property name="hibernate.hbm2ddl.auto" value="update" />
</properties>
<mapping-file>path/to/orm.xml</mapping-file>
</persistence-unit>
</persistence>
Destacamos las siguientes etiquetas:
<persistence-unit>
: define la unidad de persistencia, indicando su nombre con el atributo name.<class>
: define la clase de entidad con nombre del paquete. Puede haber tantas como clases vamos a trabajar.<provider>
: Proveedor de persistencia.<properties>
: define las propiedades del provider y cada propiedad dentro de la etiqueta<property>
.<mapping-file>
: define la ruta donde se encuentra el fichero de mapeo, en caso de que se tenga alguno.
Este fichero se debe almacenar en el directorio META_INF
localizado en el directorio principal del proyecto.
Operaciones de persistencia¶
Para realizar operaciones de persistencia será necesario crear un EntityManager
(para ello será necesario tener un EntityManagerFactory
). El método Persistence.createEntityManagerFactory("MyPersistence")
creamos un objeto EntityManagerFactory
e invocamos el método createEntityManager
para crear un EntityManager
.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("MyPersistenceUnit");
EntityManager em = emf.createEntityManager();
Para realizar cada tipo de operación será necesario obtener la transacción, comenzar y ejecutarla a través de los siguientes métodos:
Transaction getTransaction()
: obtiene el objeto transacción de laEntityManager
.begin()
: inicia la transacción.persist()
: crea un objeto en la persistencia.commit()
: actualiza los datos en la base de datos original.rollback()
: deshace la transacción en caso de error.find()
: busca un elemento de la Entidad dado un id.remove()
: elimina un elemento.
Una vez realiza las operaciones es necesario cerrar la EntityManagerFactory
y la EntityManager
.
// Crear una EntityManagerFactory basada en persistence.xml
EntityManagerFactory emf = Persistence.createEntityManagerFactory("MyPersistenceUnit");
// Crear un EntityManager a partir de EntityManagerFactory
EntityManager em = emf.createEntityManager();
// Operación de creación (Persistencia)
Employee employee = new Employee(123, "John Doe", 50000, "Developer");
em.getTransaction().begin();
em.persist(employee);
em.getTransaction().commit();
// Operación de lectura (Búsqueda)
em.getTransaction().begin();
Employee retrievedEmployee = em.find(Employee.class, 123);
em.getTransaction().commit();
System.out.println("Empleado recuperado: " + retrievedEmployee);
// Operación de actualización
em.getTransaction().begin();
retrievedEmployee.setSalary(55000);
em.getTransaction().commit();
// Operación de eliminación
em.getTransaction().begin();
em.remove(retrievedEmployee);
em.getTransaction().commit();
// Cerrar EntityManager y EntityManagerFactory
em.close();
emf.close();