Curso Jakarta EE 9 (18). JPA con Hibernate (1): Introducción.

logo Jakarta EE

En este nuevo y extenso bloque del curso empezamos a crear nuestra aplicación de ejemplo para gestionar gastos personales y presupuestos, y lo hacemos desde «abajo», esto es, la capa de persistencia.

>>>> ÍNDICE <<<<

Las bases de datos relacionales son un pilar fundamental de la práctica totalidad de aplicaciones empresariales y de gestión. Esto convierte a la especificación Jakarta Persistence \ JPA (Java Persistence API), creada para facilitar su empleo, en una de las más importantes y utilizadas. Su difusión incluso trasciende el ámbito de Jakarta EE y los servidores de aplicaciones, y la encontraremos en programas Java de todo tipo, incluyendo las desarrolladas con Spring.

Como ya viene siendo habitual, primero veamos un poco de teoría en este capítulo introductorio.

Bases de datos relaciones

Pese a la creciente apuesta por el mundo no SQL, cajón de sastre para productos muy diversos, las bases de datos relacionales siguen dominando el mercado desde su advenimiento décadas atrás y el panorama no parece que vaya a cambiar.

En estas bases de datos, que siendo rigurosos debemos llamarlas sistemas de gestión de bases de datos relacionales (SGDB o, en inglés, RDBMS), la información se organiza siguiendo el denominado «modelo relacional» propuesto por Codd en 1970, y llevado comercialmente a la práctica por Oracle e IBM a finales de la década. No es mi intención examinar este modelo (la bibliografía en español es amplia), pero sí voy a indicar sin entrar en detalles sus fundamentos para poner en contexto al lector. Volveremos sobre ellos en futuros capítulos.

En el centro del modelo relacional se encuentra el intuitivo concepto de tabla o relación. La base de datos puede concebirse como una colección de ellas. Cada una posee una serie de columnas que representan ciertos datos simples (un número, un texto, una fecha…), atributos que en conjunto definen a un concepto, por ejemplo «usuario». Cada elemento individual de ese tipo (un usuario) constituye una fila de la tabla, y se denomina registro o tupla.

Esta captura de pantalla muestra una hipotética tabla usuarios con la forma visual, valga la redundancia, de una tabla.

tabla bd

Las columnas pueden configurarse de manera que sus valores sean únicos y no se repitan en toda la tabla. También es posible establecer si admiten valores vacíos. Es el caso de la columna email del segundo registro en la imagen anterior.

Existen dos columnas especiales.

  • Clave primaria (Primary Key, PK). Permite identificar unívocamente a cada registro. Solo habrá una clave primaria por tabla, siendo además un atributo único y no vacío. Es posible que sea múltiple (compuesta) y esté formada por más de una columna.
  • Clave ajena, foránea o externa (Foreign Key, FK). Su valor relaciona dos registros ubicados en la misma tabla o -más habitual- en tablas distintas. Esa relación se suele establecer con la clave primaria, por lo que si esta es múltiple (varios atributos), la clave ajena también lo será.

En la siguiente imagen vemos una relación entre las tablas usuarios y solicitudes porque hemos decidido que un usuario puede realizar varias. En solicitudes tenemos una clave ajena (la columna usuario_id) que hace referencia a la clave primaria (id) de usuarios. De este modo, sabemos a qué usuario pertenece una solicitud y, por extensión, todas las realizadas por cada uno de ellos.

ejemplo tablas relacionadas

Para trabajar con bases de datos relaciones se utiliza el lenguaje SQL (Structure Query Language o lenguaje estructurado de consultas), el cual está estandarizado aunque cada base de datos añade elementos propios. Es fácil de comprender y sirve tanto para recuperar los datos como para crearlos, modificarlos y definir la estructura de las tablas que los contienen. Tiene la particularidad de ser un lenguaje declarativo: con él expresamos qué información queremos obtener, pero no la forma de conseguirla.

Interacción Java\bases de datos

