martes, 5 de enero de 2016

JPA - API Criteria

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 JPQL

Criteria 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 CriteriaBuilder que se puede obtener desde el interfaz EntityManager a través del método getCriteriaBuilder().

Ten presente la jerarquía de interfaces que te mostramos a continuación:

Jerarquía de principales interfaces utilizados en el Api Criteria

CriteriaBuilder 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 Tuple se 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 CriteriaQuery

createQuery(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 definirlo

Tabla que relaciona la funcionalidad ofrecida por JPQL con la de 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 AbstractQuery proporciona 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 WHERE

En 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 únicas

Se 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ón

Jerarquía de principales interfaces para construir cláusula Select

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últiples

Cuando 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:

Jerarquía de principales interfaces para construir cláusula From

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, funciones y funciones agregadas en las enlaces asociados.

Parámetros
El 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 condicional
El 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));
  
Predicados
A 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 CriteriaBuilder
El 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 CriteriaBuilder
Utilizan los mismos métodos para construir las cláusulas FROM y WHERE que las sentecias SELECT
Los 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 CriteriaBuilder
Utilizan los mismos métodos para construir las cláusulas FROM y WHERE que las sentecias SELECT
Los 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")); 

3 comentarios:

  1. Muy bueno me sirvió mucho! Saludos!!!

    ResponderEliminar
    Respuestas
    1. Buenas tardes Dario. Antes de nada, te queremos dar las gracias por compartir tu valoración. Esperamos que el resto de artículos te parezcan igual de interesantes. Si quieres proponer algún tema que te interese en particular lo tendremos en cuenta para futuras publicaciones.
      También recordarte que si te interesa puedes recibir notificación por correo electrónico de cada nueva publicación si te suscribes a nuestro boletín de novedades https://feedburner.google.com/fb/a/mailverify?uri=AprendeJavaEnEspaol&loc=es_ES

      Eliminar
  2. Lo primero es darte las gracias por el artículo, está muy bien explicado el uso de este API, cosa que no es fácil hacer porque, en mi opinión, es muy farragoso.
    Sin embargo, echo en falta algo que creo que es fundamental, ¿como obtenemos los datos seleccionados?. Los métodos select y where devuelven un nuevo CriteriaQuery pero, ¿que hacemos a partir de ahí?, no veo que esta interfaz disponga de métodos para recuperar las entidades consultadas.
    Muchas gracias

    ResponderEliminar

Entradas populares