Curso Spring Data JPA. 1: introducción

logo spring

¡Bienvenid@ a mi curso! Si no conoces Spring Data, tendrás preguntas del estilo: ¿qué problema soluciona? ¿qué es capaz de hacer? En esta introducción encontrarás las respuestas, así como una primera demostración de sus prodigios.

Nota. Voy a asumir que estás familiarizado con Spring Framework. Puedes aprender más sobre él en varios tutoriales del blog. De todas formas, los conocimientos requeridos por este curso son muy básicos.

>> ÍNDICE COMPLETO <<

Índice

  1. Un mundo de datos
  2. El «problema» con la API JDBC
  3. Hibernate y JPA
  4. Spring Data: la victoria final 🚀
  5. Código de ejemplo

Un mundo de datos

Spring Data, como su nombre sugiere, va de datos 😊.

Buena parte del código de la mayoría del software se dedica a mover datos de aquí para allá. Alguien (o «algo») envía datos a un sistema informático. Se revisan los valores proporcionados y, si son correctos, se guardan. Esa información luego aparece en listados, informes, forman parte de estadísticas…

Los datos circulan por todo el mundo entre computadoras, teléfonos inteligentes y dispositivos IoT. Algunos no valen nada, ni siquiera para sus creadores. Otros son un activo valioso para empresas, estados y organizaciones en la sombra. En cantidades ingentes, alimentan los revolucionarios sistemas de aprendizaje automático tan en boga en la actualidad.

Datos y más datos. Nuestro trabajo gira en torno a su captura, análisis y explotación. De hecho, según la Real Academia Española, la informática consiste en el tratamiento automático de la información con computadoras.

¿Cómo se almacenan? Salvo que hablemos de grandes archivos, en una base de datos. Las más populares son las que implementan en el modelo relacional: guardan la información en filas de tablas, permitiendo asociar filas de tablas distintas. También se las conoce como «bases de datos SQL» porque ofrecen el celebérrimo lenguaje SQL.

En definitiva, las bases de datos «de toda la vida» que a buen seguro conoces. Dominan el mercado desde los años ochenta y el panorama seguirá así a medio plazo. Todo esto sin menoscabo de otros productos que resultan más idóneos en ciertos nichos (Big Data, tiempo real…) e identificados con la etiqueta genérica «base de datos NoSQL».

Así pues, encontrar en el centro de las aplicaciones de gestión una base de datos relacional es lo más normal del mundo. Incluso en algunos casos estas aplicaciones son meras interfaces de la base de datos subyacente.

El «problema» con la API JDBC

Llegados a este punto, como programadores Java y\o Kotlin que somos, surge la gran pregunta: ¿cómo usamos bases de datos relacionales desde el código?

La respuesta se halla en la API JDBC incluida en Java estándar. Ejerce de mediadora entre nuestro código y la base de datos relacional porque su cometido es la ejecución de sentencias SQL. La emplearemos para interactuar con cualquier base de datos que provea una implementación, llamada driver o controlador JDBC.

Si alguna vez programaste con la API JDBC, habrás experimentado lo tedioso que puede ser trabajar con ella. ¿El motivo? La ejecución de SQL requiere un montón de código. Pero un montón de verdad.

Veámoslo con un ejemplo que habla por sí solo. El siguiente método obtiene de una tabla countries los países con una población menor que cierta cantidad. Al igual que el código de todos los capítulos, lo encontrarás en el proyecto de ejemplo del curso que presentaré en la próxima entrega.

@Autowired
private DataSource dataSource;

public List<Country> findByPopulationLessThan(int maxPopulation) throws SQLException {
    String sql = "SELECT * FROM countries WHERE population < ? ORDER BY name";
    try (Connection connection = dataSource.getConnection();
         PreparedStatement ps = connection.prepareStatement(sql);) {
         ps.setInt(1, maxPopulation);
         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 = mapToCountry(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"),
            resultSet.getString("capital"),
            resultSet.getBoolean("ocde"),
            mapToLocalDate(resultSet.getDate("united_nations_admission")),
            null);
}

private LocalDate mapToLocalDate(Date date) {
    if (date == null) {
        return null;
    }
    return date.toLocalDate();
}

¡Casi cuarenta líneas! Y eso que se trata de una operación tan rutinaria que debería ser trivial. Además, ni siquiera hemos tenido que lidiar con transacciones de manera explícita por tratarse de una simple operación de lectura.

Es siempre el mismo «rollo»: obtener la conexión, construir un Statement \PreparedStatement con el SQL y sus parámetros, ejecutarlo, recorrer el ResultSet con los resultados para volcarlos en objetos. Peléate con nulos, índices y cadenas. ¡Ah! Y recuerda cerrar los recursos y gestionar las excepciones.

Esto no es consecuencia, ni mucho menos, de un pobre diseño o de la falta de capacidades de JDBC. Pretende ser la abstracción de nivel más bajo para comunicarnos con las bases de datos. Su API es pequeña y sencilla con el objetivo de facilitar a los desarrolladores de bases de datos su implementación.

Si nos pagaran por líneas escritas, estamos de suerte. Como supongo que ningún programador se encuentra en esta situación, más pronto que tarde crearemos clases genéricas con el propósito de reutilizar ese código repetitivo (boilerplate) y nada sexy que apenas cambia entre consultas distintas. Insisto: el mismo rollo una y otra vez.

Esto último lo ofrece Spring JDBC Template con su API pequeña y fácil de utilizar. Nos permite centrarnos en la escritura de las sentencias SQL y dejar de lado la gestión de las conexiones y las transacciones, entre otras ventajas.

Nuestro ejemplo quedaría así:

@Autowired
private JdbcTemplate jdbcTemplate;

public List<Country> findByPopulationLessThanWithTemplate(int maxPopulation) {
    return jdbcTemplate.query("SELECT * FROM countries WHERE population < ? ORDER BY name",
            (rs, rowNum) -> mapToCountry(rs), maxPopulation);
}

private Country mapToCountry(ResultSet resultSet) throws SQLException {
    return new Country(
            resultSet.getLong("id"),
            resultSet.getString("name"),
            resultSet.getInt("population"),
            resultSet.getString("capital"),
            resultSet.getBoolean("ocde"),
            mapToLocalDate(resultSet.getDate("united_nations_admission")),
            null);
}

private LocalDate mapToLocalDate(Date date) {
    if (date == null) {
        return null;
    }
    return date.toLocalDate();
}

Una buena simplificación. Pero «solo» hemos reducido el código a la mitad: seguimos necesitando el método mapToCountry para procesar el ResultSet.

Hibernate y JPA

El verdadero salto que dispara nuestra productividad lo daremos si recurrimos a las herramientas de tipo ORM (object relational mapping). En pocas palabras: partiendo de la configuración de la equivalencia de ciertas clases de nuestro dominio con las tablas son capaces de realizar la transformación entre ambas. Es decir, funcionan como una especie de traductor; la bisagra que une el mundo relacional con el de la orientación a objetos. (*)

(*) Aunque existen bases de datos orientadas a objetos desde el surgimiento de este paradigma, nunca han pasado de ser una anécdota.

El ORM más popular para Java es Hibernate. Es compatible con numerosas bases de datos e implementa las APIs de la especificación Jakarta Persistence (JPA) antes conocida como «Java Persistence API». El principal valor de las especificaciones radica en que mientras nos ciñamos a ellas, nuestro código será compatible con cualquiera de sus proveedores o implementaciones.

Debes comprender lo anterior para que puedas hablar con propiedad: JPA solo define una API y su funcionamiento. El código de Hibernate la implementa, ofreciendo todo lo que especifica JPA y otras funcionalidades adicionales.

En JPA, las clases de tipo entidad modelan las tablas. Los objetos de esas clases se denominan entidades y representan a los registros de las tablas.

Uno de los grandes beneficios de JPA radica en la sincronización automática entre los registros de las tablas y las entidades. Esto significa que si modificamos un atributo de una entidad, los cambios se guardarán de manera automática en la base de datos. Otra característica destacable es la disponibilidad del lenguaje de consultas JPQL, similar a SQL, pero basado en clases de tipo entidad en lugar de tablas.

En mi curso dedicado a Jakarta EE tienes más de veinte capítulos dedicados a JPA con Hibernate. No obstante, en ningún momento perderé de vista que el presente curso está orientado a principiantes. Haré aclaraciones breves sobre JPA para que no te sientas perdido si tus conocimientos son limitados.

Observa esta nueva ilustración. Muestra la capa de abstracción que JPA añade sobre la API JDBC. Es inevitable que JDBC siga ahí, pues es la forma en la que Java interactúa con la base de datos. Pero ahora el proveedor de JPA es quien genera y ejecuta las sentencias SQL que sean necesarias de forma invisible.

Esta versión de findByPopulationLessThan usa el gestor de entidades (entityManager) de JPA:

@PersistenceContext
private EntityManager entityManager;

public List<Country> findByPopulationLessThanWithJpa(int maxPopulation) {
  return entityManager.createQuery("SELECT c FROM Country c WHERE c.population < :maxPopulation ORDER BY c.name", Country.class)
                .setParameter("maxPopulation", maxPopulation)
                .getResultList();
}

Se precisa poco código para ejecutar la consulta, en este caso con el lenguaje JPQL. La gran diferencia con respecto a Jdbc Template es que JPA sabe cómo volcar los resultados en entidades de la clase Country. Así, nos ahorramos la escritura del método mapToCountry. Debemos esta magia a la configuración que pondremos con anotaciones en la clase Country en el próximo capítulo.

Como cabría esperar, la integración de Spring con JPA e Hibernate es perfecta. Y Spring Boot la configura por nosotros.

Spring Data: la victoria final 🚀

¿Alguien da más? Pues sí, porque todavía queda el paso definitivo en la búsqueda de la simplicidad extrema: una nueva capa de abstracción o cajita que añadiremos a las ilustraciones anteriores.

Spring Data es uno de los numerosos proyectos del vasto ecosistema de desarrollo de Spring Framework. Según la página web oficial:

«La misión de Spring Data es ofrecer un modelo de programación familiar y coherente, basado en Spring, para el acceso a los datos, al tiempo que conserva las características especiales del almacén de datos subyacente. Facilita el uso de tecnologías de acceso a datos, bases de datos relacionales y no relacionales, frameworks «map-reduce» y servicios de datos basados en la nube. Es un proyecto paraguas que contiene muchos subproyectos específicos para una base de datos determinada.»

De los subproyectos o módulos de Spring Data, el curso versa sobre Spring Data JPA. Es el que da soporte a la explotación de bases de datos relacionales usando como intermediario un proveedor de JPA. En nuestro caso, será Hibernate.

Gracias a la acumulación de todas las capas de abstracción que aparecen en la imagen, se cumplirá nuestro anhelo: centrarnos en las consultas, lo único que importa, y olvidarnos casi por completo de lo demás.

Suena bien, pero estas explicaciones resultan abstractas. ¿Cómo funciona Spring Data JPA? A lo largo del curso exploraremos con detalle sus numerosas funcionalidades. De momento, es suficiente con ver la cuarta y última versión de findByPopulationLessThan:

public interface CountryRepository extends Repository<Country, Long> {

    List<Country> findByPopulationLessThanOrderByName(int population);

}

Eso es todo. La primera vez vi algo así aluciné. Estamos ante un repositorio, concepto que constituye la piedra angular sobre la que se edifica Spring Data. En este caso particular, solo se necesitó la declaración del método. Su nombre define con precisión las entidades que obtiene porque sigue una serie de convenciones que examinaremos en el capítulo cuatro (consultas derivadas). La interfaz CountryRepository está lista para ser inyectada donde queramos llamar a su método.

¡Y todavía no has visto nada! Lo anterior apenas es un aperitivo de las maravillas que Spring Data nos regala.

La siguiente imagen (clic para ampliar) muestra el itinerario que hemos seguido. ¿Mereció la pena? Mi respuesta es un rotundo sí.

Pongámonos pesimistas: ¿y si encuentras algún caso en el que Spring Data JPA no te sirva? Ningún problema. Puedes recurrir al gestor de entidades de JPA, Spring Jdbc Template o incluso la API JDBC. Nada te impide usar cualquiera de las cajitas del diagrama.

Muchas posibilidades, ninguna limitación. Flexibilidad absoluta.

Para finalizar, quiero subrayar que Spring Data JPA es tan solo uno de los módulos que componen el proyecto Spring Data. En todos trabajaremos de forma similar mediante la creación de repositorios y una API común (Spring Data Commons). Es el modelo de programación coherente al que hace referencia la definición que puse. Y todo sin renunciar a las posibilidades específicas de cada tipo de fuente de datos que encontraremos en su módulo.

Podrás reutilizar parte de los conocimientos que conseguirás en el curso si en algún momento necesitas trabajar, por ejemplo, con Spring Data MongoDB o Spring Data Cassandra. Seré riguroso, de tal modo que el empleo los términos «Spring Data» y «Spring Data JPA» será intencionado: estaré distinguiendo entre las características genéricas de Spring Data y las propias del módulo JPA.

Código de ejemplo

El proyecto de ejemplo del curso se encuentra en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.


Capítulo 2: proyecto de ejemplo >>>


Otros tutoriales relacionados con Spring

Introducción a Spring Boot: Aplicación Web con servicios REST y Spring Data JPA

Spring Boot: Gestión de errores en aplicaciones web y REST

Testing en Spring Boot con JUnit 45. Mockito, MockMvc, REST Assured, bases de datos embebidas

Spring JDBC Template: simplificando el uso de SQL

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.