Archive

Posts Tagged ‘criteriaquery’

Creación de consultas dinámicas en JPA sin SQL (ni JPQL): Usando el metamodelo

En la entrada anterior vimos cómo realizar consultas JPA sin escribir código SQL. Sin embargo,  echemos un vistazo a una porción concreta del código que usamos en aquel momento:

  final CriteriaQuery<User> q = cb.createQuery(User.class);
  final Root<User> users = q.from(User.class);
  final Predicate condition = cb.equal(users.get("privilegeLevel"), 5);
  q.select(users).where(condition).orderBy(cb.asc(users.get("userId")));

Puede observarse como, en ciertos lugares, se obtienen valores de atributos del objeto User, en concreto, privilegeLevel y userId, haciendo referencia al nombre del dicho atributo. Este enfoque tiene el problema de que no se realizan comprobaciones en tiempo de ejecución. Dicho de otro modo, si, por ejemplo,  se cambiase el nombre del atributo userId a user_id, el código seguiría siendo correcto desde el punto de vista del compilador. Sin embargo, al ejecutarlo, se producirían errores al no existir tal atributo.
Una posible solución consiste en usar el metamodelo de Java. Una introducción, con ejemplos no muy afortunados, desde mi punto de vista, puede encontrarse en The Java EE6 Tutorial.

El truco consiste en acceder a los atributos del objeto usando el metamodelo, de manera que un cambio en cualquiera de los atributos del objeto sería detectado mediante un fallo de compilación. Lo primero que hay que hacer es generar la clase del metamodelo correspondiente, la EntityType. Hay varias formas de hacerlo, aunque quizás la mas sencilla sea indicarlo con el flag de compilación correspondiente. Por ejemplo, para openJPA, el flag es  -Aopenjpa.metamodel=true. 

Se genera la clase User_, que no es mas que la clase de  metamodelo de User, siguiendo el estándar de nomenclatura .

* Generated by OpenJPA MetaModel Generator Tool. **/
package com.wordpress.tododev.criteria.entities;
import javax.persistence.metamodel.SingularAttribute;
@javax.persistence.metamodel.StaticMetamodel
(value=com.wordpress.tododev.criteria.entities.User.class)
@javax.annotation.Generated
(value="org.apache.openjpa.persistence.meta.AnnotationProcessor6",date="Mon Mar 04 16:47:46 CET 2013")
public class User_ {
 public static volatile SingularAttribute<User,Boolean> active;
 public static volatile SingularAttribute<User,String> email;
 public static volatile SingularAttribute<User,String> name;
 public static volatile SingularAttribute<User,Integer> privilegeLevel;
 public static volatile SingularAttribute<User,String> userDigestedPasswd;
 public static volatile SingularAttribute<User,Integer> userId;
}

Quizás la primera opción que se viene a la cabeza sea añadir este código autogenerado al sistema de control de versiones. Pero no suele ser buena idea añadir código autogenerado y, sobre todo, una modificación posterior en los atributos de User pasaría desapercibida por el compilador a menos que se generase de nuevo la clase User_.  En el caso de usar ant, podría solucionarse añadiendo un target específico que genere las clase del metamodelo. Dicho target debería ejecutarse cada vez que se realizara alguna modificación en cualquiera de las Entities JPA de la aplicación.

Otra opción consiste en instruir al IDE para generar código de acuerdo con las anotaciones. En Eclipse, hay que añadir el flag de compilación ya mencionado en  Properties->Java Compiler->Annotation Processor y, a continuación, añadir el jar que contiene el Annotation Processor de la implementación JPA elegida en la sección Factory Path (dentro de Annotations Processor). El problema es que, en este caso, la clase de metamodelo User_ se genera al compilar, pero para compilar se necesita la clase del metamodelo, y llegamos al dilema del huevo o la gallina.

En cualquier caso, una vez se disponga de la clase de metamodelo, se puede añadir otro test a la suite anterior que proporciona el mismo resultado sin necesidad de pasar una cadena con el nombre del atributo:

@Test
 public void testUserCriteriaMetaModel(){
 EntityManagerFactory emf = null;
 EntityManager em = null;
 try {
 emf = Persistence.createEntityManagerFactory("criteria");
 em = emf.createEntityManager();
 final CriteriaBuilder cb = em.getCriteriaBuilder();
 final CriteriaQuery<User> q = cb.createQuery(User.class);
 final Metamodel m = em.getMetamodel();
 final Root<User> user = q.from(m.entity(User.class));
 final Predicate condition = cb.equal(user.get(User_.privilegeLevel), 5);
 q.select(user).where(condition).orderBy(cb.asc(user.get(User_.userId)));

 em.getTransaction().begin();
 List<User> result = em.createQuery(q).getResultList();
 em.getTransaction().commit();

 assertNotNull(result);
 assertEquals(2, result.size());

 assertEquals(1, (int)result.get(0).getUserId());
 assertEquals("Pepe", result.get(0).getName());

 assertEquals(3, (int)result.get(1).getUserId());
 assertEquals("Dolores", result.get(1).getName());
} catch (Exception e) {
 fail("Unexpected Exception " + e.getMessage());
 } finally {
 if (em != null)
 em.close();
 if (emf != null)
 emf.close();
 }
 }

Los cambios importantes están en las líneas en negrita. Sobre todo,  user.get(User_.privilegeLevel), en lugar de users.get(“privilegeLevel”) y user.get(User_.userId) en lugar de users.get(“userId”).

Podéis encontrar el código fuente actualizado en GitHub.