Consultas JPQL con @Query en repositorios
JPQL (Java Persistence Query Language) es el lenguaje de consultas estándar de JPA que nos permite escribir consultas orientadas a objetos en lugar de SQL tradicional. Spring Data JPA integra JPQL a través de la anotación @Query, permitiéndonos definir consultas personalizadas directamente en nuestros repositorios.
A diferencia de las consultas derivadas que se basan en nombres de métodos, @Query nos ofrece control total sobre la consulta que queremos ejecutar. Esto resulta especialmente útil cuando necesitamos consultas complejas que serían difíciles de expresar mediante convenciones de nombres.
Sintaxis básica de JPQL
JPQL utiliza nombres de entidades y sus propiedades en lugar de nombres de tablas y columnas. Las consultas se escriben contra el modelo de objetos, no contra el esquema de base de datos:
@Repository
public interface ProductoRepository extends JpaRepository<Producto, Long> {
@Query("SELECT p FROM Producto p WHERE p.precio > :precio")
List<Producto> findProductosCaros(@Param("precio") BigDecimal precio);
}
En este ejemplo, Producto
hace referencia a la entidad JPA, no a la tabla de base de datos. La consulta utiliza el parámetro nombrado :precio
que se mapea con el parámetro del método mediante @Param
.
Parámetros en consultas JPQL
Existen dos formas principales de pasar parámetros a las consultas JPQL: parámetros posicionales y parámetros nombrados.
Parámetros posicionales utilizan números precedidos por ?
:
@Query("SELECT p FROM Producto p WHERE p.categoria = ?1 AND p.stock > ?2")
List<Producto> findByCategoriaAndStock(String categoria, Integer stock);
Parámetros nombrados utilizan nombres precedidos por :
y son más legibles:
@Query("SELECT p FROM Producto p WHERE p.categoria = :categoria AND p.stock > :stockMinimo")
List<Producto> findByCategoriaAndStock(
@Param("categoria") String categoria,
@Param("stockMinimo") Integer stockMinimo
);
Los parámetros nombrados son la práctica recomendada porque hacen el código más mantenible y menos propenso a errores.
Consultas de selección con proyecciones
JPQL permite seleccionar campos específicos en lugar de entidades completas, lo que mejora el rendimiento cuando solo necesitamos ciertos datos:
@Query("SELECT p.nombre, p.precio FROM Producto p WHERE p.categoria = :categoria")
List<Object[]> findNombreYPrecioPorCategoria(@Param("categoria") String categoria);
Para mayor type safety, podemos crear interfaces de proyección:
public interface ProductoResumen {
String getNombre();
BigDecimal getPrecio();
String getCategoria();
}
@Query("SELECT p.nombre as nombre, p.precio as precio, p.categoria as categoria " +
"FROM Producto p WHERE p.activo = true")
List<ProductoResumen> findProductosActivosResumen();
Consultas con JOIN
JPQL soporta diferentes tipos de JOIN para consultar datos relacionados:
@Query("SELECT p FROM Producto p JOIN p.categoria c WHERE c.nombre = :nombreCategoria")
List<Producto> findProductosPorNombreCategoria(@Param("nombreCategoria") String nombreCategoria);
@Query("SELECT p FROM Producto p LEFT JOIN p.reviews r WHERE r.puntuacion > :puntuacion")
List<Producto> findProductosConBuenasReviews(@Param("puntuacion") Integer puntuacion);
Consultas de modificación
Para operaciones de actualización y eliminación, necesitamos usar @Modifying
junto con @Query
:
@Modifying
@Query("UPDATE Producto p SET p.precio = p.precio * :factor WHERE p.categoria = :categoria")
int actualizarPreciosPorCategoria(
@Param("factor") BigDecimal factor,
@Param("categoria") String categoria
);
@Modifying
@Query("DELETE FROM Producto p WHERE p.stock = 0 AND p.fechaCreacion < :fecha")
int eliminarProductosSinStockAntiguos(@Param("fecha") LocalDateTime fecha);
Es importante recordar que las consultas de modificación deben ejecutarse dentro de una transacción, típicamente usando @Transactional
en el método del servicio que las invoca.
Consultas con ordenación y paginación
JPQL integra perfectamente con Pageable para implementar paginación:
@Query("SELECT p FROM Producto p WHERE p.precio BETWEEN :precioMin AND :precioMax ORDER BY p.precio ASC")
Page<Producto> findProductosEnRangoPrecio(
@Param("precioMin") BigDecimal precioMin,
@Param("precioMax") BigDecimal precioMax,
Pageable pageable
);
También podemos especificar ordenación directamente en la consulta:
@Query("SELECT p FROM Producto p WHERE p.categoria = :categoria ORDER BY p.fechaCreacion DESC, p.nombre ASC")
List<Producto> findProductosPorCategoriaOrdenados(@Param("categoria") String categoria);
Funciones agregadas y agrupación
JPQL soporta funciones agregadas como COUNT, SUM, AVG, MAX y MIN:
@Query("SELECT COUNT(p) FROM Producto p WHERE p.categoria = :categoria")
Long contarProductosPorCategoria(@Param("categoria") String categoria);
@Query("SELECT p.categoria, AVG(p.precio) FROM Producto p GROUP BY p.categoria")
List<Object[]> obtenerPrecioPromedioPorCategoria();
@Query("SELECT p.categoria, COUNT(p) FROM Producto p GROUP BY p.categoria HAVING COUNT(p) > :minimo")
List<Object[]> obtenerCategoriasConMinimoProductos(@Param("minimo") Long minimo);
Consultas con subconsultas
JPQL permite subconsultas para lógica más compleja:
@Query("SELECT p FROM Producto p WHERE p.precio > " +
"(SELECT AVG(p2.precio) FROM Producto p2 WHERE p2.categoria = p.categoria)")
List<Producto> findProductosConPrecioSuperiorALaMedia();
@Query("SELECT p FROM Producto p WHERE p.id IN " +
"(SELECT v.producto.id FROM Venta v WHERE v.fecha > :fecha)")
List<Producto> findProductosVendidosDespuesDe(@Param("fecha") LocalDateTime fecha);
Ventajas de usar @Query con JPQL
El uso de @Query con JPQL ofrece varios beneficios importantes. Proporciona flexibilidad total para escribir consultas complejas que serían imposibles con consultas derivadas. Mantiene la portabilidad entre diferentes bases de datos al usar JPQL en lugar de SQL nativo. Ofrece type safety al trabajar con entidades y sus propiedades, y permite optimización mediante proyecciones y consultas específicas.
Esta aproximación resulta ideal cuando necesitamos consultas que van más allá de las capacidades de las consultas derivadas, manteniendo al mismo tiempo la elegancia y simplicidad del ecosistema Spring Data JPA.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en SpringBoot
Documentación oficial de SpringBoot
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, SpringBoot es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de SpringBoot
Explora más contenido relacionado con SpringBoot y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender la sintaxis y uso básico de JPQL en Spring Data JPA.
- Aprender a definir consultas personalizadas con @Query y manejar parámetros nombrados y posicionales.
- Implementar proyecciones y consultas con JOIN para optimizar la recuperación de datos.
- Realizar consultas de modificación con @Modifying y gestionar transacciones adecuadamente.
- Aplicar paginación, ordenación, funciones agregadas y subconsultas en JPQL para consultas complejas.