HQL: El Lenguaje de Consulta de Hibernate
Hibernate está equipado con un lenguaje de consulta extremadamente potente que
(intencionalmente en absoluto) se parece muchísimo a SQL. Pero no te engañes por la sintaxis;
HQL es completamente orientado a objetos, entendiendo nociones como herencia, polimorfismo
y asociación.
Sensibilidad a Mayúsculas
Las consultas son insensibles a mayúsculas, excepto para nombres de clases Java y propiedades. De modo que
SeLeCT es lo mismo que sELEct e igual a SELECT,
pero org.hibernate.eg.FOO no lo es a org.hibernate.eg.Foo y
foo.barSet no es igual a foo.BARSET.
Este manual usa palabras clave HQL en minúsculas. Algunos usuarios encuentran las consultas con
palabras clave en mayúsculas más leíbles, pero encontramos esta convención fea cuando se encaja
en código Java.
La cláusula from
La consulta más simple posible de Hibernate es de la forma:
que simplemente devuelve todas las instancias de la clase eg.Cat.
Usualmente no necesitamos cualificar el nombre de la clase, ya que auto-import
está por defecto. De modo que casi siempre escribimos solamente:
La mayoría del tiempo, necesitarás asignar un alias, ya que querrás referirte al
Cat en otras partes de la consulta.
Esta consulta asigna el alias cat a las instancias de Cat,
de modo que podríamos usar ese alias luego en la consulta. La palabra clave as
es opcional; también podríamos escribir:
Pueden aparecer múltiples clases, resultando en un producto cartesiano o unión "cruzada" (cross join).
Se considera buena práctica el nombrar los alias de consulta usando una inicial en minúsculas,
consistente con los estándares de nombrado de Java para variables locales
(por ejemplo, domesticCat).
Asociaciones y uniones (joins)
Podemos también asignar aliases a entidades asociadas, e incluso a elementos de una colección de valores,
usando una join.
Los tipos de join soportados son prestados de ANSI SQL
inner join
left outer join
right outer join
full join (no útil usualmente)
Las construcciones inner join, left outer join y
right outer join pueden ser abreviadas.
Puedes proveer condiciones de unión extra usando la palabra clave with de HQL.
10.0]]>
En adición, un "fetch" join permite a las asociaciones o colecciones de valores ser inicializadas
junto a sus objetos padres, usando una sola selección. Esto es particularmente útil en el case de una
colección. Efectivamente sobrescribe el outer join y las declaraciones perezosas (lazy) del fichero
de mapeo para asociaciones y colecciones. Ver para más
información.
Usualmente a un fetch join no se necesita asignársele un alias, porque los objetos asociados no deben
ser usados en la cláusula where (ni en cualquier otra cláusula). Además, los objetos
asociados no son devueltos directamente en los resultados de consulta. En cambio, pueden ser accedidos
vía el objeto padre. La única razón por la que necesitaríamos un alias es estamos uniendo recursivamente
otra colección:
Nota que la construcción fetch no puede usarse en consultas llamadas usando
scroll() o iterate(). Ni debe usarse fetch
junto con setMaxResults() o setFirstResult(). Tampoco puede usarse
fetch junto a una condición with ad hoc. Es posible crear
un producto cartesiano trayendo por join más de una colección en una colección, así que ten cuidado en
este caso. Traer por join múltiples roles de colección también da a veces resultados inesperados para mapeos
de bag, así que sé cuidadoso sobre cómo formular tus consultas en este caso. Finalmente, nota que
full join fetch y right join fetch no son significativos.
Si estás usando recuperación perezosa a nivel de propiedad (con instrumentación de bytecode), es posible
forzar a Hibernate a traer las propiedades perezosas inmediatamente (en la primera consulta) usando
fetch all properties.
La cláusula select
La cláusula select escoge qué objetos y propiedades devolver in el conjunto resultado
de la consulta. Considera:
La consulta seleccionará mates de otros Cats.
Realmente, puedes expresar esta consulta en un forma más compacta como:
Las consultas pueden devolver propiedades de cualquier tipo de valor incluyendo propiedades de
tipo componente:
Las consultas pueden devolver múltiples objetos y/o propiedades como un array de tipo
Object[],
o como una List,
o como un objeto real Java de tipo seguro,
asumiendo que la clase Family tiene un constructor apropiado.
Puedes asignar aliases para seleccionar expresiones usando as:
Esto es lo más útil cuando se usa junto con select new map:
Esta consulta devuelve un Map de aliases a valores seleccionados.
Funciones de agregación
Las consultas HQL pueden incluso devolver resultados de funciones de agregación sobre propiedades:
Las funciones de agregación soportadas son
avg(...), sum(...), min(...), max(...)
count(*)
count(...), count(distinct ...), count(all...)
Puedes usar operadores aritméticos, concatenación, y funciones SQL reconocidas en la cláusula select:
Las palabras clave distinct y all pueden ser usadas y tienen las misma
semántica que en SQL.
Consultas polimórficas
Una consulta como:
devuelve instancias no sólo de Cat, sino también de subclases como
DomesticCat. Las consultas de Hibernate pueden mencionar cualquier
clase o interface Java en la cláusula from. La consulta devolverá instancias de todas
las clases persistentes que extiendan esa clase o implementen la interface. La siguiente consulta devolvería
todos los objetos persistentes.
La interface Named podría ser implementada por varias clases persistentes:
Nota que estas dos últimas consultas requerirán más de un SELECT SQL. Esto significa
que la cláusula order by no ordenará correctamente todo el conjunto resultado.
(Significa además que no puedes llamar estas consulta usando Query.scroll().)
La cláusula where
La cláusula where te permite estrechar la lista de instancias devueltas. Si no existe ningún alias.
puedes referirte a las propiedades por nombre:
Si existe un alias, usan un nombre cualificado de propiedad:
devuelve las instancias de Cat llamadas 'Fritz'.
devolverá todas las instancias de Foo para las cuales exista una instancia
de bar con una propiedad date igual a la propiedad
startDate del Foo. Las expresiones de ruta compuestas hacen
la cláusula where extremadamente potente. Considera:
Esta consulta se traduce en una consulta SQL con una unión de tabla (interna). Si fueses a escribir algo como
terminarías con una consulta que requeriría cuatro uniones de tablas en SQL.
El operador = puede ser usado para comparar no sólo propiedades, sino también instancias:
La propiedad especial (en minúsculas) id puede ser usada para referenciar el identificador
único de un objeto. (También puedes usar su nombre de propiedad.)
La segunda consulta es eficiente. ¡No se requiere ninguna unión de tablas!
También pueden ser usadas las propiedades de identificadores compuestos. Supón que Person
tiene un identificador compuesto consistente en country y medicareNumber.
Una vez más, la segunda consulta no requiere ninguna unión de tablas.
Asimismo, la propiedad especial class acccede al valor discriminador de una instancia en
el caso de persistencia polimórfica. Un nombre de clase Java embebido en la cláusula where será
traducido a su valor discriminador.
Puedes también especificar propiedades de componentes o tipos compuestos de usuario (y de componentes
de componentes, etc). Nunca intentes usar una expresión de ruta que termine en una propiedad de tipo
componente (al contrario de una propiedad de un componente). Por ejemplo, si store.owner
es una entidad con un componente address
Un tipo "any" tiene las propiedades especiales id y class,
permiténdonos expresar un join en la siguiente forma (donde AuditLog.item es una
propiedad mapeada con <any>).
Nota que log.item.class y payment.class harían referencia a
los valores de columnas de base de datos completamente diferentes en la consulta anterior.
Expresiones
Las expresiones permitidas en la cláusula where incluyen la mayoría del tipo de cosas
que podrías escribir en SQL:
operadores matemáticos +, -, *, /
operadores de comparación binarios =, >=, <=, <>, !=, like
operadores lógicos and, or, not
Paréntesis ( ), indicando agrupación
in,
not in,
between,
is null,
is not null,
is empty,
is not empty,
member of y
not member of
Caso "simple", case ... when ... then ... else ... end,
y caso "buscado", case when ... then ... else ... end
concatenación de cadenas ...||... o concat(...,...)
current_date(), current_time(),
current_timestamp()
second(...), minute(...),
hour(...), day(...),
month(...), year(...),
Cualquier función u operador definido por EJB-QL 3.0: substring(), trim(),
lower(), upper(), length(), locate(), abs(), sqrt(), bit_length(), mod()
coalesce() y nullif()
str() para convertir valores numéricos o temporales a una cadena legible.
cast(... as ...), donde el segundo argumento es el nombre de un tipo Hibernate
, y extract(... from ...) si cast() y
extract() fuesen soportados por la base de datos subyacente.
la función index() de HQL, que se aplica a alias de una colección
indexada unida.
funciones de HQL que tomen expresiones de ruta valuadas en colecciones: size(),
minelement(), maxelement(), minindex(), maxindex(), junto a las funciones especiales
elements() and indices que pueden ser cuantificadas usando
some, all, exists, any, in.
Cualquier función escalar SQL soportada por la base de datos como sign(),
trunc(), rtrim(), sin()
parámetros posicionales JDBC ?
parámetros con nombre :name, :start_date, :x1
literales SQL 'foo', 69, 6.66E+2,
'1970-01-01 10:00:01.0'
constantes Java public static final eg.Color.TABBY
in y between pueden usarse como sigue:
y pueden escribirse las formas negadas
Asimismo, is null y is not null pueden ser usadas para comprobar
valores nulos.
Los booleanos pueden ser fácilmente usados en expresiones declarando substituciones de consulta HQL
en la configuración de Hibernate:
true 1, false 0]]>
Esto remplazará las palabras clave true y false con los literales
1 y 0 en el SQL traducido de este HQL:
Puedes comprobar el tamaño de una colección con la propiedad especial size, o la función
especial size().
0]]>
0]]>
Para colecciones indexadas, puedes referirte a los índices máximo y mínimo usando las funciones
minindex y maxindex. Similarmente, puedes referirte a los elementos
máximo y mínimo de una colección de tipo básico usando las funciones
minelement y maxelement.
current date]]>
100]]>
10000]]>
Las funciones SQL any, some, all, exists, in están soportadas cuando se les pasa
el conjunto de elementos o índices de una colección (funciones elements y
indices) o el resultado de una subconsulta (ver debajo).
all elements(p.scores)]]>
Nota que estas construcciones - size, elements,
indices, minindex, maxindex,
minelement, maxelement - pueden ser usadas solamente
en la cláusula where en Hibernate3.
Los elementos de colecciones indexadas (arrays, listas, mapas) pueden ser referidos por índice
(en una cláusula where solamente):
La expresión dentro de [] puede incluso ser una expresión aritmética.
HQL provee además el función prefabricada index(), para elementos de una
asociación uno-a-muchos o colección de valores.
Pueden usarse las funciones SQL escalares soportadas por la base de datos subyacente
Si aún no estás convencido de todo esto, piensa cuánto más largo y menos leíble sería la siguiente
consulta en SQL:
Ayuda: algo como
La cláusula order by
La lista devuelta por una consulta puede ser ordenada por cualquier propiedad de una clase devuelta
o componentes:
Los asc o desc opcionales indican ordenamiento ascendente o
descendente respectivamente.
La cláusula group by
Una consulta que devuelve valores agregados puede ser agrupada por cualquier propiedad de una clase
devuelta o componentes:
Se permite también una cláusula having.
Las funciones y funciones de agregación SQL están permitidas en las cláusulas
having y order by, si están soportadas por la base de datos
subyacente (por ejemplo, no en MySQL).
100
order by count(kitten) asc, sum(kitten.weight) desc]]>
Nota que ni la cláusula group by ni la cláusula order by pueden
contener expresiones aritméticas.
Subconsultas
Para bases de datos que soportan subconsultas, Hibernate soporta subconsultas dentro de consultas. Una
subconsulta debe ser encerrada entre paréntesis (frecuentemente por una llamada a una función de agregación
SQL). Incluso se permiten subconsultas correlacionadas (subconsultas que hacen referencia a un alias en la
consulta exterior).
(
select avg(cat.weight) from DomesticCat cat
)]]>
Para las subconsultas con más de una expresión en la lista de selección, puedes usar un constructor
de tuplas:
Nota que en algunas bases de datos (pero no en Oracle o HSQL), puedes usar constructores de tuplar en
otros contextos, por ejemplo al consultar componentes o tipos de usuario compuestos:
Que es equivalente a la más verborrágica:
Existen dos buenas razones por las cuales podrías no querer hacer este tipo de cosa: primero, no es
completamente portable entre plataformas de base de datos; segundo, la consulta ahora es dependiente
del orden de propiedades en el documento de mapeo.
Ejemplos de HQL
Las consultas de Hibernate pueden ser abolutamente potentes y complejas, De hecho, el poder del lenguaje
de consulta es uno de los puntos principales de venta de Hibernate. He aquí algunos consultas de ejemplo
muy similares a consultas que he usado en un proyecto reciente. ¡Nota que la mayoría de las consultas
que escribirás som mucho más simples que estas!
La siguiente consulta devuelve el order id, número de items y valor total de la orden para todas
las ordenes inpagas de un cliente en particular y valor total mínimo dados, ordenando los resultados
por valor total. Al determinar los precios, usa el catálogo actual. La consulta SQL resultante,
contra las tablas ORDER, ORDER_LINE, PRODUCT,
CATALOG and PRICE tiene cuatro joins interiores y una subselect
(no correlacionada).
= all (
select cat.effectiveDate
from Catalog as cat
where cat.effectiveDate < sysdate
)
group by order
having sum(price.amount) > :minAmount
order by sum(price.amount) desc]]>
¡Qué monstruo! Realmente, en la vida real, no estoy muy afilado en subconsultas, de modo que mi
consulta fue realmente algo como esto:
:minAmount
order by sum(price.amount) desc]]>
La próxima consulta cuenta el número de pagos en cada estado, excluyendo todos los pagos
en el estado AWAITING_APPROVAL donde el estado más reciente fue hecho por el
usuario actual. Se traduce en una consulta SQL con dos joins interiores y una subselect
correlacionada contra las tablas PAYMENT, PAYMENT_STATUS y
PAYMENT_STATUS_CHANGE.
PaymentStatus.AWAITING_APPROVAL
or (
statusChange.timeStamp = (
select max(change.timeStamp)
from PaymentStatusChange change
where change.payment = payment
)
and statusChange.user <> :currentUser
)
group by status.name, status.sortOrder
order by status.sortOrder]]>
Si hubiese mapeado la colección statusChanges como una lista, en vez de un conjunto,
la consulta habría sido mucho más simple de escribir.
PaymentStatus.AWAITING_APPROVAL
or payment.statusChanges[ maxIndex(payment.statusChanges) ].user <> :currentUser
group by status.name, status.sortOrder
order by status.sortOrder]]>
La próxima consulta usa la función isNull() de MS SQL Server para devolver
todas las cuentas y pagos inpagos de la organización a la que pertenece el usuario actual.
Se traduce en una consulta SQL con tres joins interiores, un join exterior y una subconsulta
contra las tablas ACCOUNT, PAYMENT, PAYMENT_STATUS,
ACCOUNT_TYPE, ORGANIZATION y ORG_USER.
Para algunas bases de datos, necesitaríamos eliminar la subselect (correlacionada).
Sentencias UPDATE y DELETE masivas
HQL soporta ahora sentencias UPDATE y DELETE en HQL.
Ver para detalles.
Consejos y Trucos
Puedes contar el número de resultados de una consulta sin devolverlos realmente:
Para ordenar un resultado por el tamaño de una colección, usa la siguiente consulta:
Si tu base de datos soporta subselects, puedes colocar una condición sobre el tamaño de selección
en la cláusula where de tu consulta:
= 1]]>
Si tu base de datos no soporta subselects, usa la siguiente consulta:
= 1]]>
Como esta solución no puede devolver un User con cero mensajes debido a la unión interior,
la siguiente forma es también útil:
Las propiedades de un JavaBean pueden ser ligadas al parámetros de consulta con nombre:
Las colecciones son paginables usando la interface Query con un filtro:
Los elementos de colección pueden ser ordenados o agrupados usando un filtro de consulta:
Puedes hallar el tamaño de una colección sin inicializarla: