Revista Informática

JPA - API Criteria

Publicado el 07 enero 2016 por Miguel Miguel Gómez Cuesta @mgomezcuesta

Introducción:

Antes de que los lenguajes como JPQL llegarán a estandarizarse, el método más común para la construcción de consultas era a través de un API de programación.Con la llegada de JPQL los API de construcción de consultas se siguen manteniendo debido a que dan acceso a unos características adicionales que no proporciona todavía JPQLCriteria nos permite construir consultas que estandarizan muchos de las características que existen en aplicaciones con persistencia de datos. Criteria no es simplemente una traducción de JPQL al lenguaje de programación Java
Criteria adopta las mejores prácticas como por ejemplo encadenamiento de métodos y hace un uso completo de las características del lenguaje de programación Java

Creando una Consulta:

El corazón del Api Criteria es el interfaz CriteriaBuilderque se puede obtener desde el interfaz EntityManagera través del método getCriteriaBuilder().Ten presente la jerarquía de interfaces que te mostramos a continuación:JPA - API CriteriaCriteriaBuilder es una factoría que nos permite crear instancias de la interfaz CriteriaQuery(definiciones de consulta). Las definiciones de consultas son la base para la construcción de nuevas consultas. Una instancia de CriteriaQuery es básicamente un caparazón vacío en el que únicamente definimos el tipo de retorno de la consulta, posteriormente veremos como ir definiendo las distintas cláusulas que ya conocemos debe tener una consulta.Existen 3 métodos para la creación de instancias CriteriaQuery:
  • createQuery(Class<T>)Método más común con un parámetro que especifica la clase correspondiente al resultado de la consulta
  •  createQuery() Método sin parámetros que corresponde con una consulta con un resultado de tipo Object
  • createTupleQuery()Método que corresponde con una consulta que devuelve múltiples elementos (la cláusula SELECT tiene varias expresiones) . Es equivalente a createQuery(Tuple). El interfaz Tuplese puede utilizar siempre que se deseen combinar varios elementos en un único tipo de objeto

El siguiente paso será usar el método del interfaz EntityManager para construir la consulta a partir de este.createQuery(CriteriaQuery<T> criteriaQuery) Método que devuelve TypedQuery a partir de CriteriaQuerycreateQuery(java.lang.String qlString) Método que devuelve Query a partir de String que representa una consulta JPQL.
Consejo: Para entender las diferencias entre definición de consultas con Api Criteria vs JPQL podemos pensar que cada instancia de CriteriaQuery es similar a la representación interna de una consulta JPQL. Cada proveedor de persistencia usa la representación interna después de que el String JPQL haya sido parseado

Estructura:

La estructura de las consultas está formada por las mismas cláusulas que en JPQL. Existe una correspondencia entre JPQL y Api Criteria, en la siguiente tabla podemos identificar cada cláusula con el método e interfaz del Api Criteria que necesitaremos para definirloJPA - API Criteria

Conceptos fundamentales:

Origen de consulta (Query Roots):

Un origen de consulta en criteria corresponde con una variable de identificación usada en la cláusula FROM de las consultas JPQL. El interfaz AbstractQueryproporciona el método from() para definir las entidades que formarán parte de la base de nuestra consulta. El método admite una entidad como parámetro y añade un nuevo origen a la consulta
CriteriaQuery<Employee> c = cb.createQuery(Employee.class); Root<Employee> emp = c.from(Employee.class);
El método from() devuelve una instancia de Root correspondiente al tipo de entidad definido. Cada llamada al método from() es aditiva, es decir, cada llamada añade otro origen a la consulta que produce un producto cartesiano si hay varios origen definidos y no se añade ningún filtro en la cláusula WHEREEn el siguiente ejemplo vemos la correspondencia entre consulta JPQL entre varias entidades
SELECT DISTINCT d
FROM Department d, Employee e
WHERE d = e.department
CriteriaQuery<Department> c = cb.createQuery(Department.class);
Root<Department> dept = c.from(Department.class);
Root<Employee> emp = c.from(Employee.class);
c.select(dept).distinct(true).where(cb.equal(dept, emp.get("department")));

Expresiones de ruta (Path expressions):

Son una pieza clave debido a la potencia y flexibilidad tanto en JPQL como en Api Criteria. Podemos ver la correspondencia entre JPQL y las expresiones de ruta en Api Criteria
SELECT e
FROM Employee e
WHERE e.address.city = 'New York'
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp).where(cb.equal(emp.get("address").get("city"), "New York"));

Nota: En Api Criteria es necesario almacenar la instancia Root en una variable local para posteriormente utilizarlo para formar expresiones de ruta en las cláusulas que sea necesario. En el ejemplo anterior se utiliza la expresión de ruta para filtrar los resultados en la cláusula WHERE

CLÁUSULA SELECT

Expresiones únicasSe utiliza el método select(Selection<? extends T> selection) del interfaz CriteriaQuery para construir la cláusula SELECT de una consulta con Criteria.Ten presente la jerarquía de interfaces que te mostramos a continuaciónJPA - API Criteria

Podemos pasar un origen de consulta como parámetro:

CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp);
También podemos pasar al metodo select() una expresión que seleccione un atributo de una entidad o una expresión compatible con el tipo requerido
CriteriaQuery<String> c = cb.createQuery(String.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp.<String>get("name"));

Notas: El tipo de la expresión proporcionada al método select() debe ser compatible con el tipo resultado usado para instanciar el objeto CriteriaQuery.

El método get() devuelve un objeto Path<Object> porque el compilador no puede inferir el tipo basado en la propiedad name, así que, es necesario indicar el tipo de retorno como hemos realizado en el ejemplo anterior.Expresiones múltiplesCuando definimos una cláusula SELECT con múltiples expresiones, debemos hacerlo compatible con el tipo resultado usado para instanciar el objeto CriteriaQuery (definición de consulta)Si definimos el tipo de resultado Tuple el método select() debe recibir un parámetro de tipo CompoundSelection<Tuple>.
Una opción es utilizar el método del interfaz CriteriaBuilder CompoundSelection<Tuple> tuple(Selection<?>... selections)
CriteriaQuery<Tuple> c= cb.createTupleQuery();
Root<Employee> emp = c.from(Employee.class);
c.select(cb.tuple(emp.get("id"), emp.get("name")));

Otra opción es utilizar el método del interfaz CriteriaQuery
CriteriaQuery<T> multiselect(Selection<?>... selections)
CriteriaQuery<Tuple> c = cb.createTupleQuery();
Root<Employee> emp = c.from(Employee.class);
c.multiselect(emp.get("id"), emp.get("name"));

CLÁUSULA FROM:

Join son creados en Criteria utilizando el método join() de la interfaz From.Cualquier origen de consulta (query root) puede ser un join y los join() pueden encadenarse con otro join()
Ten en cuenta la jerarquía de interfaces que se muestra a continuación:JPA - API Criteria
El método join está sobrecargado, y por tanto hay numerosas formas de generar el Join, os vamos a detallar una de las más sencillas de manejar.
<X,Y> Join<X,Y>join(java.lang.String attributeName, JoinType jt)
El método join() tiene dos tipos parametrizados que corresponden con la entidad origen y destino de la unión. Esto nos permite mayor claridad para entender la unión que se está realizando. Además incluye un argumento que indica el tipo de unión (inner, left o right)
Join<Employee,Project> project = emp.join("projects", JoinType.LEFT);

CLÁUSULA WHERE

La cláusula WHERE se establece utilizando el método where() de la interfaz AbstractQuery. El método acepta argumentos con ninguno o varios Predicate o simplemente una Expresion<Booelean>La clave para construir expresiones en Criteria es el interfaz CriteriaBuilder, que contiene métodos para todos predicados, expresiones y funciones soportadas en JPQL y características adicionales
Podemos ver las tabla con la relación que existe entre JPQL y Criteria para construir predicados,  expresiones, funcionesy funciones agregadas en las enlaces asociados.
ParámetrosEl uso de parámetros en Criteria es diferente de JPQL. Es necesario crear explícitamente una instancia del tipo correcto del ParameterExpresion que será usada posteriormente en alguna expresión condicionalEl método parameter() necesita un tipo de clase y un nombre para que el parámetro sea usado posteriormente
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp);
ParameterExpression<String> deptName =cb.parameter(String.class, "deptName");
c.where(cb.equal(emp.get("dept").get("name"), deptName));
PredicadosA continuación vemos un ejemplo de construcción de un Predicate que filtra por varios parámetros.
Predicate criteria = cb.conjunction();
if (name != null) {
ParameterExpression<String> p =
cb.parameter(String.class, "name");
criteria = cb.and(criteria, cb.equal(emp.get("name"), p));
}
if (deptName != null) {
ParameterExpression<String> p =
cb.parameter(String.class, "dept");
criteria = cb.and(criteria,
cb.equal(emp.get("dept").get("name"), p));
}

CLÁUSULA ORDER BY

El método orderBy() de la interfaz CriteriaQuery establece el orden para una definición de consulta criteria. El método acepta uno o varios objetos Order. Estos objetos son creadas gracias a los métodos asc() y desc() de la interfaz CriteriaBuilderEl siguiente ejemplo ordena los resultados por nombre de departamento ascendente y en segundo lugar por nombre de empleado descendente.
CriteriaQuery<Tuple> c = cb.createQuery(Tuple.class);
Root<Employee> emp = c.from(Employee.class);
Join<Employee,Department> dept = emp.join("dept");
c.multiselect(dept.get("name"), emp.get("name"));
c.orderBy(cb.desc(dept.get("name")),cb.asc(emp.get("name")));

CLÁUSULA GROUP BY

Los métodos groupBy() y having() de la interfaz AbstractQuery son equivalentes a JPQL, ambos métodos admiten uno o más expresiones que se usan para agrupar y filtrar datos.
A continuación tenemos la relación entre JPQL y Criteria de una consulta que obtiene los empleados y el número de proyectos en los que está asignado  siempre que cumpla que está asignado a más de un proyecto
SELECT e, COUNT(p)
FROM Employee e JOIN e.projects p
GROUP BY e
HAVING COUNT(p) >= 2
CriteriaQuery<Object[]> c = cb.createQuery(Object[].class);
Root<Employee> emp = c.from(Employee.class);
Join<Employee,Project> project = emp.join("projects");
c.multiselect(emp, cb.count(project)).groupBy(emp).having(cb.ge(cb.count(project),2));

SENTENCIAS UPDATE

UPDATE son creados utilizando el método createCriteriaUpdate() de la interfaz CriteriaBuilderUtilizan los mismos métodos para construir las cláusulas FROM y WHERE que las sentecias SELECTLos métodos específicos se encuentran encapsulados en el interfaz CriteriaUpdate
A continuación vemos la relación entre JPQL y Criteria con un ejemplo
UPDATE Employee e
SET e.salary = e.salary + 5000
CriteriaUpdate<Employee> q = cb.createCriteriaUpdate(Employee.class);
Root<Employee> emp = q.from(Employee.class);
q.set(emp.get("salary"), cb.sum(emp.get("salary"), 5000));

El método set() se utiliza para actualizar el valor de la propiedad especificada

SENTENCIAS DELETE


DELETE son creados utilizando el método createCriteriaDelete() de la interfaz CriteriaBuilderUtilizan los mismos métodos para construir las cláusulas FROM y WHERE que las sentecias SELECTLos métodos específicos se encuentran encapsulados en el interfaz CriteriaDelete
A continuación vemos la relación entre JPQL y Criteria con un ejemplo
DELETE FROM Employee e
WHERE e.department IS NULL
CriteriaDelete q = cb.createCriteriaDelete(Employee.class); 
Root emp = c.from(Employee.class);
q.where(cb.isNull(emp.get("dept"));

Volver a la Portada de Logo Paperblog

Revista