Archive

Posts Tagged ‘jpa’

Compile-time checking JPA queries

JPA provides several alternatives for querying data. Such alternatives may be classified attending to a variety of criteria, eg, language used (SQL vs JPQL) or whether queries are static (compilation time) or dynamic (execution time).

Static queries are defined using annotations @NamedQuery (javax.persistence.NamedQuery) and @NamedQueries (javax.persistence.NamedQueries) in the @Entity class definition itself:

 @NamedQuery(
            name="findAllCustomersWithName",
            query="SELECT c FROM Customer c WHERE c.name LIKE :custName"
    )

On the other hand, EntityManager provides methods createQuery(…) y createNativeQuery(…) which take either a JPQL or a SQL query, respectively.

Thus, queries can be defined both in compilation or execution time.

(Note: It is advisable to always use parametrized queries using methods setParameter(…) from Query to avoid SQL Injection vulnerabilities.

Criteria API

However, JPA provides an alternative approach to query objects: Criteria API. Indeed, one of the motivations to switch to JPA is to deal with objects rather than SQL dialects, isn’t it ?

Let’s look a sample code.

Entity definition:

@Entity
public class User {

 @Id
 private Integer userId;

 @Basic
 @Column(length=15, nullable=false)
 private String name;

 @Basic
 @Column(length=64, nullable=false)
 private String userDigestedPasswd;

 @Basic
 @Column(length=50, nullable=true)
 private String email;

 @Basic
 @Column(nullable=false)
 public Integer privilegeLevel;

 @Basic
 @Column(nullable=false)
 private Boolean active;
}

Let’s query db and check results (using JUnit):

public class UserTest {
 @Test
 public void testUserCriteria(){
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 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")));
  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();
}
}
}

Following lines show query creation:

final CriteriaBuilder cb = em.getCriteriaBuilder();
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")));

First of all, get a CriteriaBuilder from an EntityManager. Then, get a CriteriaQuery instance, setting the class to hold results. In our case, User.class:

final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<User> q = cb.createQuery(User.class);

Following, the Entity to run the query against must be set:

final Root<User> users = q.from(User.class);

Now it’s time to set query matching conditions. In the sample code, the condition is just attribute privilegeLevel to be equals to 5:

final Predicate condition = cb.equal(users.get("privilegeLevel"), 5);

Finally, query is built adding conditions on Root. Grouping and sorting options may be set too (ie, ascending sorting is set on userId):

q.select(users).where(condition).orderBy(cb.asc(users.get("userId")));

Please have a look at CriteriaBuilder for different options. Grouping and sorting options may be found at CriteriaQuery.

Using metamodel for compile-time checking

Note the query we have just build requires to keep track of object attributes names. Eg, to build the query, the name of the attribute privilegeLevel is used. However, if attribute name were changed later, the code would compile and only fail at runtime:

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")));

That is no good.

Fortunately, using metamodel, we will be able to build compile-time checked queries. A brief introduction can be found at The Java EE6 Tutorial.

Using metamodel, the code will reference an SingularAttribute of the object rather than using a String holding the object attribute name. So, if object attribute were changed later, the compiler would flag it for us.

First of all, the correspondent metamodel class (EntityType) must be created. Although it can achieved by several ways, probably the easiest one, for openJPA implementation, is to add a openJPA build flag:  -Aopenjpa.metamodel=true.

So we have the class User_ created, which is the correspondent metamodel class for User:

* 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;
}

If such class were added to code repo, any later change to class User would remain unnoticeable. Moreover, it is not a good idea to add auto-generated items to code versioning systems.

Using ant, maven or similar tools, a target could be added to create metamodel classes. Such target should be executed after any change to JPA Entities.

Also possible to use IDE for that. Eg, for those using Eclipse, just need to add the already mentioned compilation flag to Properties->Java Compiler->Annotation Processor and the lib (jar) containing the Annotation Processor for the chosen JPA implementation to section Factory Path within Annotations Processor (could lead to compilation issues in auto mode, provided that metamodel class must be compiled before the code using it).

Let us add another test to the suite. This one will not provide a String containing the attribute name, but use the metamodel class instead:

@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();
 }
 }

More relevant changes are user.get(User_.privilegeLevel) instead of users.get(“privilegeLevel”) and  user.get(User_.userId) instead of  users.get(“userId”).

Download source code from GitHub

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.

%d bloggers like this: