Consultas JPQL con @Query en Spring Data JPA

Avanzado
SpringBoot
SpringBoot
Actualizado: 13/06/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

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.

¿Te está gustando esta lección?

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

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.

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.

Completa SpringBoot y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración