Home > Desarrollo, Librerías/Frameworks > Creación de consultas dinámicas en JPA sin SQL (ni JPQL)

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

En JPA se pueden crear varios tipos de consultas. Dichas consultas pueden clasificarse de acuerdo a distintos criterios. En nuestro caso, nos interesan dos criterios de distinción: según el lenguaje en que se formulan (SQL, JPQL, …) y si son estáticas (definidas en el propio código) o dinámicas (se construyen en tiempo de ejecución).

Las consultas estáticas se definen usando las anotaciones @NamedQuery (javax.persistence.NamedQuery) y @NamedQueries (javax.persistence.NamedQueries) en la propia clase @Entity:

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

Por otra parte, los objetos EntityManager proporcionan los métodos createQuery(…) y createNativeQuery(…) que aceptan como parámetro una consulta JQPL o SQL, respectivamente. Por lo tanto, las consultas pueden construirse dinámicamente, en tiempo de ejecución. (Importante: usar siempre consultas parametrizadas usando los métodos setParameter(…) de los objetos Query creados para evitar posibles vulnerabilidades SQL Injection).

Sin embargo, existe otra forma de crear consultas dinámicas en JPA sin tener que usar JPQL ni SQL: usando la API Criteria. En definitiva, uno de los motivos para usar JPA es tratar con objetos en lugar de con dialectos SQL y derivados, verdad ? A continuación se muestra un ejemplo sencillo.

En primer lugar, definimos una Entity:

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

Partiendo de una base de datos en la que se han insertado varios objetos, procedemos a realizar una consulta y comprobamos los datos recibidos (es un test JUnit que puede ejecutarse directamente desde cualquier IDE actual):

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

La creación de la consulta se realiza en las siguientes líneas:

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

En primer lugar, se obtiene un CriteriaBuilder a partir del EntityManager. A partir de dicho objeto se crea una CriteriaQuery, especificando la clase resultado, que en este caso es User.class:

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

A continuación, definimos la Root, que es otra cosa que la @Entity sobre la que se va a realizar la consulta:

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

Queda ahora especificar las condiciones que deben cumplir los objetos seleccionados. En este caso, la condición es que el atributo privilegeLevel del objeto sea igual a 5:

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

Finalmente, se contruye la consulta combinando la Root con las condiciones que deben cumplir los objetos a seleccionar (pueden añadirse y combinarse lógicamente varias condiciones, de la misma forma que se usaría AND y OR en SQL/JQPL), y especificando opciones de agrupación, ordenado, etc… En este caso,  orden ascendente según el valor del atributo userId:

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

La consulta ya está creada y sólo quedaría ejecutarla.

Pueden obtenerse las diferentes opciones echando un vistazo a la referencia de la interfaz CriteriaBuilder. Las opciones de agrupación, ordenación, … (similares a ORDER BY, GROUP BY, …) se encuentran en la referencia de la interfaz CriteriaQuery.

Puede descargarse el código fuente (Eclipse Project) desde GitHub

  1. ignaciodaluz
    05/28/2013 at 13:25

    Excelente material.
    Arriba!!!

  2. gustavo
    04/15/2014 at 04:45

    hola, habia estado buscando varios tutoriales de criteria, pero hasta ahora este es el unico tuto que explica que hace cada linea de la consulta, muy bueno todo de verdad muchas gracias por compartir sus conocimientos con los que empezamos en esto Saludos y de nuevo gracias

    • 04/15/2014 at 07:55

      Muchas gracias por tu comentario Gustavo, nos alegra saber que sea de utilidad. Un saludo !!

  3. 08/13/2014 at 12:56

    Muy bien explicado, lo necesario destacado. Te hago una consulta, hay alguna forma de crear una consulta dinámica por la cual se consulte a la Base de Datos solo por los atributos de la entidad que no son nulos, por ejemplo, Tengo una Entidad con 3 atributos A, B y C, donde A es igual a “algo” y los demás en este caso están nullos, podría crear una consulta “select * from entidad where A = ‘algo’;” y si luego A y B tienen algún valor entonces la consulta seria “select * from entidad where A = ‘algo’ and B = ‘algo’;”

    Lo estoy buscando por todos lados pero no logro encontrar la solución, antes cuando usaba Hibernate utilizaba los examples para hacer eso de manera muy sencilla.

    Gracias, y felicitaciones.

  1. 03/04/2013 at 18:07
  2. 09/10/2015 at 21:10

Leave a comment