miércoles, 26 de diciembre de 2007

Criteria Query en Hibernate

Uno de los puntos débiles que encontré en el nuevo estándar JPA (Java Persistence API de Sun), es que no provee una funcionalidad análoga al Criteria Query de Hibernate.

El Criteria Query nos permite definir consultas con un estilo orientado a objetos, muy distinto al clásico SQL o HQL. La primera impresión del API me resultó un poco extraña, difícil de leer. Por ejemplo, el siguiente query con Criteria:


List personas = sess.createCriteria(Persona.class)
.add(Restrictions.like("nombre", "Fede%"))
.add(Restrictions.isNull("edad"))
.addOrder(Order.asc("nombre"))
.list();
sería el equivalente a éste otro con HQL:

String query = "from Persona where nombre like 'Fede%' and edad is not null order by nombre asc";
List personas = sess.createQuery(query).list();

Sin dudas el query HQL es mas fácil de leer a simple vista, la versión orientada a objetos requiere un análisis mas detallado.

Pero por otro lado, que sucedería si la consulta tuviera que armarse dinámicamente?
Por ejemplo, si el usuario selecciona las opciones por las cuales desea realizar la búsqueda, entonces el "where" del query debemos establecerlo en tiempo de ejecución. En éste caso es dónde personalmente le encuentro una gran ventaja al Criteria porque podemos hacer cosas como:

Criteria criteria = sess.createCriteria(Persona.class);
if (nombre != null) {
criteria.add(Restrictions.eq("nombre", nombre));
}
if (edad != null) {
criteria.add(Restrictions.eq("edad", edad));
}
if (pais != null) {
criteria.add(Restrictions.eq("pais", pais));
}
List personas = criteria.list();

El código anterior define una consulta que toma como entidad base a "Persona" y luego dependiendo de si hay un valor asignado para nombre, edad y país agrega las restricciones al Criteria.

Para resolverlo en HQL deberíamos armar el query concatenando strings, que no está mal, pero puede inducir a errores en la definición, me parece que en estos casos es mas natural usar el estilo orientado a objetos del Criteria.

Además permite desarrollar un esquema de consultas genérico, donde el usuario del sistema "arma" su propio query sin mayor esfuerzo por parte del desarrollador, en próximos posts publicaré ejemplos.

22 comentarios:

Mauro Dj dijo...

muy bueno federico. gracias.

Federico Varela dijo...

Gracias por el comentario, saludos.

geremora dijo...

Necesita un ejemplo asi. Justamente tenia que hacer una consulta multicriterio. Pero ademas intervenian otras clases (tablas).
Con lo que use..
criteria.createAlias("","").

Gracias.
Estoy metiendome con hibernate, cualquier post sobre esto me interesa.

Federico Varela dijo...

geremora, gracias por el aporte, el detalle que comentas es muy importante, cuando agregamos restricciones sobre relaciones hay que usar el createAlias, así indicamos el join necesario.

Saludos

GB-Over dijo...

hola
Tengo un problema con el query q estoy realizando.


Criteria criterios = getSession().createCriteria(TamanosVo.class);

if (tamanoVo.getDescripcion().isEmpty() == false)
criterios.add(Restrictions.like("descripcion", tamanoVo
.getDescripcion()
+ "%"));

if (tamanoVo.getPersonas() != null)
criterios.add(Restrictions.eq("personas", tamanoVo.getPersonas()
+ "%"));

criterios.addOrder(Order.asc("idTamano"));
List TamanosVo tamano = criterios.list();
return tamano;
}


Este es mi query ha simple vista no tendria que haber algun problema, pero el metodo getPersonas() es un Integer, ya trate de ahcer un CAST de diferente manera y no he podido que me realice la busqueda metiendo solo el numero de personas.

Agradesco la ayuda.

Federico Varela dijo...

gb, hola, no me queda claro cual es el problema con el query, si te da un error o si no obtienes resultados. Si tienes habilitado el log de las sentencias SQL (property show_sql en hibernate.cfg.xml) puedes ver en la consola si la sentencia queda armada correctamente.
Una cosa rara que veo en tu query es que agregas un "%" usando Restrictions.eq para el atributo personas...

Saludos

GB-Over dijo...

Si obtengo resultados de la busqueda pero esto solo sucede cuando hago una busqueda general, es decir que sin poner ningun parametro de busqueda, pero si meto algun numero que solo me traiga de la lista los registros de tengan esa numero no me la hace.

El error que me aroja es :
Hibernate: select this_.idtamano as idtamano11_0_, this_.idusuarioalta as idusuari2_11_0_, this_.idestatus as idestatus11_0_, this_.idusuariomodificacion as idusuari4_11_0_, this_.descripcion as descripc5_11_0_, this_.personas as personas11_0_, this_.fechalta as fechalta11_0_, this_.fechamodificacion as fechamod8_11_0_ from public.adm_ctamanos this_ where this_.personas=? order by this_.idtamano asc
07/08/08 16:39:58 INFO (NullableType.nullSafeSet:140) - could not bind value '4%' to parameter: 1; java.lang.String cannot be cast to java.lang.Integer
07/08/08 16:39:58 ERROR (StandardWrapperValve.invoke:253) - Servlet.service() para servlet Faces Servlet lanzó excepción

No puedo pasar un String a un Integer es por eso que ya hice varios Cats pero ninguno me ha quedado.

Federico Varela dijo...

gb, como te decía en el comentario anterior, no se por que usas el % en el atributo personas, por eso te da el error: could not bind value '4%'. Si el atributo es un integer no puedes usar un string. El % solo se usa con el like sobre strings.
Elimina la parte del + "%" y debería funcionar.

Saludos

GB-Over dijo...

El % lo utilizo para que no solo me realice una busqueda exacta si no con la coincidencia de los primeros numeros o letras.

Federico Varela dijo...

En ese caso debes cambiar el atributo personas a string en la clase y a varchar en la BD, de lo contrario no puedes usar %.

Jorge dijo...

Hola muy buenos los aportes sobre criteria que haces.
Estoy utilizando el criteria para generar una informacion en un plano, la consulta la realiza bien, pero al momento de hacer el criteria utilizando :
criteria.setCacheable(false)
.setMaxResults(tamanoBuffer)
.setFirstResult(valor).list(), en algunos casos se bloquea y no genera el criteria. Me puedes colaborar con esto.. gracias,

Federico Varela dijo...

Jorge, no entiendo la parte que dices "algunos casos se bloquea y no genera el criteria".
¿Te da un error, no retorna resultados?

Saludos

Jorge dijo...

Hola Federico, no genera ningun error, solo se queda bloqueado, al hacerlo con el debug, no pasa del setFirstResult(valor).list()), te cuento que es lo que tengo, primero ecuentro un numero de registros a travéz del criteria, este lo ingreso a un ciclo (for) que recorre un join de registros los cuales se generan uno a uno en un plano .cvs.

Federico Varela dijo...

Jorge, no veo nada extraño para que suceda el bloqueo, tal vez el resultado de la consulta sea muy grande (varios miles de registros) y demore en la carga por falta de memoria, o tengas algún problema de locking en la base de datos (Isolation Level en Hibernate)

Saludos

Jorge dijo...

Federico te sigo contando, el proyecto que estoy desarrollando lo realizo con base en vistas materializadas, pero al crear el proyecto lo que hice fue crear las tablas con sus llaves y Fk con las otras tablas, luego cambie los nombres de las clases en hibernate y me funciono perfecto... hasta que.. Para darle mayor performance al proceso le cree unos indices a a las vistas, desde ese momento se bloque en el punto que te dije en los correos anteriores. Te comento ademas q realice una prueba borrando los indices y.. sorpresa.. funciono pero el performance es alto... mas a un si esto es para uso de muchos usuarios.

Federico Varela dijo...

Jorge, no se que base de datos estás usando, hay algo que se llama Index Locking, te recomiendo que busques en google. Trata de buscar la forma de deshabilitar o bajar el nivel de locking.

Anónimo dijo...

hola, felicidades por la publicación.
Ando trabajando con hibernate y en particular me gusta trabajar con ¨criteria¨, pero me ha surgido una duda, que sucederá al ejecutar una criteria la cual espere como valor un string y esta sea una cosulta, ¿se ejecutará la cunsulta?

Federico Varela dijo...

Hola, gracias por visitar blog. Creo que lo que quieres hacer es un subquery, o también llamado query anidado. Hibernate lo soporta, aunque es una funcionalidad que no recuerdo haberla usado, puedes ver la doc aquí:

http://docs.jboss.org/hibernate/stable/core/reference/en/html/querycriteria.html#querycriteria-detachedqueries

Saludos

Federico

sickdhartha dijo...

Hola que hay, bueno aqui buscando una consulta sobre un campo que fuera collection, o set es decir que el bean tuviera una variable que en realidad fuera un set, lo que use fue el alias, en realidad esto me permite hacer consultas sobre columnas que son listas.

dejo el ejemplo:

criterio.createAlias("municipios","municipioEstado");
//Aqui municipios en el bean es de tipo set, lo renombro con el alias y le agrego el restriccion y hace la consulta.
criterio.add(Restrictions.eq("municipioEstado.estado.id", busquedaConflictosDGCEFVO.getIdestado()));
//agrego el restriccion

quetz dijo...
Este comentario ha sido eliminado por el autor.
quetz dijo...

Oye amigo donde consigo un manual, tutotial, o libro para aprender a usar criteria. Salu2 y gracias

quetz dijo...
Este comentario ha sido eliminado por el autor.