Cómo Crear Consultas Like en Spring JPA

Introducción

En esta entrada, vamos a cubrir diferentes formas de crear consultas LIKE en los Repositorios de Spring JPA. Dedicaremos especial atención a las diversas palabras clave que podemos utilizar al crear métodos de consulta. A su vez, exploraremos la anotación @Query con parámetros nombrados y ordenados, lo que nos permitirá realizar consultas más complejas de manera efectiva.

Configuración

Para nuestro ejemplo, estaremos consultando una tabla de películas. Primero, definiremos nuestra entidad Movie:


@Entity
public class Movie {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    private String title;
    private String director;
    private String rating;
    private int duration;

    // Métodos getter y setter estándar
}

Con nuestra entidad Movie definida, pasemos a crear algunas sentencias de inserción de ejemplo:


INSERT INTO movie(id, title, director, rating, duration) 
VALUES(1, 'Godzilla: King of the Monsters', 'Michael Dougherty', 'PG-13', 132);
INSERT INTO movie(id, title, director, rating, duration) 
VALUES(2, 'Avengers: Endgame', 'Anthony Russo', 'PG-13', 181);
INSERT INTO movie(id, title, director, rating, duration) 
VALUES(3, 'Captain Marvel', 'Anna Boden', 'PG-13', 123);
INSERT INTO movie(id, title, director, rating, duration) 
VALUES(4, 'Dumbo', 'Tim Burton', 'PG', 112);
INSERT INTO movie(id, title, director, rating, duration) 
VALUES(5, 'Booksmart', 'Olivia Wilde', 'R', 102);
INSERT INTO movie(id, title, director, rating, duration) 
VALUES(6, 'Aladdin', 'Guy Ritchie', 'PG', 128);
INSERT INTO movie(id, title, director, rating, duration) 
VALUES(7, 'The Sun Is Also a Star', 'Ry Russo-Young', 'PG-13', 100);

Métodos de Consulta LIKE

3.1. Containing, Contains, IsContaining y Like

Para muchos escenarios sencillos de búsqueda LIKE, podemos aprovechar una variedad de palabras clave para crear métodos de consulta en nuestros repositorios. Consideremos cómo realizar la siguiente consulta LIKE:


SELECT * FROM movie WHERE title LIKE '%in%';

Primero, definamos métodos de consulta utilizando Containing, Contains e IsContaining:


List findByTitleContaining(String title);
List findByTitleContains(String title);
List findByTitleIsContaining(String title);

Llamemos a nuestros métodos de consulta con un título parcial in:


List results = movieRepository.findByTitleContaining("in");
assertEquals(3, results.size());

results = movieRepository.findByTitleIsContaining("in");
assertEquals(3, results.size());

results = movieRepository.findByTitleContains("in");
assertEquals(3, results.size());

Podemos esperar que cada uno de los tres métodos devuelva los mismos resultados. Es relevante mencionar que Spring también proporciona la palabra clave Like, pero se comporta de manera ligeramente diferente, ya que requerimos proporcionar los caracteres comodín con nuestro parámetro de búsqueda.

Definamos un método de consulta LIKE:


List findByTitleLike(String title);

Ahora llamaremos a nuestro método findByTitleLike usando el mismo valor que usamos antes, pero incluyendo los caracteres comodín:


results = movieRepository.findByTitleLike("%in%");
assertEquals(3, results.size());

3.2. StartsWith

Veamos la siguiente consulta:


SELECT * FROM Movie WHERE Rating LIKE 'PG%';

Utilizaremos la palabra clave StartsWith para crear un método de consulta:


List findByRatingStartsWith(String rating);

Con nuestro método definido, llamémoslo con el valor PG:


List results = movieRepository.findByRatingStartsWith("PG");
assertEquals(6, results.size());

3.3. EndsWith

Spring proporciona la funcionalidad opuesta mediante la palabra clave EndsWith. Consideremos esta consulta:


SELECT * FROM Movie WHERE director LIKE '%Burton';

Ahora definiremos un método de consulta EndsWith:


List findByDirectorEndsWith(String director);

Una vez que hayamos definido nuestro método, llamémoslo con el parámetro Burton:


List results = movieRepository.findByDirectorEndsWith("Burton");
assertEquals(1, results.size());

3.4. Insensibilidad a Mayúsculas y Minúsculas

A menudo queremos encontrar todos los registros que contienen una cierta cadena, independientemente del caso. En SQL, podemos lograr esto forzando la columna a letras mayúsculas o minúsculas y proporcionando el mismo valor que buscamos.