Con una base de datos relacional resolvemos el almacenamiento de la información de nuestras aplicaciones. Ahora, el objetivo será explotarla con Java, donde además seguimos un modelo muy distinto (orientación a objetos) fundamentado en clases y no en tablas. La solución es recurrir SQL, utilizando como mediador entre nuestro código y la base de datos la API JDBC, un estándar incluido en la JRE que permite interactuar con cualquier base de datos relacional que proporcione una implementación, llamada driver o controlador JDBC. Con ella, ejecutamos sentencias SQL de cualquier tipo y obtenemos sus resultados en los casos en que sea necesario.

De nuevo, apreciamos uno de los beneficios del uso de especificaciones y APIs genéricas: con JDBC nuestro código será portable. Las incompatibilidades solo deberían surgir si usamos tipos, funciones, etc, exclusivos de una base de datos específica.

Echemos un vistazo al siguiente fragmento de código. Obtiene una conexión en un bloque try-with-resources que asegura su cierre, recupera un listado de registros de una tabla con SQL y los convierte en objetos.

public <Country> findAllPureJdbc() throws SQLException {
    String sql = "SELECT * FROM country";
    try (Connection connection = getConnection();
           PreparedStatement ps = connection.prepareStatement(sql);) {
      ResultSet resultSet = ps.executeQuery();
      return mapResults(resultSet);
   } 
}
private List<Country> mapResults(ResultSet resultSet) throws SQLException {
    List<Country> results = new ArrayList<>();
    while (resultSet.next()) {
        Country country = mapResult(resultSet);
        results.add(country);
    }
    return results;
}
private Country mapToCountry(ResultSet resultSet) throws SQLException {
    return new Country(
        resultSet.getLong("id"),
        resultSet.getString("name"),
        resultSet.getInt("population"));
}

Demasiado código para hacer una tarea rutinaria, y ni siquiera hemos tenido que tratar con transacciones o variables en las consultas. Para cada tabla, probablemente necesitemos codificar un conjunto de operaciones denominadas CRUD (crear, obtener, actualizar, eliminar). Esto será bastante código que se repite una y otra vez con pequeñas diferencias. Una cantidad ingente de trabajo, tedioso y aburrido, por mucho que consigamos generalizar y reutilizar parte del código.

Así trabajábamos a principios de siglo y te aseguro que, al menos yo, no es algo que eche de menos. Rara vez tengo que recurrir a usar directamente JDBC.

JPA al rescate

Variadas soluciones fueron surgiendo con el objetivo de simplificar y abstraer estas tareas tan repetitivas, pero necesarias, a la hora de interactuar con bases de datos relacionales. Vamos a centrarnos en las herramientas conocidas con el nombre de ORM (object relational mapping). Groso modo, partiendo de la configuración de la correspondencia entre las clases de nuestro dominio y las tablas, son capaces de realizar la transformación entre ambas. Es decir, funcionan como una especie de «traductor». El resultado, en pocas palabras, es que trabajaremos con clases en vez de tablas y sin interactuar de forma directa con JDBC.

La especificación Jakarta Persistence (de ahora en adelante me referiré a ella como JPA), nace en 2006 con la intención de estandarizar las herramientas ORM que por aquel entonces ya gozaban de cierta implantación y tras el poco éxito en este campo de las especificaciones EJB 2.x y JDO. En concreto, JPA se basó en Hibernate ORM y sus anotaciones, recogiendo un conjunto de ellas suficiente para solucionar los requerimientos más frecuentes.

La siguiente línea temporal muestra la evolución de JPA (fila superior) frente a JEE y Java. Desde 2009, no ha habido grandes novedades y un par de nuevas versiones han pulido el juego de características centrales de la especificación que ya quedaron bien definidas en la entrega 2.0. La 3.0 solo supone el alineamiento de JPA con Jakarta EE 9 que vimos en el primer capítulo.

