Los últimos dos blogs publicados (parte 1 y parte 2), presentaban ejemplos de cómo desarrollar aplicaciones utilizando Spring Boot de Reactive. Siguiendo con el mismo tema, analizaremos ahora cómo diseñar e implementar repositorios R2DBC “inteligentes” para PostgreSQL. El propósito de este post es presentar una asombrosa característica del ORM de R2DBC para motores de base de datos PostgreSQL. Gracias a ella, es posible traducir métodos Java (usando sus firmas) en consultas PostgreSQL que permiten realizar todo tipo de acciones:
- Aplicar a la consulta la cláusula WHERE correspondiente.
- Traducir los parámetros del método Java a tipos de datos SQL
- Añadir restricciones de la cláusula WHERE con agregación (AND, OR, NOT, etc.)
- Realizar de forma segura la propagación de comprobaciones de valores NULL y la búsqueda de datos NULL o no NULL en la base de datos
- Comprobar que el valor esté dentro de los límites (las cláusulas SQL BETWEEN e IN)
R2DBC permite a los desarrolladores escribir estas consultas de ambas maneras; pueden hacerlo usando el código SQL sin formato con el atributo @Query, o pueden escribir el método con una firma específica que permita a R2DBC traducir la consulta automáticamente. En este post, aprenderá a usar la característica de R2DBC para traducir la consulta de forma automática en el repositorio.
Aplicación Web Java
Continuaremos usando la misma aplicación Java que desarrollamos en las últimas dos partes de la serie sobre Spring Boot de Reactive. El objetivo es explicar más a fondo la interesante característica que ofrece R2DBC. Añadiré los cambios para los componentes y mencionaré cualquier nuevo elemento que añada a la biblioteca para que usted también pueda incluirlo en su proyecto.
Repositorio PostgreSQL
Ya hemos desarrollado el repositorio PostgreSQL (recién implementado) que nos permite realizar las operaciones básicas CRUD (acrónimo de “Crear, Leer, Actualizar y Borrar”). Nuestros controladores RESTful utilizan este repositorio para crear nuevos registros en PostgreSQL así como para consultar o eliminar los registros de la base de datos. Aunque todo esto funciona muy bien, para escribir consultas personalizadas necesitaremos usar el atributo @Query y escribir allí nuestro script SQL. Algo como esto:
@Query(“SELECT * FROM books WHERE publish_year = :year”)
public Flux<Book> findAllBooksByPublishYear(int year);
Esto debería funcionar bien, aunque requiere que escribamos la consulta para cada una de las operaciones que necesitamos realizar. En cambio, R2DBC se encarga de interpretar y traducir de forma automática los métodos Java introducidos usando un formato especial.
La estructura es la siguiente: empezamos con el nombre del método utilizando “findBy” y luego añadimos el nombre de la columna que necesitamos usar como filtro. Por ejemplo, si queremos encontrar los libros en función de su clasificación, escribiríamos findByRating y proporcionaríamos el valor de la clasificación como parámetro para este método.
Como ejemplo, presentaremos dos métodos:
- findByPublishYear
- findByRatingGreaterThan
Estos dos métodos ilustran los beneficios que ofrece R2DBC. En el primer método, empleando la columna del año de publicación, se traduce la consulta mediante una cláusula WHERE que usa el año de publicación de un libro. En el segundo método, se indica a R2DBC que queremos resultados en los que la columna de clasificación contenga un valor superior al que se ha proporcionado. A continuación se ilustran los dos métodos Java:
package com.demos.reactivepostgresql.data;
import com.demos.reactivepostgresql.models.Book;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Flux;
public interface BookRepository extends ReactiveCrudRepository<Book, Long> {
Flux<Book> findByPublishYear(int publishYear);
Flux<Book> findByRatingGreaterThan(double rating);
}
Este es el código completo que necesitamos escribir en nuestro repositorio para obtener esta característica; recuerde que también ReactiveCrudRepository presenta sus propios métodos para las operaciones básicas CRUD.
Limitar los resultados
R2DBC permite incluir “First” antes de “By”, en el nombre del método, para limitar los resultados de la consulta al primer elemento superior devuelto por el cursor general.
Flux<Book> findFirstByPublishYear(int publishYear);
Búsqueda de texto
El repositorio permite utilizar la operación SQL LIKE con la cláusula WHERE para consultar el contenido del texto que sigue una estructura. Para conseguirlo, al ejecutar SQL, se pueden aplicar “Like”, “StartingWith” y “EndingWith” a una consulta específica.
Asimismo, es posible invertir la operación limitando los casos en los que el texto es “not like”. Todas estas operaciones son realizadas automáticamente por el repositorio.
Tipos de datos
El repositorio permite también consultar los resultados utilizando tipos de datos personalizados o específicos por lenguaje. El tipo de datos más común sería en lenguaje Java. Se pueden emplear “Before” or “After” para comprobar si el registro fue creado antes o después de una fecha específica.
Java cuenta con una amplia gama de aplicaciones de la palabra clave null, y R2DBC permite verificar si en la base de datos algún valor es nulo o no, para luego tomar acciones específicas. Es posible usar “IsNull” o “IsNotNull” en los nombres de los métodos del repositorio para aplicar las marcas.
Se pueden utilizar también valores booleanos como “IsTrue” o “IsFalse” para consultar los registros correspondientes a esos tipos, contribuyendo así a que la aplicación Java sea más legible.
Antes de escribir sus propias consultas, por favor, aprenda las reglas básicas que R2DBC utiliza para traducir los métodos y sus nombres a las correspondientes consultas SQL en ejecución. Esto le ayudará a evitar que se produzcan errores lógicos o de ejecución.
Puntos de conexión RESTful
Se han añadido al repositorio dos nuevos métodos que realizan consultas en la base de datos siguiendo un criterio de búsqueda que obedece a los parámetros Java. En nuestro controlador, necesitamos añadir dos puntos de conexión adicionales que nos permitan consultar la base de datos PostgreSQL de acuerdo con las consultas SQL generadas en el repositorio.
Este es el código que usaremos:
package com.demos.reactivepostgresql.controllers;
import com.demos.reactivepostgresql.data.BookRepository;
import com.demos.reactivepostgresql.models.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping(value = "/api/books")
public class HomeController {
@Autowired BookRepository bookRepository;
// 1
@GetMapping("")
public Flux<Book> getAllBooks() {
return bookRepository.findAll();
}
// 2
@GetMapping("publishyear")
public Flux<Book> getBooksByPublishYear(int value) {
return bookRepository.findByPublishYear(value);
}
// 3
@GetMapping("rating")
public Flux<Book> getBooksByRating(double value) {
return bookRepository.findByRatingGreaterThan(value);
}
@PostMapping("")
public Mono<Book> postBook(@RequestBody Book book) {
return bookRepository.save(book);
}
@PutMapping("")
public Mono<Book> updateBook(@RequestBody Book book) {
return bookRepository.save(book);
}
@DeleteMapping("")
public boolean deleteBook(@RequestBody Book book) {
try {
bookRepository.deleteById(book.getId()).block();
return true;
} catch (Exception e) {
return false;
}
}
}
El código contiene tres manejadores GET. Uno de ellos se encarga de la operación básica de lectura, y dos de las acciones específicas. En la siguiente sección, aparece la consulta SQL que deberá ser escrita de forma manual por el desarrollador Java que desea usar el enfoque @Query para escribir los métodos.
Para que este código funcione, tenemos que modificar nuestro modelo Java también para la categoría de libros. Este es el código actualizado:
package com.demos.reactivepostgresql.models;
import org.springframework.data.annotation.Id;
public class Book {
@Id
private Long id;
private String title;
private String author;
private int publishYear;
private double rating;
public Book() {}
public Book(String title, String author, int publishYear, double rating) {
this.title = title;
this.author = author;
this.publishYear = publishYear;
this.rating = rating;
}
public Book(Long id, String title, String author, int publishYear, double rating) {
this.id = id;
this.title = title;
this.author = author;
this.publishYear = publishYear;
this.rating = rating;
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return this.title;
}
public String getAuthor() {
return this.author;
}
public int getPublishYear() {
return this.publishYear;
}
public double getRating() {
return this.rating;
}
@Override
public String toString() {
return "book"; // update to suit your app-specific needs, reader.
}
}
Obtener los registros
Para obtener los registros de las consultas SQL, añada la siguiente línea al archivo application.properties:
logging.level.org.springframework.data.r2dbc=DEBUG
Esto permitirá el registro SQL de las consultas que se envían al motor de base de datos PostgreSQL.
Realizar consultas inteligentes
Tras haber sentado las bases para nuestro motor PostgreSQL y para la ejecución de consultas en la base de datos mediante la aplicación web Java, podemos realizar las consultas utilizando los puntos de conexión RESTful. Al igual que en nuestros blogs anteriores, usaremos Postman para enviar la solicitud y capturar/imprimir la respuesta. Luego emplearemos OmniDB para verificar y comparar los resultados obtenidos averiguando si coinciden.
La siguiente es la captura de pantalla de los contenidos de nuestra base de datos:
Obtener todos los resultados
Si enviamos la petición al punto de conexión /api/books, nuestro método findAll() se ejecutará en el repositorio, devolviendo todos los registros de la tabla.
Obtenemos los datos a través de una secuencia que se genera a medida que PostgreSQL devuelve los mismos datos a nuestra aplicación Java. Spring Boot de Reactive traduce la consulta SQL como:
o.s.d.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [SELECT book.* FROM book]
En la siguiente parte, observará cómo R2DBC traduce las consultas de nuestros métodos inteligentes.
Resultados por año de publicación
Ahora, usando la propiedad “año de publicación” de nuestra entidad, obtendremos los resultados que han sido filtrados por PostgreSQL para esta columna. Enviamos la petición al punto de conexión /api/books/publishyear y pasamos el valor usando el parámetro de la cadena de consulta de valor.
PostgreSQL devuelve sólo los datos que están de acuerdo con la condición, que en el caso de nuestro ejemplo, es el año 2019. Puede comparar este resultado con los datos originales de la tabla que aparece arriba en la captura de pantalla de OmniDB.
Podemos obtener este resultado usando el siguiente código SQL:
SELECT * FROM public.books WHERE publish_year = 2019;
Esta es una consulta SQL muy básica que permite filtrar los datos. Para escenarios especializados puede ser necesario añadir la cláusula ORDER BY o incluir también los nombres de las columnas. En este contexto, la escritura manual de estas consultas resulta complicada, especialmente cuando se deben filtrar centenares de tablas y de columnas.
R2DBC, utiliza la consulta SQL parametrizada para obtener los datos de la base de datos:
o.s.d.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [SELECT book.id, book.title, book.author, book.publish_year, book.rating FROM book WHERE book.publish_year = $1]
R2DBC, utiliza la consulta SQL parametrizada para obtener los datos de la base de datos:
Resultados por calificación
Al escribir los métodos Java, R2DBC permite utilizar en la consulta SQL operadores de comparación: > (mayor que),
Tras llamar al punto de conexión /api/books/rating, pasamos el valor de 4 a la cadena de consulta. Esto permitirá limitar los resultados a los registros que presenten un valor de 4 o más.
Puede también comparar este resultado con la captura de pantalla inicial de la base de datos en OmniDB. PostgreSQL devolvió una lista de los registros en los que el valor de clasificación era mayor que 4. Podemos obtener los resultados usando el siguiente código SQL:
SELECT * FROM public.books WHERE rating > 4;
Como se muestra en la siguiente captura de pantalla:
Para obtener los resultados, R2DBC tradujo el método a la siguiente consulta SQL:
o.s.d.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [SELECT book.id, book.title, book.author, book.publish_year, book.rating FROM book WHERE book.rating > $1]
Siguiendo este enfoque, es posible escribir métodos Java para cada una de las operaciones que permitan filtrar, ordenar o limitar algún tipo de valor. R2DBC se encargará de la generación del código SQL correspondiente.
Resumen
En este post, analizamos cómo crear mejores repositorios R2DBC basados en Java, para conexiones de bases de datos PostgreSQL. Estos nos ayudarán a realizar consultas SQL más expresivas en el backend. Vimos también varias convenciones de nomenclatura de métodos soportadas por los repositorios R2DBC de PostgreSQL. Gracias a ellas es posible traducir la consulta de forma sencilla en SQL y en Java. Además, descubrimos lo fácil que es usar R2DBC en aplicaciones Java para desarrollar un front-end para bases de datos PostgreSQL.
Consulte también la documentación del proyecto R2DBC y aprenda qué otros métodos están disponibles para la traducción desde Java a PostgreSQL. La traducción para PostgreSQL ocurre gracias a la dependencia PostgreSQL.