Datos de primavera JPA @Query

Descripción general

Los datos de primavera proporcionan muchas formas de definir una consulta que podemos ejecutar. Una de ellas es la anotación @Query.

En este tutorial, demostraremos cómo usar la anotación @Query en Spring Data JPA para ejecutar consultas JPQL y SQL nativas.

También mostraremos cómo crear una consulta dinámica cuando la anotación @ Query no sea suficiente.

Más información:

Métodos de consulta derivados en Repositorios JPA de Datos de Primavera

Explore el mecanismo de derivación de consultas en JPA de datos de Primavera.
Leer más →

JPA de datos de primavera @Modificando Anotación

Cree consultas DML y DDL en JPA de datos de primavera combinando las anotaciones @Query y @Modificando
Leer más →

Seleccionar Consulta

Para definir SQL a ejecutar para un método de repositorio de datos de Primavera, podemos anotar el método con la anotación @Query: su atributo de valor contiene el JPQL o SQL a ejecutar.

La anotación @Query tiene prioridad sobre las consultas con nombre, que se anotan con @NamedQuery o se definen en un or.archivo xml.

Es un buen enfoque colocar una definición de consulta justo encima del método dentro del repositorio en lugar de dentro de nuestro modelo de dominio como consultas con nombre. El repositorio es responsable de la persistencia, por lo que es un mejor lugar para almacenar estas definiciones.

2.1. JPQL

De forma predeterminada, la definición de consulta utiliza JPQL.

Veamos un método de repositorio simple que devuelve entidades de usuario activas de la base de datos:

@Query("SELECT u FROM User u WHERE u.status = 1")Collection<User> findAllActiveUsers();

2.2. Nativo

También podemos usar SQL nativo para definir nuestra consulta. Todo lo que tenemos que hacer es establecer el valor del atributo nativeQuery en true y definir la consulta SQL nativa en el atributo value de la anotación:

@Query( value = "SELECT * FROM USERS u WHERE u.status = 1", nativeQuery = true)Collection<User> findAllActiveUsersNative();

Definir orden en una consulta

Podemos pasar un parámetro adicional de tipo Sort a una declaración de método de datos de Resorte que tenga la anotación @Query. Se traducirá a la cláusula ORDER BY que se pasa a la base de datos.

3.1. Clasificación para Métodos proporcionados por JPA y Derivados

Para los métodos que obtenemos de la caja, como findAll (Sort) o los que se generan mediante el análisis de firmas de métodos, solo podemos usar propiedades de objeto para definir nuestra clasificación:

userRepository.findAll(Sort.by(Sort.Direction.ASC, "name"));

Ahora imagine que queremos ordenar por la longitud de una propiedad name:

userRepository.findAll(Sort.by("LENGTH(name)"));

Cuando ejecutemos el código anterior, recibiremos una excepción:

org.springframework.datos.asignación.PropertyReferenceException: ¡No se ha encontrado ninguna longitud de propiedad(nombre) para el tipo Usuario!

3.2. JPQL

Cuando usamos JPQL para una definición de consulta, Spring Data puede manejar la clasificación sin ningún problema, todo lo que tenemos que hacer es agregar un parámetro de método de tipo Sort:

@Query(value = "SELECT u FROM User u")List<User> findAllUsers(Sort sort);

Podemos llamar a este método y pasar un parámetro de Ordenación, que ordenará el resultado por la propiedad name del objeto User:

userRepository.findAllUsers(Sort.by("name"));

Y debido a que usamos la anotación @Query, podemos usar el mismo método para obtener la lista ordenada de usuarios por la longitud de sus nombres:

userRepository.findAllUsers(JpaSort.unsafe("LENGTH(name)"));

Es crucial que usemos JpaSort.unsafe() para crear una instancia de objeto de ordenación.

Cuando usamos:

Sort.by("LENGTH(name)");

entonces recibiremos exactamente la misma excepción que vimos anteriormente para el método findAll ().

Cuando Spring Data descubre el orden de clasificación inseguro para un método que usa la anotación @Query, simplemente agrega la cláusula sort a la consulta, omite verificar si la propiedad por la que se va a ordenar pertenece al modelo de dominio.

3.3. Nativo

Cuando la anotación @ Query utiliza SQL nativo, no es posible definir una ordenación.

Si lo hacemos, recibiremos una excepción:

org.springframework.datos.jpa.repositorio.consulta.InvalidJpaQueryMethodException: No se pueden usar consultas nativas con ordenación dinámica y/o paginación

Como dice la excepción, la ordenación no es compatible con consultas nativas. El mensaje de error nos da una pista de que la paginación también causará una excepción.

Sin embargo, hay una solución alternativa que habilita la paginación, y la trataremos en la siguiente sección.

Paginación

La paginación nos permite devolver solo un subconjunto de un resultado completo en una página. Esto es útil, por ejemplo, cuando se navega a través de varias páginas de datos en una página web.

Otra ventaja de la paginación es que la cantidad de datos enviados del servidor al cliente se minimiza. Al enviar datos más pequeños, generalmente podemos ver una mejora en el rendimiento.

