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:
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
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 CriteriaSELECT 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
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<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:
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"));
Muy bueno me sirvió mucho! Saludos!!!
ResponderEliminarBuenas 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.
EliminarTambié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
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.
ResponderEliminarSin 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