Usaremos Hibernate como implementación o proveedor de JPA, pues es la proporcionada por WildFly (nótese que Red Hat\JBoss es la propietaria de ambos productos). Aunque, como es habitual, nos centraremos en el estándar, en este caso concreto resulta imprescindible estudiar el funcionamiento de la implementación. Este conocimiento es necesario para usarla de forma eficiente y, además, su API ofrece funcionalidades exclusivas que resultan irresistibles y amplían las posibilidades de JPA. El inconveniente si las usamos es la pérdida de portabilidad.

Otros proveedores son Eclipse Link (implementación de referencia para JPA 2.2 ) y Apache OpenJPA. A continuación se muestra los que incluyen los servidores de aplicaciones más populares.

HibernateWildFly \ JBoss EAP
Apache OpenJPAApache TomEE
Eclipse LinkPayara Server, Eclipse GlassFish, Open Liberty

Con independencia de la implementación elegida, enumero algunas ventajas del uso de JPA.

  • Las operaciones básicas de tipo CRUD ya las tenemos, solo hay que invocar el método adecuado (a veces ni eso, ver punto siguiente). En consecuencia, nos ahorraremos escribir código rutinario y tedioso.
  • Sincronización automática entre objetos y registros.
  • Es independiente de la base de datos utilizada. Hibernate es compatible con las más populares (PostgreSQL, MySQL, Oracle, SQL Server, Informix…).
  • Ofrece un lenguaje declarativo de consultas similar a SQL, llamado JPQL, en el que se usan clases y atributos en lugar de tablas y columnas.
  • Las sentencias JPQL se pueden construir de forma programática y tipada con Criteria API.
  • Facilidad para usar transacciones, cachés o bloqueos (locking).
  • Gracias a su popularidad hay muchos libros, blogs y cursos disponibles. Y, por supuesto, consultas en StackOverflow.

Una maravilla, pero no es oro todo lo que reluce. Ya señalé que debemos conocer muy bien Hibernate para que nos dé un buen rendimiento. De lo contrario, terminaremos haciendo auténticas barbaridades que hundirán el desempeño de la aplicación -¡he visto cada cosa!-. Iré subrayando los errores más comunes y ofreceré consejos y recomendaciones para usar Hibernate de manera «responsable» y productiva.

¿Dónde queda SQL?

Tengo la impresión de que algunos programadores utilizan JPA con un enfoque equivocado. Su objetivo no es reemplazar SQL, enterrándolo bajo una cómoda capa de abstracción, de tal modo que nos olvidemos por completo de su existencia y de la base de datos subyacente. Lo que sí abstrae es la engorrosa API JDBC.

De entrada, se requiere del conocimiento del modelo relacional para definir la correspondencia entre clases y tablas. Asimismo, las consultas que escribamos con JPQL o Criteria API serán traducidas a un SQL que debemos comprender y revisar con el fin de asegurar que no estamos causando problemas de rendimiento.

A veces, las capacidades de JPQL no son suficientes para nuestros propósitos. Tendremos que escribir SQL (en argot JPA, «consultas nativas») para conseguir los información que queremos con eficiencia, aprovechando todo el «poder» y las prestaciones de la base de datos, como, por ejemplo, las funciones analíticas (window functions) o las tablas derivadas. En casos extremos, esto incluye el desarrollo y empleo de procedimientos almacenados para implementar lógica de negocio «pesada» basada en el procesamiento masivo de registros.

Pensando en esta problemática, JPA ofrece un soporte excepcional para trabajar con SQL sin tener que recurrir al uso directo de JDBC. Nos beneficiaremos, entre otros, de sus mecanismos de paginación, transformación de resultados y gestión de transacciones. También podremos invocar con sencillez funciones y procedimientos almacenados. Lo veremos a su debido tiempo.

El precio a pagar es una reducción de la portabilidad de la aplicación, pero en la mayoría de proyectos no tendremos que asumir el coste: será muy raro que tengamos que cambiar de base de datos o ser compatibles con varias al mismo tiempo. Y aunque nos encontremos en esas situaciones, la portabilidad no debería ser una excusa para crear aplicaciones lentas e ineficientes cuando se trata de usar la base de datos.

>>>> ÍNDICE <<<<

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.