Con Spring JPA, podemos utilizar el IgnoreCase combinado con cualquiera de nuestras otras palabras clave:


List findByTitleContainingIgnoreCase(String title);

Ahora podemos llamar al método con the y esperar obtener resultados que contengan tanto resultados en minúsculas como en mayúsculas:


List results = movieRepository.findByTitleContainingIgnoreCase("the");
assertEquals(2, results.size());

3.5. Not

En algunas ocasiones, queremos encontrar todos los registros que no contienen una cadena determinada. Para ello, podemos usar las palabras clave NotContains, NotContaining y NotLike.

Definamos una consulta utilizando NotContaining para encontrar películas con calificaciones que no contengan PG:


List findByRatingNotContaining(String rating);

Ahora llamemos a nuestro método recién definido:


List results = movieRepository.findByRatingNotContaining("PG");
assertEquals(1, results.size());

Para lograr una funcionalidad que encuentre registros donde el nombre del director no comienza con una cadena específica, utilizaremos la palabra clave NotLike para controlar la ubicación de nuestro comodín:


List findByDirectorNotLike(String director);

Finalmente, llamemos al método para encontrar todas las películas donde el nombre del director comienza con algo distinto de An:


List results = movieRepository.findByDirectorNotLike("An%");
assertEquals(5, results.size());

Podemos usar NotLike de manera similar para lograr una funcionalidad combinada de Not y EndsWith.

Uso de la Anotación @Query

A veces, necesitamos crear consultas que son demasiado complicadas para los Métodos de Consulta o que resultarían en nombres de métodos absurdamente largos. En esos casos, podemos usar la anotación @Query para consultar nuestra base de datos.

4.1. Parámetros Nombrados

Para fines de comparación, crearemos una consulta equivalente al método findByTitleContaining que definimos anteriormente:


@Query("SELECT m FROM Movie m WHERE m.title LIKE %:title%")
List searchByTitleLike(@Param("title") String title);

Incluimos nuestros comodines en la consulta que suministramos. La anotación @Param es importante aquí porque estamos utilizando un parámetro nombrado.

4.2. Parámetros Ordenados

Además de los parámetros nombrados, podemos utilizar parámetros ordenados en nuestras consultas:


@Query("SELECT m FROM Movie m WHERE m.rating LIKE ?1%")
List searchByRatingStartsWith(String rating);

Tenemos control sobre nuestros comodines, así que esta consulta es equivalente al método findByRatingStartsWith.

Veamos cómo encontrar todas las películas con una calificación que comience con PG:


List results = movieRepository.searchByRatingStartsWith("PG");
assertEquals(6, results.size());

Cuando utilizamos parámetros ordenados en consultas LIKE con datos no confiables, debemos escapar de los valores de búsqueda entrantes.

Si estamos utilizando Spring Boot 2.4.1 o posterior, podemos usar el método escape del Lenguaje de Expresión de Spring (SpEL):


@Query("SELECT m FROM Movie m WHERE m.director LIKE %?#{escape([0])} escape ?#{escapeCharacter()}")
List searchByDirectorEndsWith(String director);

Llamemos ahora a nuestro método con el valor Burton:


List results = movieRepository.searchByDirectorEndsWith("Burton");
assertEquals(1, results.size());

Conclusión

En este artículo, aprendimos cómo crear consultas LIKE en Repositorios de Spring JPA. Primero, exploramos cómo utilizar las palabras clave proporcionadas para crear métodos de consulta. Luego, aprendimos cómo realizar tareas similares utilizando la anotación @Query con parámetros nombrados y ordenados.

Para programadores especializados en JAVA, aquí hay algunos consejos prácticos:

  • Utiliza los métodos simples: Siempre que sea posible, usa los métodos como Containing, StartsWith, y EndsWith para mantener tu código limpio y fácil de entender.
  • Considera el rendimiento: En consultas complejas, revisa el rendimiento de tus consultas LIKE. A veces, una búsqueda más específica puede ahorrar tiempo y recursos.
  • Escapado de caracteres: Siempre escapa caracteres en consultas que incluyan entradas de usuarios, para prevenir ataques de inyección SQL.
  • Pruebas unitarias: Es importante implementar pruebas unitarias para tus métodos de consulta para asegurarte de que devuelven los resultados esperados.

La combinación de estas prácticas te permitirá maximizar la eficiencia de tu trabajo con Spring JPA y las consultas SQL en general. ¡Feliz programación!