Hibernate: API Criteria
Descubre cómo usar la API Criteria de Hibernate para construir consultas dinámicas y seguras en Java con JPA Criteria API y joins avanzados.
Aprende Hibernate GRATIS y certifícateAPI Criteria de Hibernate
La API Criteria representa una de las características más versátiles de Hibernate para la construcción de consultas dinámicas de forma programática. A diferencia de las consultas HQL o SQL nativas que se escriben como cadenas de texto, la API Criteria permite crear consultas utilizando métodos Java, proporcionando type safety y facilitando la construcción de consultas complejas en tiempo de ejecución.
Fundamentos de la API Criteria
La API Criteria se basa en el estándar JPA Criteria API, que Hibernate implementa y extiende con funcionalidades adicionales. Esta aproximación programática resulta especialmente útil cuando necesitamos construir consultas que varían según condiciones específicas del negocio o parámetros de entrada del usuario.
El punto de entrada principal es la interfaz CriteriaBuilder
, que obtenemos a través del EntityManager
. Esta interfaz proporciona métodos para crear diferentes tipos de consultas y expresiones:
EntityManager em = entityManagerFactory.createEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
Construcción básica de consultas
Para crear una consulta Criteria, necesitamos definir tres elementos fundamentales: el CriteriaQuery, el Root y opcionalmente las condiciones de filtrado. El CriteriaQuery
representa la consulta en sí, mientras que el Root
define la entidad principal sobre la que realizaremos la consulta.
// Consulta básica para obtener todos los usuarios
CriteriaQuery<Usuario> query = cb.createQuery(Usuario.class);
Root<Usuario> root = query.from(Usuario.class);
query.select(root);
List<Usuario> usuarios = em.createQuery(query).getResultList();
La construcción de predicados (condiciones WHERE) se realiza mediante métodos del CriteriaBuilder
. Estos predicados pueden combinarse usando operadores lógicos para crear condiciones complejas:
// Consulta con condiciones
CriteriaQuery<Usuario> query = cb.createQuery(Usuario.class);
Root<Usuario> root = query.from(Usuario.class);
Predicate nombrePredicate = cb.equal(root.get("nombre"), "Juan");
Predicate edadPredicate = cb.greaterThan(root.get("edad"), 18);
Predicate condicionFinal = cb.and(nombrePredicate, edadPredicate);
query.select(root).where(condicionFinal);
Trabajando con atributos y expresiones
La API Criteria utiliza metamodelos para referenciar atributos de las entidades de forma type-safe. Aunque podemos usar cadenas de texto para referenciar atributos, el uso de metamodelos generados automáticamente previene errores en tiempo de compilación:
// Usando cadenas (menos seguro)
root.get("nombre")
// Usando metamodelo (más seguro)
root.get(Usuario_.nombre)
Las expresiones en Criteria API permiten realizar operaciones sobre los atributos de las entidades. Podemos crear expresiones para cálculos matemáticos, manipulación de cadenas y funciones de agregación:
// Expresión para concatenar campos
Expression<String> nombreCompleto = cb.concat(
cb.concat(root.get("nombre"), " "),
root.get("apellido")
);
// Expresión para cálculos matemáticos
Expression<Double> precioConIva = cb.prod(root.get("precio"), 1.21);
Joins y relaciones
La API Criteria maneja las relaciones entre entidades mediante joins explícitos. Esto proporciona control total sobre cómo se realizan las uniones y permite optimizar las consultas según las necesidades específicas:
CriteriaQuery<Pedido> query = cb.createQuery(Pedido.class);
Root<Pedido> pedidoRoot = query.from(Pedido.class);
// Join con la entidad Cliente
Join<Pedido, Cliente> clienteJoin = pedidoRoot.join("cliente");
// Condición sobre la entidad relacionada
Predicate clienteActivo = cb.equal(clienteJoin.get("activo"), true);
query.select(pedidoRoot).where(clienteActivo);
Los diferentes tipos de join (INNER, LEFT, RIGHT) se especifican mediante el parámetro JoinType
:
// Left join para incluir pedidos sin cliente asignado
Join<Pedido, Cliente> clienteJoin = pedidoRoot.join("cliente", JoinType.LEFT);
Ordenación y agrupación
La ordenación de resultados se configura mediante el método orderBy()
, que acepta una lista de objetos Order
. Podemos crear ordenaciones ascendentes o descendentes sobre múltiples campos:
CriteriaQuery<Usuario> query = cb.createQuery(Usuario.class);
Root<Usuario> root = query.from(Usuario.class);
query.select(root)
.orderBy(
cb.asc(root.get("apellido")),
cb.desc(root.get("fechaRegistro"))
);
La agrupación se implementa con groupBy()
y permite realizar consultas de agregación. Cuando agrupamos resultados, la cláusula SELECT debe contener únicamente campos incluidos en el GROUP BY o funciones de agregación:
CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
Root<Venta> root = query.from(Venta.class);
query.multiselect(
root.get("categoria"),
cb.sum(root.get("importe"))
).groupBy(root.get("categoria"));
Subconsultas
Las subconsultas en Criteria API se crean mediante el método subquery()
del CriteriaQuery
. Estas subconsultas pueden utilizarse en condiciones WHERE o como parte de expresiones más complejas:
CriteriaQuery<Cliente> queryPrincipal = cb.createQuery(Cliente.class);
Root<Cliente> clienteRoot = queryPrincipal.from(Cliente.class);
// Subconsulta para encontrar clientes con pedidos
Subquery<Long> subquery = queryPrincipal.subquery(Long.class);
Root<Pedido> pedidoRoot = subquery.from(Pedido.class);
subquery.select(pedidoRoot.get("cliente").get("id"))
.where(cb.greaterThan(pedidoRoot.get("total"), 1000));
// Usar la subconsulta en la consulta principal
queryPrincipal.select(clienteRoot)
.where(cb.in(clienteRoot.get("id")).value(subquery));
Funciones de agregación y estadísticas
La API Criteria proporciona métodos para las principales funciones de agregación como COUNT, SUM, AVG, MAX y MIN. Estas funciones resultan especiales para generar informes y estadísticas:
CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
Root<Producto> root = countQuery.from(Producto.class);
countQuery.select(cb.count(root))
.where(cb.equal(root.get("activo"), true));
Long totalProductos = em.createQuery(countQuery).getSingleResult();
Para consultas que devuelven múltiples valores agregados, utilizamos multiselect()
y trabajamos con arrays de objetos o tuplas:
CriteriaQuery<Tuple> query = cb.createTupleQuery();
Root<Venta> root = query.from(Venta.class);
query.multiselect(
cb.count(root).alias("total"),
cb.sum(root.get("importe")).alias("suma"),
cb.avg(root.get("importe")).alias("promedio")
);
Tuple resultado = em.createQuery(query).getSingleResult();
Long total = resultado.get("total", Long.class);
BigDecimal suma = resultado.get("suma", BigDecimal.class);
Construcción dinámica de consultas
Una de las principales ventajas de la API Criteria es la capacidad de construir consultas dinámicamente según condiciones de runtime. Esto resulta especialmente útil para implementar filtros de búsqueda flexibles:
public List<Usuario> buscarUsuarios(String nombre, Integer edadMinima, Boolean activo) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Usuario> query = cb.createQuery(Usuario.class);
Root<Usuario> root = query.from(Usuario.class);
List<Predicate> predicates = new ArrayList<>();
if (nombre != null && !nombre.trim().isEmpty()) {
predicates.add(cb.like(root.get("nombre"), "%" + nombre + "%"));
}
if (edadMinima != null) {
predicates.add(cb.greaterThanOrEqualTo(root.get("edad"), edadMinima));
}
if (activo != null) {
predicates.add(cb.equal(root.get("activo"), activo));
}
query.select(root).where(predicates.toArray(new Predicate[0]));
return em.createQuery(query).getResultList();
}
Esta aproximación permite crear interfaces de búsqueda flexibles donde los usuarios pueden combinar diferentes criterios de filtrado sin necesidad de escribir múltiples consultas estáticas.
Lecciones de este módulo de Hibernate
Lecciones de programación del módulo API Criteria del curso de Hibernate.
Ejercicios de programación en este módulo de Hibernate
Evalúa tus conocimientos en API Criteria con ejercicios de programación API Criteria de tipo Test, Puzzle, Código y Proyecto con VSCode.