Jpa Criteria Api
Guía de la Criteria API en Jakarta EE / Spring Data JPA
Nota: Esta documentación está en español y cubre los conceptos fundamentales de la Criteria API:
CriteriaBuilder,RootyCriteriaQuery.
¿Qué es la Criteria API?
La Criteria API permite construir consultas a la base de datos de forma programática usando Java puro. A diferencia de JPQL (que usa cadenas de texto), las consultas con Criteria API son:
- ✅ Typesafe → los errores se detectan en tiempo de compilación
- ✅ Portables → funcionan independientemente del motor de base de datos
- ✅ Dinámicas → se pueden construir condiciones en tiempo de ejecución
Los tres pilares de la Criteria API
EntityManager
│
├──► CriteriaBuilder → fábrica de objetos de consulta
│ │
│ └──► CriteriaQuery<T> → define la estructura de la consulta
│ │
│ └──► Root<T> → punto de partida (FROM)
│
└──► TypedQuery<T> → ejecuta la consulta
1. CriteriaBuilder
¿Qué es?
Es la fábrica principal. Se obtiene desde el EntityManager y sirve para crear todos los demás componentes de la consulta.
¿Cómo se obtiene?
EntityManager em = ...; // inyectado por Spring o JPA
CriteriaBuilder cb = em.getCriteriaBuilder();
Métodos principales
Crear una consulta
CriteriaQuery<Libro> cq = cb.createQuery(Libro.class);
Comparaciones
| Método | Descripción |
|---|---|
cb.equal(expr, valor) |
campo = valor |
cb.notEqual(expr, valor) |
campo != valor |
cb.like(expr, patron) |
campo LIKE 'patron' |
cb.gt(expr, valor) |
campo > valor |
cb.lt(expr, valor) |
campo < valor |
cb.ge(expr, valor) |
campo >= valor |
cb.le(expr, valor) |
campo <= valor |
cb.between(expr, v1, v2) |
campo BETWEEN v1 AND v2 |
Operadores lógicos (componer predicados)
| Método | Descripción |
|---|---|
cb.and(p1, p2) |
p1 AND p2 |
cb.or(p1, p2) |
p1 OR p2 |
cb.not(p) |
NOT p |
Ordenamiento
cb.asc(expresion) // ORDER BY campo ASC
cb.desc(expresion) // ORDER BY campo DESC
2. CriteriaQuery<T>
¿Qué es? Define la estructura completa de la consulta: qué entidad se consulta, qué se selecciona, qué condiciones aplican y cómo se ordenan o agrupan los resultados.
¿Cómo se crea?
CriteriaQuery<Libro> cq = cb.createQuery(Libro.class);
El tipo genérico <T> indica el tipo del resultado que devolverá la consulta.
Métodos principales
from() — define el FROM
Root<Libro> libro = cq.from(Libro.class);
select() — define el SELECT
cq.select(libro); // SELECT l FROM Libro l
cq.select(libro.get("titulo")); // SELECT l.titulo FROM Libro l
where() — define el WHERE
cq.where(cb.equal(libro.get("autor"), "García Márquez"));
Se pueden pasar múltiples predicados (se combinan con AND):
cq.where(predicado1, predicado2, predicado3);
orderBy() — define el ORDER BY
cq.orderBy(cb.asc(libro.get("titulo")));
cq.orderBy(cb.desc(libro.get("anio")), cb.asc(libro.get("titulo")));
groupBy() y having() — agrupación
cq.groupBy(libro.get("genero"));
cq.having(cb.gt(cb.count(libro), 5L)); // solo géneros con más de 5 libros
3. Root<T>
¿Qué es?
Representa la entidad raíz de la consulta, equivalente al alias en el FROM de JPQL (FROM Libro l → l sería el Root). Desde el Root se navega hacia los atributos y entidades relacionadas.
¿Cómo se obtiene?
Root<Libro> libro = cq.from(Libro.class);
Navegar atributos con get()
libro.get("titulo") // accede al campo "titulo" del Libro
libro.get("autor") // accede al campo "autor"
Con metamodelo estático (tipado seguro):
libro.get(Libro_.titulo) // usando clase metamodelo generada
Joins — navegar relaciones
Cuando la consulta necesita acceder a una entidad relacionada:
// JOIN simple
Join<Libro, Autor> autor = libro.join("autores");
// JOIN encadenado
Join<Autor, Direccion> dir = libro.join("autores").join("direccion");
Tipos de join disponibles: JOIN (inner por defecto), LEFT JOIN, RIGHT JOIN:
libro.join("autores", JoinType.LEFT);
Flujo completo: construir y ejecutar una consulta
Paso a paso
// 1. Obtener CriteriaBuilder
CriteriaBuilder cb = em.getCriteriaBuilder();
// 2. Crear CriteriaQuery con el tipo de resultado
CriteriaQuery<Libro> cq = cb.createQuery(Libro.class);
// 3. Definir la entidad raíz (FROM)
Root<Libro> libro = cq.from(Libro.class);
// 4. Construir predicados (condiciones)
Predicate porAutor = cb.equal(libro.get("autor"), "Borges");
Predicate porTitulo = cb.like(libro.get("titulo"), "%laberinto%");
// 5. Aplicar SELECT, WHERE, ORDER BY
cq.select(libro)
.where(cb.and(porAutor, porTitulo))
.orderBy(cb.asc(libro.get("anio")));
// 6. Crear TypedQuery y ejecutar
TypedQuery<Libro> query = em.createQuery(cq);
List<Libro> resultados = query.getResultList(); // varios resultados
// Libro unico = query.getSingleResult(); // un solo resultado
Predicados dinámicos (consultas opcionales)
Un caso muy común es construir filtros que solo se aplican si el parámetro viene con valor:
List<Libro> buscar(String autor, String titulo) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Libro> cq = cb.createQuery(Libro.class);
Root<Libro> libro = cq.from(Libro.class);
List<Predicate> predicados = new ArrayList<>();
if (autor != null) {
predicados.add(cb.equal(libro.get("autor"), autor));
}
if (titulo != null) {
predicados.add(cb.like(libro.get("titulo"), "%" + titulo + "%"));
}
cq.where(predicados.toArray(new Predicate[0]));
return em.createQuery(cq).getResultList();
}
Uso en Spring: @Repository
Spring permite inyectar el EntityManager directamente en un bean @Repository:
@Repository
class LibroRepository {
private final EntityManager em;
LibroRepository(EntityManager em) {
this.em = em;
}
List<Libro> findByAutorYTitulo(String autor, String titulo) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Libro> cq = cb.createQuery(Libro.class);
Root<Libro> libro = cq.from(Libro.class);
cq.where(
cb.equal(libro.get("autor"), autor),
cb.like(libro.get("titulo"), "%" + titulo + "%")
);
return em.createQuery(cq).getResultList();
}
}
Spring activa la traducción automática de excepciones JPA gracias a
@Repository.
JPA Specifications (Spring Data)
Spring Data ofrece la interfaz Specification<T> para encapsular predicados reutilizables:
// Definir especificaciones reutilizables
static Specification<Libro> tieneAutor(String autor) {
return (libro, cq, cb) -> cb.equal(libro.get("autor"), autor);
}
static Specification<Libro> tituloContiene(String texto) {
return (libro, cq, cb) -> cb.like(libro.get("titulo"), "%" + texto + "%");
}
El repositorio debe extender JpaSpecificationExecutor<T>:
interface LibroRepository extends JpaRepository<Libro, Long>,
JpaSpecificationExecutor<Libro> {}
Uso combinado con operadores lógicos:
// Solo por autor
libroRepository.findAll(tieneAutor("Borges"));
// Autor AND titulo
libroRepository.findAll(
where(tieneAutor("Borges")).and(tituloContiene("laberinto"))
);
Resumen visual
CriteriaBuilder ──────────────────────────────────────────
│ │
├── createQuery(Clase.class) ──► CriteriaQuery<T> │
│ │ │
│ from() ├──► Root<T> │
│ select()│ │
│ where() │ │
│ orderBy()│ │
│ groupBy()│ │
│ │ │
└── Predicados, Order, etc. ◄────────────┘ │
│
EntityManager.createQuery(cq) ──► TypedQuery<T> │
│ │
getResultList() │
getSingleResult() │
Documentación elaborada a partir del Jakarta EE Tutorial y la guía de Spring Data JPA.