Persistencia con JPA

JPA es un API de persistencia desarrollada para la plataforma Java EE. JPA se basa en el patrón ORM para no perder las ventajas de la programación orientada a objetos, al interactuar con bases de datos relacionales.

Existen muchas implementaciones de JPA, entre las cuales están EclipseLink, TopLink, Hibernate, Amber, etc. En esta aplicación se usa EclipseLink.

Configuración

Para configurar el uso de EclipseLink en el proyecto JavaEE, es necesario incluir las dependencias en el POM del proyecto. Esto se realiza añadiendo lo siguiente al archivo pom.xml:

<dependency>
    <groupId>org.eclipse.persistence</groupId>
    <artifactId>eclipselink</artifactId>
    <version>2.6.0</version>
    <scope>provided</scope> <!-- Provista por GlassFish -->
</dependency>

Adicionalmente, es necesario configurar el acceso a la fuente de datos a la cual debemos acceder. Para esto necesitamos crear un archivo llamado persistence.xml en la ruta src/main/resources/META-INF. Este archivo nos indica a qué base de datos debemos conectarnos y nos permite definir diferentes comportamientos, como la creación de tablas o el nivel de detalle del logging.

Conexión directa

Una de las opciones para conectarse a la base de datos, es dar a JPA acceso directo a la misma. Esto se logra configurando el archivo persistence.xml de la siguiente manera:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="MarketPlacePU" transaction-type="JTA"> 
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <exclude-unlisted-classes>false</exclude-unlisted-classes>
        <shared-cache-mode>NONE</shared-cache-mode>
        <properties>
            <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
            <property name="eclipselink.target-database" value="Derby"/>            
            <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:derby://localhost:1527/jdbcMarketPlaceTest"/>
            <property name="javax.persistence.jdbc.user" value="APP"/>
            <property name="javax.persistence.jdbc.password" value="APP"/>
            <property name="eclipselink.logging.level" value="FINE"/>     
            <property name="eclipselink.logging.parameters" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

Esta configuración es usada, por ejemplo, en el archivo de persistencia para la ejecución de pruebas, dado que dicho ambiente debe ser independiente del ambiente de despliegue.

Conexión por JDBC Resource

Para la conexión a la base de datos durante el despliegue, se usará una conexión a un JDBC Resource. Estos Data Source son recursos de GlassFish que permiten mantener activo un pool de conexiones a una fuente de datos. De esta manera, cuando la aplicación necesita acceder a ella, el contenedor GlassFish le provee una de las conexiones ya creadas, mejorando el rendimiento de la aplicación.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="MarketPlacePU" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>java:app/jdbc/MarketPlace</jta-data-source>
    <properties>
      <property name="eclipselink.logging.level" value="FINE"/>
      <property name="eclipselink.ddl-generation" value="create-or-extend-tables"/>
      <property name="eclipselink.cache.type.default" value="NONE" />
    </properties>
  </persistence-unit>
</persistence>

En el archivo persistence.xml encontramos la siguiente información:

  • Persistence Unit: Unidad de persistencia con datos relacionados a la conexión a la fuente de datos.
    • Name: Nombre de la unidad de persistencia. En este caso se nombró SportClassPU
    • Transaction-Type: Cómo se manejará las transacciones. Existe dos opciones, RESOURCE_LOCAL cuando se quiere controlar manualmente las transacciones, y JTA cuando se desea que el contenedor de aplicaciones realice estas acciones automáticamente de acuerdo a su implementación de JTA. Para más información consultar este enlace
    • JTA Data Source: Referencia al JNDI Name del recurso definido en el contenedor para mantener un pool de conexiones activo.
    • Logging Level Property: Nivel de logging a realizar para las transacciones.
    • DDL Generation Property: Comportamiento para la definición de los datos. En este ejemplo se establece que EclipseLink deberá crear las tablas no existentes y añadir las columnas faltantes a las tablas ya existentes.

Configuración de JDBC Resource

Para conectarse a una base de datos a partir de un JDBC Resource, es necesario crear dicho recurso. Esto puede hacerse de dos maneras:

  1. En la consola de administración de glassfish dirigirse al menú de JDBC Resource, y crear el mismo. Los recursos creados con este método son accesibles por cualquier aplicación en el contenedor.
  2. Crear un archivo glassfish-resources.xml en la carpeta WEB-INF del proyecto web con la definición del recurso. Los recursos creados con este método son accesibles sólo por la aplicación que contiene el archivo.
  3. En el caso del Marketplace de libros, se usa un archivo glassfish-resources.xml con la siguiente configuración:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
    <jdbc-connection-pool 
        name="MarketPlace_pool"
        datasource-classname="org.postgresql.ds.PGConnectionPoolDataSource"
        res-type="javax.sql.ConnectionPoolDataSource">
        <property name="serverName" value="157.253.238.75"/>
        <property name="portNumber" value="5432"/>
        <property name="databaseName" value="mp-books"/>
        <property name="User" value="marketplace"/>
        <property name="Password" value="Gestion2015"/>
    </jdbc-connection-pool>
    <jdbc-resource enabled="true" jndi-name="java:app/jdbc/MarketPlace" object-type="user" pool-name="MarketPlace_pool"/>
</resources>

Entidades

Las entidades son el mapeo del ORM usado en la aplicación, es decir, JPA. Permite, a partir de objetos, definir cómo será el esquema de la base de datos. En las entidades usan las siguientes anotaciones:

  • @Entity: Define la entidad como un objeto que debe ser mapeado a la base de datos.
  • @Id: Identifica un atributo como la llave primaria de la entidad.
  • @GeneratedValue: Define que el campo es autogenerado y la estrategia para realizar dicha generación.
  • @ManyToOne: Establece una relación de muchos a uno con respecto a otra entidad.
  • @OneToMany: Establece una relación de uno a muchos con respecto a otra entidad.
  • @OneToOne: Establece una relación uno a uno entre dos entidades.

Bean Genérico de JPA

En este proyecto se encuentra la clase CrudPersistence. Esta clase contiene métodos genéricos que permiten realizar operaciones CRUD en la base de datos para cualquier entidad. De esta manera, se puede retutilizar la misma lógica en diferentes partes, mejorando la facilidad de modificar el código.

Las funciones implementadas en esta clase son:

  • count: Función para obtener la cantidad de registros de una entidad
  • create: Función para crear un registro en la base de datos.
  • update: Función para actualizar un registro de la base de datos.
  • delete: Función para eliminar un registro de la base de datos.
  • find: Función para encontrar un registro a partir de su ID
  • findAll: Función para listar todos los registros de una entidad. Acepta variables de paginación.
  • findByName: Función para encontrar registros que contienen una cadena en el nombre
  • executeSingleNamedQuery: Función para ejecutar NamedQueries que retornan un único registro.
  • executeListNamedQuery: Función para ejecutar NamedQueries que retornan una colección de registros.

Beans de persistencia

Los Bean de persistencia heredan de CrudPersistence para reutilizar todas sus funcionalidades, y permitir la extensión de la misma gracias a las posibilidades que ofrece la herencia en programación orientada a objetos.

Para que las funciones heredadas de CrudPersistence funcionen, es necesario definir en el constructor la entidad que gestiona el bean de persistencia:

public class ClientPersistence extends CrudPersistence<ClientEntity> {

    public ClientPersistence() {
        this.entityClass = ClientEntity.class;
    }
    ...
}

En adelante, si se desea extender la funcionalidad de los beans de persistencia, es posible crear tantas funciones como se deseen, accediendo al EntityManager almacenado en la propiedad em o reutilizando aquellas heredadas de CrudPersistence.

public ClientEntity getClientByUserId(String userId){
        try {
            Map<String, Object> params = new HashMap<String, Object>();
            params.put("user_id", userId);
            return this.executeSingleNamedQuery("Client.getByUserId", params);
        } catch (NoResultException e) {
            return null;
        }
    }