4.1. JPQL

Usar la paginación en la definición de consulta JPQL es sencillo:

@Query(value = "SELECT u FROM User u ORDER BY id")Page<User> findAllUsersWithPagination(Pageable pageable);

Podemos pasar un parámetro PageRequest para obtener una página de datos.

La paginación también es compatible con consultas nativas, pero requiere un poco de trabajo adicional.

4.2. Nativo

Podemos habilitar la paginación de consultas nativas declarando un atributo adicional countQuery.

Esto define el SQL a ejecutar para contar el número de filas en todo el resultado:

@Query( value = "SELECT * FROM Users ORDER BY id", countQuery = "SELECT count(*) FROM Users", nativeQuery = true)Page<User> findAllUsersWithPagination(Pageable pageable);

4.3. Versiones de Spring Data JPA Anteriores a 2.0.4

La solución anterior para consultas nativas funciona bien para las versiones de Spring Data JPA 2.0.4 y posteriores.

Antes de esa versión, cuando intentemos ejecutar una consulta de este tipo, recibiremos la misma excepción que describimos en la sección anterior sobre ordenación.

Podemos superar esto agregando un parámetro adicional para la paginación dentro de nuestra consulta:

@Query( value = "SELECT * FROM Users ORDER BY id \n-- #pageable\n", countQuery = "SELECT count(*) FROM Users", nativeQuery = true)Page<User> findAllUsersWithPagination(Pageable pageable);

En el ejemplo anterior, agregamos «\n – #pageable\n » como marcador de posición para el parámetro de paginación. Esto le indica a Spring Data JPA cómo analizar la consulta e inyectar el parámetro pageable. Esta solución funciona para la base de datos H2.

Hemos cubierto cómo crear consultas de selección simples a través de JPQL y SQL nativo. A continuación, mostraremos cómo definir parámetros adicionales.

Parámetros de consulta indexados

Hay dos formas posibles de pasar parámetros de método a nuestra consulta: parámetros indexados y con nombre.

En esta sección, cubriremos los parámetros indexados.

5.1. JPQL

Para los parámetros indexados en JPQL, los datos de resorte pasarán los parámetros del método a la consulta en el mismo orden en que aparecen en la declaración del método:

@Query("SELECT u FROM User u WHERE u.status = ?1")User findUserByStatus(Integer status);@Query("SELECT u FROM User u WHERE u.status = ?1 and u.name = ?2")User findUserByStatusAndName(Integer status, String name);

Para las consultas anteriores, el parámetro de método de estado se asignará al parámetro de consulta con índice 1, y el parámetro de método de nombre se asignará al parámetro de consulta con índice 2.

5.2. Los parámetros indexados nativos

para las consultas nativas funcionan exactamente de la misma manera que para JPQL:

@Query( value = "SELECT * FROM Users u WHERE u.status = ?1", nativeQuery = true)User findUserByStatusNative(Integer status);

En la siguiente sección, mostraremos un enfoque diferente: pasar parámetros por nombre.

Parámetros con nombre

También podemos pasar parámetros de método a la consulta usando parámetros con nombre. Los definimos usando la anotación @Param dentro de nuestra declaración de método de repositorio.

Cada parámetro anotado con @Param debe tener una cadena de valor que coincida con el nombre de parámetro de consulta JPQL o SQL correspondiente. Una consulta con parámetros con nombre es más fácil de leer y es menos propensa a errores en caso de que la consulta necesite ser refactorizada.

6.1. JPQL

Como se mencionó anteriormente, usamos la anotación @Param en la declaración del método para hacer coincidir los parámetros definidos por nombre en JPQL con los parámetros de la declaración del método:

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")User findUserByStatusAndNameNamedParams( @Param("status") Integer status, @Param("name") String name);

Tenga en cuenta que en el ejemplo anterior, definimos nuestros parámetros de consulta y método SQL para que tengan los mismos nombres, pero no es necesario siempre que las cadenas de valor sean las mismas:

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")User findUserByUserStatusAndUserName(@Param("status") Integer userStatus, @Param("name") String userName);

6.2. Nativo

Para la definición de consulta nativa, no hay diferencia en cómo pasamos un parámetro a través del nombre a la consulta en comparación con JPQL: usamos la anotación @Param:

@Query(value = "SELECT * FROM Users u WHERE u.status = :status and u.name = :name", nativeQuery = true)User findUserByStatusAndNameNamedParamsNative( @Param("status") Integer status, @Param("name") String name);

7. Parámetro de colección

Consideremos el caso cuando la cláusula where de nuestra consulta JPQL o SQL contiene la palabra clave IN (o NO IN) :

SELECT u FROM User u WHERE u.name IN :names

En este caso, podemos definir un método de consulta que tome la colección como parámetro:

@Query(value = "SELECT u FROM User u WHERE u.name IN :names")List<User> findUserByNameList(@Param("names") Collection<String> names);

Como el parámetro es una colección, se puede usar con Lista, HashSet, etc.

A continuación, mostraremos cómo modificar los datos con la anotación @ Modificando.

Actualizar consultas Con @Modificando

Podemos usar la anotación @Query para modificar el estado de la base de datos agregando también la anotación @Modificando al método repositorio.

8.1. JPQL

El método de repositorio que modifica los datos tiene dos diferencias en comparación con la consulta select: tiene la anotación @ Modifying y, por supuesto, la consulta JPQL usa update en lugar de select:

@Modifying@Query("update User u set u.status = :status where u.name = :name")int updateUserSetStatusForName(@Param("status") Integer status, @Param("name") String name);

El valor devuelto define cuántas filas actualizó la ejecución de la consulta. Tanto los parámetros indexados como los con nombre se pueden usar dentro de las consultas de actualización.

8.2. Nativo

Podemos modificar el estado de la base de datos también con una consulta nativa. Solo necesitamos agregar la anotación @ Modificando:

@Modifying@Query(value = "update Users u set u.status = ? where u.name = ?", nativeQuery = true)int updateUserSetStatusForNameNative(Integer status, String name);

8.3. Inserts

Para realizar una operación de inserción, tenemos que aplicar @Modificando y usar una consulta nativa, ya que INSERT no forma parte de la interfaz JPA:

@Modifying@Query( value = "insert into Users (name, age, email, status) values (:name, :age, :email, :status)", nativeQuery = true)void insertUser(@Param("name") String name, @Param("age") Integer age, @Param("status") Integer status, @Param("email") String email);

Consulta dinámica

A menudo, nos encontraremos con la necesidad de crear sentencias SQL basadas en condiciones o conjuntos de datos cuyos valores solo se conocen en tiempo de ejecución. Y en esos casos, no podemos usar una consulta estática.

9.1. Ejemplo de una Consulta dinámica

Por ejemplo, imaginemos una situación en la que necesitamos seleccionar a todos los usuarios cuyo correo electrónico es como uno de un conjunto definido en tiempo de ejecución: email1, email2, em, emailn:

SELECT u FROM User u WHERE u.email LIKE '%email1%' or u.email LIKE '%email2%' ... or u.email LIKE '%emailn%'

Dado que el conjunto está construido dinámicamente, no podemos saber en tiempo de compilación cuántas cláusulas LIKE agregar.

En este caso, no podemos usar simplemente la anotación @Query, ya que no podemos proporcionar una instrucción SQL estática.

En su lugar, implementando un repositorio compuesto personalizado, podemos ampliar la funcionalidad base de JpaRepository y proporcionar nuestra propia lógica para crear una consulta dinámica. Echemos un vistazo a cómo hacer esto.

9.2. Repositorios personalizados y la API de criterios JPA

Afortunadamente para nosotros, Spring proporciona una forma de extender el repositorio base a través del uso de interfaces de fragmentos personalizados. Luego podemos vincularlos para crear un repositorio compuesto.

Comenzaremos creando una interfaz de fragmentos personalizada:

public interface UserRepositoryCustom { List<User> findUserByEmails(Set<String> emails);}

Y luego lo implementaremos:

public class UserRepositoryCustomImpl implements UserRepositoryCustom { @PersistenceContext private EntityManager entityManager; @Override public List<User> findUserByEmails(Set<String> emails) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<User> query = cb.createQuery(User.class); Root<User> user = query.from(User.class); Path<String> emailPath = user.get("email"); List<Predicate> predicates = new ArrayList<>(); for (String email : emails) { predicates.add(cb.like(emailPath, email)); } query.select(user) .where(cb.or(predicates.toArray(new Predicate))); return entityManager.createQuery(query) .getResultList(); }}

Como se muestra arriba, aprovechamos la API de criterios de JPA para crear nuestra consulta dinámica.

Además, debemos asegurarnos de incluir el postfijo Impl en el nombre de la clase. Spring buscará la implementación UserRepositoryCustom como UserRepositoryCustomImpl. Dado que los fragmentos no son repositorios por sí mismos, Spring se basa en este mecanismo para encontrar la implementación de fragmentos.

9.3. Extender el Repositorio existente

Observe que todos los métodos de consulta de la sección 2 a la sección 7 están en el repositorio de usuarios.

Así que ahora integraremos nuestro fragmento extendiendo la nueva interfaz en el repositorio de usuario:

public interface UserRepository extends JpaRepository<User, Integer>, UserRepositoryCustom { // query methods from section 2 - section 7}

9.4. Usando el Repositorio

Y finalmente, podemos llamar a nuestro método de consulta dinámica:

Set<String> emails = new HashSet<>();// filling the set with any number of itemsuserRepository.findUserByEmails(emails);

Hemos creado con éxito un repositorio compuesto y llamado nuestro método personalizado.

Conclusión

En este artículo, cubrimos varias formas de definir consultas en métodos de repositorio JPA de datos de primavera utilizando la anotación @Query.

También aprendimos a implementar un repositorio personalizado y crear una consulta dinámica.

Como siempre, los ejemplos de código completos utilizados en este artículo están disponibles en GitHub.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.