🌿 JPQL (Java Persistence Query Language) Cheatsheet Completo 🌿
JPQL es el lenguaje de consulta orientado a objetos definido como parte de la especificación Java Persistence API (JPA). Permite escribir consultas que operan sobre entidades y sus relaciones, no sobre tablas y columnas de base de datos directamente. Es similar a SQL en su sintaxis, pero opera a un nivel más alto de abstracción.
1. 🌟 Conceptos Clave
- Orientado a Objetos: Las consultas se escriben utilizando los nombres de las entidades, sus campos y relaciones, tal como están definidos en el modelo de objetos Java.
- Independencia de la Base de Datos: Como opera sobre entidades, las consultas JPQL son portables a través de diferentes bases de datos subyacentes (MySQL, PostgreSQL, Oracle, etc.), siempre que tu proveedor JPA (ej. Hibernate) soporte esa base de datos.
- Resultados Tipados: Las consultas JPQL devuelven objetos Java (entidades o DTOs), no
ResultSets. - No es SQL Nativo: Aunque similar en sintaxis, JPQL no es SQL. No se puede usar para DDL (CREATE TABLE) o DCL (GRANT). Se enfoca en DQL (SELECT) y DML básico (UPDATE, DELETE).
EntityManager: La interfaz central de JPA para realizar operaciones de persistencia, incluyendo la creación y ejecución de consultas JPQL.
2. 🛠️ Sintaxis Básica JPQL
JPQL es un lenguaje de consulta fuertemente tipado. Las palabras clave no distinguen entre mayúsculas y minúsculas (aunque se usa mayúsculas por convención).
2.1. Cláusulas Básicas
SELECT: Define qué se va a recuperar (entidades, campos, agregaciones).FROM: Declara las entidades sobre las que se va a consultar. ¡Siempre usa el nombre de la Entidad (clase), no el nombre de la tabla! Se asigna un alias a la entidad.WHERE: Filtra los resultados basándose en condiciones.ORDER BY: Ordena los resultados.GROUP BY: Agrupa los resultados basándose en columnas.HAVING: Filtra los grupos.
2.2. Ejemplo de Entidad
// src/main/java/com/example/model/Product.java
import jakarta.persistence.*; // o javax.persistence.*
@Entity // Nombre por defecto es el nombre de la clase: Product
@Table(name = "productos")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double price;
private Boolean active;
@ManyToOne
@JoinColumn(name = "category_id")
private Category category; // Relación con Category
// Getters y Setters
}
// src/main/java/com/example/model/Category.java
import jakarta.persistence.*; // o javax.persistence.*
@Entity
@Table(name = "categorias")
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// Getters y Setters
}
3. 🚀 Consultas SELECT
3.1. Seleccionar Entidades Completas
- Obtener todos los productos:
SELECT p FROM Product p - Obtener productos activos:
SELECT p FROM Product p WHERE p.active = TRUE - Obtener productos por nombre (case-insensitive):
SELECT p FROM Product p WHERE LOWER(p.name) LIKE :productName:productNamees un parámetro con nombre.
3.2. Seleccionar Campos Específicos (Projections)
- Devuelve una lista de arrays de objetos o un objeto
Tuple(si se usacreateTupleQuery).SELECT p.name, p.price FROM Product p - Construir DTOs (Constructor Expression):
- El DTO debe tener un constructor que coincida con los tipos y el orden de las columnas seleccionadas.
// public class ProductDto { // public ProductDto(Long id, String name, Double price) { ... } // }SELECT NEW com.example.dto.ProductDto(p.id, p.name, p.price) FROM Product p
3.3. Funciones de Agregación
COUNT,SUM,AVG,MIN,MAX.SELECT COUNT(p) FROM Product p SELECT SUM(p.price) FROM Product p WHERE p.active = TRUE SELECT AVG(p.price) FROM Product p
3.4. GROUP BY y HAVING
- Agrupa por un campo y filtra grupos.
SELECT p.category.name, AVG(p.price) FROM Product p GROUP BY p.category.name HAVING COUNT(p.id) > 5
3.5. Ordenación (ORDER BY)
ASC(ascendente, por defecto),DESC(descendente).SELECT p FROM Product p ORDER BY p.price DESC, p.name ASC
3.6. Paginación (setFirstResult, setMaxResults)
- No son cláusulas de JPQL, sino métodos de
Query.// En Java (usando EntityManager) // Query query = entityManager.createQuery("SELECT p FROM Product p", Product.class); // query.setFirstResult(10); // Offset: saltar los primeros 10 resultados // query.setMaxResults(5); // Limit: obtener solo 5 resultados // List<Product> products = query.getResultList();
4. 🔄 JOINS (Combinación de Entidades)
INNER JOIN: Incluye solo las filas donde hay una coincidencia en ambas entidades.JOINes un sinónimo deINNER JOIN.
SELECT p FROM Product p JOIN p.category c WHERE c.name = 'Electronics'LEFT JOIN(oLEFT OUTER JOIN): Incluye todas las filas de la entidad izquierda y las filas coincidentes de la derecha. Si no hay coincidencia, los campos de la derecha seránNULL.SELECT u FROM User u LEFT JOIN u.orders oRIGHT JOIN(oRIGHT OUTER JOIN): Incluye todas las filas de la entidad derecha.FETCH JOIN: Para evitar el problema “N+1 query” al cargar relacionesLAZY. Carga eager una relación junto con la entidad principal. No modifica el tipo de resultado de la consulta.SELECT p FROM Product p JOIN FETCH p.category SELECT u FROM User u LEFT JOIN FETCH u.addresses
5. 🪆 Subconsultas
JPQL soporta subconsultas en la cláusula WHERE y HAVING.
- Con
IN/NOT IN:SELECT p FROM Product p WHERE p.category.id IN (SELECT c.id FROM Category c WHERE c.name LIKE 'Elec%') - Con
EXISTS/NOT EXISTS:SELECT u FROM User u WHERE EXISTS (SELECT o FROM Order o WHERE o.user = u AND o.total > 100) - Con
ALL/ANY/SOME:SELECT p FROM Product p WHERE p.price > ALL (SELECT p2.price FROM Product p2 WHERE p2.category.name = 'Books')
6. 📝 Sentencias DML (Modificación de Datos)
JPQL también permite operaciones básicas de UPDATE y DELETE. No soporta INSERT.
UPDATE:UPDATE Product p SET p.price = p.price * 1.10 WHERE p.active = TRUEDELETE:DELETE FROM Product p WHERE p.active = FALSE ```* **Importante**: Las operaciones DML de JPQL operan directamente en la base de datos sin pasar por el contexto de persistencia. Los objetos que ya están en el `EntityManager` pueden no reflejar los cambios hasta que el `EntityManager` se vacíe o se refresque.
7. 🔑 Parámetros de Consulta
Para pasar valores dinámicos a las consultas. Protege contra la inyección SQL.
- Parámetros Posicionales (
?n):SELECT p FROM Product p WHERE p.name = ?1 AND p.price < ?2- En Java:
query.setParameter(1, "Laptop"); query.setParameter(2, 1000.00);
- En Java:
- Parámetros con Nombre (
:name): ¡Preferido por su legibilidad!SELECT p FROM Product p WHERE p.name = :productName AND p.price < :maxPrice- En Java:
query.setParameter("productName", "Laptop"); query.setParameter("maxPrice", 1000.00);
- En Java:
8. 🌐 Funciones JPQL Comunes
JPQL tiene un conjunto limitado de funciones incorporadas, a menudo mapeadas a funciones de SQL.
- String Functions:
CONCAT(s1, s2),SUBSTRING(s, start, len),LENGTH(s),LOCATE(s1, s2),LOWER(s),UPPER(s),TRIM(s). - Numeric Functions:
ABS(n),SQRT(n),MOD(n, m). - Date/Time Functions:
CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP. (Las funciones de manipulación de fechas son limitadas y suelen depender del proveedor JPA). - Type Conversion:
CAST(expression AS type). - Condicionales:
CASE WHEN condition THEN result ELSE result END. - Literales:
- Cadenas:
'text' - Números:
123,3.14 - Booleanos:
TRUE,FALSE - Nulos:
NULL
- Cadenas:
TYPE(entity_alias): Devuelve el tipo de entidad (para jerarquías de herencia).
9. 💡 Buenas Prácticas y Consejos
- Nombres de Entidades y Campos, NO Tablas y Columnas: Siempre usa los nombres de las clases y propiedades de Java en tu JPQL, no los nombres de las tablas y columnas de la base de datos.
FROM EntityName alias: Siempre declara un alias para cada entidad en la cláusulaFROM.- Preferencia a Parámetros con Nombre: Son más legibles y menos propensos a errores que los posicionales.
- Usa
FETCH JOINcon Cuidado:FETCH JOINes potente para resolver el problema N+1, pero puede crear un producto cartesiano si hay múltiples coleccionesEAGERo si no se maneja correctamente. No usarDISTINCTenSELECTsi usasJOIN FETCHen el mismo JPQL para coleccionesToManycargadasEAGERLYsi la idea es obtener la entidad principal sin duplicados. Para eso, es mejor cargar la entidad principal y luego inicializar la colección en un método aparte (Hibernate.initialize()). - Evita Consultas JPQL Demasiado Largas o Complejas: Si una consulta se vuelve muy compleja, considera:
- Dividirla en varias consultas más pequeñas.
- Realizar parte de la lógica en Java (después de obtener datos).
- Crear una Vista en la base de datos.
- Usar Criteria API (más programático, para consultas dinámicas).
- Usar SQL nativo (
@NamedNativeQueryocreateNativeQuery) si es una consulta muy específica de la base de datos o si no se puede expresar en JPQL.
@NamedQuery: Define consultas JPQL una vez en la entidad y refiérelas por nombre. Útil para la reutilización y para que las consultas se validen en tiempo de compilación.- Optimiza con
EXPLAIN: Si tu proveedor JPA lo soporta, puedes usarquery.unwrap(org.hibernate.query.Query.class).getQueryString()para ver el SQL generado y luego usarEXPLAINen tu base de datos para optimizar. - Pruebas: Escribe pruebas unitarias/de integración para tus consultas JPQL para asegurarte de que devuelven los datos esperados y que no hay errores de sintaxis.
Este cheatsheet te proporciona una referencia completa de JPQL, cubriendo sus conceptos esenciales, sintaxis básica, operaciones SELECT/DML, JOINS, subconsultas, funciones y las mejores prácticas para escribir consultas orientadas a objetos eficientes y portátiles en tus aplicaciones JPA.