Tutorial de JPA 2 Criteria API

10/06/2016

jpa-mini-logo

Introducción

Cuando se introdujo JPA 2 en el 2009, incluía la nueva criteria API. El propósito de este API era alejarse del uso de los strings JQL (JPA Query Language) en tu código. Aunque JQL parece una buena idea para “apalancarte” en tus conocimientos de SQL, en el mundo de la OO (Orientación a Objetos) tiene una grave desventaja: no existe la comprobación de las query strings en tiempo de compilación. La primera vez que tienes constancia de algo mal escrito o sintácticamente incorrecto en tu query string es en tiempo de ejecución. Esto puede hacer que baje mucho el rendimiento si los desarrolladores tienen que corregir, compilar y redesplegar para continuar.

Los test unitarios pueden ayudar a solucionar este problema pero un área donde no ayudan los test unitarios es en la refactorización. La mayoría de las herramientas de refactorización no se llevan bien con las cadenas de texto y acabas teniendo que ejecutar de nuevo los test y teniendo que corregir manualmente cada string en cada iteración de test hasta que todo está correcto.

Con la JPA Criteria API es posible tener consultas type safe (con comprobación de tipado) que se comprueben en tiempo de ejecución y que permitan una refactorización mucho más eficiente.

JPA Criteria API

Existe un precio que pagar para tener estas comprobaciones estáticas. Primero, tienes que generar una serie de clases de Meta Modelo que describen los campos de tus entidades; afortunadamente esto se consigue añadiendo un paso al proceso de construcción MAVEN. El segundo precio es lo tremendamente “especificativo” y poco intuitivo inicialmente que es el API.

Cómo usar la JPA Criteria API

La Criteria API puede parecer bastante difícil al principio pero no es tan mala una vez que te haces con la forma en la que está diseñada. Existen dos objetos principales que utilizaremos para crear un consulta, el objeto CriteriaBuilder y el objeto CriteriaQuery. El primer paso es hacerse con un objeto CriteriaBuilder para luego crear un objeto CriteriaQuery. Esto lo conseguimos con el siguiente código, donde em es un objeto EntityManager:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cqry = em.createQuery();

A partir de un objeto CriteriaQuery crearemos los cuatro componentes de una consulta:

Componente Descripción Cómo crearlo
Select Los objetos o campos de objeto que queremos devolver. Prácticamente equivalente a la parte Select de una query JQL. Aquí se pueden hacer agregaciones y devolver campos de los objetos pero en este breve tutorial solamente devolveremos objetos. CriteriaQuery.select
From Aquí indicamos que entidad (tabla) estamos consultando. También podemos seguir las relaciones con otras entidades que formen parte de la entidad raíz, esto es, “joins” y también SubSelects. CriteriaQuery.from
Where Aquí es donde se estipulan los criterios a aplicar a las entidades que se están consultando. Creamos “Predicates” (Predicados) que se usarán para construir la cláusula “where”. CriteriaQuery.where

CriteriaBuilder.equals
CriteriaBuilder.lessThanOrEquals

Order By Si queremos especificar el orden que deben tener los objetos devueltos, lo haremos aquí. CriteriaQuery.orderBy
Group By Para realizar agregaciones especificamos aquí los campos del objetos o los objetos. CriteriaQuery.groupBy

En la práctica casi cualquier método de CriteriaQuery y CriteriaBuilder recibe un objeto que implementa la interfaz java.persistence.criteria.Expression, Selection o Predicate, lo cual no ayuda mucho a decidir qué poner ahí cuando estamos empezando; especialmente cuando hay un montón de objetos que implementan ambos interfaces y la interfaz Expresssion hereda de la interfaz Selection.

Realmente no tiene mucho sentido que un objeto Path se pueda usar en el método where. En cualquier caso, no prestes demasiada atención es estos interfaces de alto nivel y céntrate en aprender el proceso de creación de una CriteriaQuery, al menos al principio.

El modo más sencillo de entender la API consiste en separar las tareas de crear una consulta en los siguientes pasos:

  1. Crea los objetos CriteriaBuilder y CriteriaQuery
  2. Configura tu cláusula from
  3. Configura tu cláusula select
  4. Configura tus criterios o predicates
  5. Configura tu cláusula where usando tus predicates
  6. Ejecuta la query

Un ejemplo sencillo de CriteriaQuery

Supongamos que tenemos un objeto sencillo llamado MyEntity que tiene los siguientes campos:

  • dateCreated – Date
  • age – Integer

Este sería un ejemplo sencillo de CriteriaQuery:

//Siempre igual
CriteriaBuilder cb = em.getCriteriaBuilder(); //Paso 1
CriteriaQuery cqry = em.createQuery(); //Paso 1

//Las cosas interesantes de hacen aquí
Root<MyEntity> root = cqry.from(MyEntity.class); //Paso 2
cqry.select(root); //Paso 3

//Siempre igual
Query qry = em.createQuery(cqry); //Paso 6
List<MyEnity> results = qry.getResultList(); //Paso 6

Tenemos dos secciones en las que el código siempre se hace igual, la sección 1 y la sección 6. La sección 1 crear los objetos Criteria que necesitamos y la sección 6, finalmente, ejecuta la consulta.

El trabajo principal ocurre en el medio, pasos 2 – 5, donde construimos la consulta. El sencillo ejemplo de arriba devolverá todas la entidades de la tabla mapeada con la clase MyEntity. Hemos tenido que decirle a la consulta de que entidad estábamos buscando mediante el método “from“, para después indicarle qué queríamos retornar de dicha consulta mediante el método “select“.

Usando nuestro método paso a paso podemos empezar a construir consultas más complicadas. Supongamos que queremos utilizar algún criterio en nuestra consulta. Para hacer esto tenemos que rellenar el método “where” del objeto CriteriaQuery con objetos “Predicate“.

Generalmente los objetos Predicate se crean usando uno de los métodos de la clase CriteriaBuilder, aunque la interfaz Expression también crear algunos Predicates, pero por ahora piensa solamente en la clase CriteriaBuilder para hacer esto. Así que digamos que queremos buscar todos los objetos MyEntity con age > 10.

Root<MyEntity> root = cqry.from(MyEntity.class); //Paso 2

cqry.select(root); //Paso 3
Predicate pGtAge = cb.gt(root.get("age"),10); //Paso 4
cqry.where(pGtAge); //Paso 5

Cuando creamos un predicado, debemos indicar al API contra qué campo de qué objeto estamos comparando. Podemos tener más de una entidad contra la que estamos haciendo la consulta, por ejemplo con una join (cruce), de modo que se necesita una referencia a la entidad consultada.

Lo que utilizamos aquí es el objeto devuelto por el método “from” de más arriba. Luego utilizamos el método “get” para especificar el campo del objeto de entidad contra el que queremos comparar. Si, el API es, desafortunadamente, tremendamente “especificativo”.

Si quisiéramos concatenar juntos más de un criterio, digamos que age > 10 y dateCreated > “2011-07-01” entonces crearíamos dos Predicates y los uniríamos con “and” del siguiente modo:

//asumamos que hemos creado un objeto Date llamado date con la fecha 2011-07-01

Root<MyEnity> root = cqry.from(MyEntity.class); //Paso 2
cqry.select(root); //Paso 3
Predicate pGtAge = cb.gt(root.get("age"),10); //Paso 4
Predicate pGtDateCreated=
cb.greaterThan(root.get("dateCreated"),date); //Paso 4
Predicate pAnd = cb.and(pGtDateCreated,pGtAge); //Paso 4

cqry.where(pAnd); //Paso 5

Usando clases Meta Model en la Query

Te preguntará porqué estamos usando strings en nuestros objetos “Predicate“. Al fin y al cabo ¿el objetivo de todo esto no era evitar el JQL? ¿Tiene las CriteriaQueries las mismas deficiencias que las consultas JQL?

La respuesta a esas preguntas está en utilizar las clases del Meta Model. Una vez que las clases han sido generadas nos podemos referir a los campos de los objetos Entity utilizando las clases del Meta Model. La clase del Meta Model tiene el mismo nombre que la clase Entity con un guión bajo (_) añadido. Así que si estamos comparando con el campo age, usaríamos MyEntity_.age. La consulta de más arriba reescrita usando clases Meta Model quedaría así:

//asumamos que hemos creado un objeto Date llamado date con la fecha 2011-07-01

Root<MyEnity> root = cqry.from(MyEntity.class); //Paso 2
cqry.select(root); //Step 3

Predicate pGtAge = cb.gt(root.get(MyEntity_.age),10); //Paso 4
Predicate pGtDateCreated=
cb.greaterThan(root.get(MyEntity_.dateCreated),date); //Paso 4
Predicate pAnd = cb.and(pGtDateCreated,pGtAge); //Paso 4

cqry.where(pAnd); //Step 5

CriteriaQuery usando Join

¿Qué ocurre si queremos realizar una consulta entre entidades, es decir, hacer una consulta “join“? Digamos que tenemos otro objeto Entity llamado AnotherEntity con los siguientes campos:

  • name – String
  • enabled – boolean

Además extenderemos el objeto MyEntity para que tenga una referencia al objeto AnotherEntity. Así, MyEntity quedaría como:

  • dateCreated – Date
  • age – Integer
  • anotherEntity – AnotherEntity

Ahora digamos que queremos consultar todos los objetos MyEntity que tienen un objeto AnotherEntity deshabilitado. Lo haríamos así:

Root<MyEnity> root = cqry.from(MyEntity.class); //Paso 2
Join<MyEntity,AnotherEntity> join =
root.join(MyEntity_.anotherEntity); //Paso 2
//Join<MyEntity,AnotherEntity> join =
// root.join("anotherEntity"); //Paso 2

cqry.select(root); //Paso 3
Predicate pGtAge = cb.gt(root.get(MyEntity_.age),10); //Paso 4
Predicate pGtDateCreated=
cb.greaterThan(root.get(MyEntity_.dateCreated),date); //paso 4
Predicate pEqEnabled = cb.equals(join.get(AnotherEntity_.enabled),false);
Predicate pAnd = cb.and(pGtDateCreated,pGtAge,pEqEnabled); //Paso 4

cqry.where(pAnd); //Paso 5

Como puedes ver nuestra paso “from” se complica al especificar con qué hacer el join. Como con los Predicate, podemos utilizar String o las clases del Meta Model para especificar el campo con el que queremos hace el join.

Cláusula Select más compleja

Veamos ahora cómo hacer una cláusula select más compleja, nuestro paso 2. Digamos que solamente estamos interesados en el campo dateCreated de nuestro objeto MyEntity. Nuestro código sería algo así:

Root<MyEnity> root = cqry.from(MyEntity.class); //Paso 2
cqry.select(root.get(MyEntity_.dateCreated)); //Paso 3

Si quisiéramos usar una función de agregación y recuperar la dateCreated mínima podríamos hacer algo como:

Root<MyEnity> root = cqry.from(MyEntity.class); //Paso 2
Expression min =
cb.min(root.get(MyEntity_.dateCreated));//Paso 3
cqry.select(min); //Paso 3

vía

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: