O Hibernate possui uma linguagem de consulta para retornar objetos da base de dados (com SELECT) chamada HQL que é muito parecida com o SQL, mas ao invés de efetuar operações nas tabelas e colunas da base de dados, o HQL trabalha com objetos persistentes e suas propriedades, ou seja, classes e propriedades das classes. O HQL inspirou a criação da JPQL (Java Persistence Query Language) que faz parte da especificação oficial da JPA. A linguagem HQL não se limita apenas a consultas, mas também pode ser usada para alterar informações da base de dados (com INSERT, UPDATE, DELETE). O HQL não permite que possamos alterar a estrutura da base de dados.
A utilização da HQL é sempre bastante recomendada para evitarmos problemas de portabilidade e principalmente termos como vantagens a geração do SQL por parte do Hibernate feita da melhor forma e mais otimizada, e também as estratégias de cache que o Hibernate cria.
Sintaxe Básica do SELECT
O HQL tem sua própria sintaxe e gramática, sendo escritas como Strings ao contrário da API Criteria do Hibernate que tem a forma de uma API convencional do Java. As consultas HQL são traduzidas pelo Hibernate em consultas SQL. Além disso, o Hibernate também provê uma API que permite consultas SQL diretamente.
O SELECT é utilizado para consultar classes e suas propriedades na base de dados. Apesar da linguagem HQL ter um poder expressivo altamente poderoso, se quisermos uma maior complexidade utilizando joins e like, devemos optar pela API Criteria que é mais apropriada. Na Listagem 1 temos um exemplo de como utilizar o comando SELECT.
Listagem 1. Comando SQL
[SELECT [DISTINCT] property [, ...]]
FROM path [[AS] alias] [, ...] [FETCH ALL PROPERTIES]
WHERE logicalExpression
GROUP BY property [, ...]
HAVING logicalExpression
ORDER BY property [ASC | DESC] [, ...]
O atributo path é o nome completo da entidade. O “alias” podem ser usados para abreviar referências para entidades específicas ou suas propriedades e também pode ser utilizado quando nomes de propriedades usadas são ambíguas. O atributo property é o nome das propriedades das entidades listadas no path após FROM. Se utilizarmos o FETCH ALL PROPERTIES, as semânticas lazy loading serão ignoradas e dessa forma, todas as propriedades dos objetos recuperados serão ativamente carregados (isto não é aplicado recursivamente). Uma das diferenças ao utilizarmos JPQL ao invés de HQL é que a JPQL requer a cláusula SELECT, ao passo que no HQL podemos omitir o SELECT.
Palavras-chaves do HQL podem ser maiúsculas ou minúsculas. Por outro lado, o nome das classes e propriedades são case sensitive. Assim, uma consulta “from Produto” é similar a “FROM Produto”, mas uma consulta “from produto” não é o mesmo que “from Produto”.
Um “alias” pode ser usado com AS definido ou sem, como por exemplo “from Produto produto” ou “from Produto as produto”.
Se tivermos nomes de classes duplicados na aplicação podemos utilizar o pacote com o nome da classe como, por exemplo, “from com.exemplo.produtos.Produto”.
Outra forma de usar o SELECT é retornar apenas algumas propriedades ao invés do objeto inteiro, como por exemplo: “select produto.nome from Produto produto” onde essa consulta retornaria uma LISTA de objetos String.
Mas se quisermos retornar alguns atributos, teríamos o HQL “select produto.nome, produto.preco from Produto produto” onde teríamos um resultado contendo uma lista de arrays de objetos, onde cada array representa um conjunto de propriedades.
Utilizando Restrições
Assim como no SQL, podemos usar a cláusula WHERE para selecionar resultados que combinam com as nossas consultas. HQL provê diferentes expressões que podemos usar para construir uma consulta. As possíveis expressões são:
- Operadores Lógicos: OR, AND, NOT
- Operadores de Igualdade: =,
- Operadores de Comparação: , =, like, not like, between, not between
- Operadores Matemáticos: +, -, *, /
- Operadores de Concatenação: ||
- Cases: Case when then else _ end
- Expressões de Coleção: some, exists, all, any
Além dessas ainda podemos usar a seguintes expressões nas cláusulas where:
- Parâmetros nomeados do HQL: :date, :quantity
- Parâmetros de consulta JDBC: ?
- Operadores de Data e Tempo do SQL-92: current_time(), current_date(), current_timestamp()
- Funções SQL (Suportados pela Base de Dados): length(), upper(), lower(), ltrim(), rtrim(), etc.
Dessa forma, podemos construir cláusulas com WHERE no HQL tão poderosas quanto uma consulta SQL. Segue abaixo uma consulta HQL que demonstra algumas das restrições discutidas acima:
from Produto where preco > 25.0 and nome like ''Tec%''
Nesse exemplo temos as operações ">", "and", e "like". Nessa consulta estamos procurando produtos com preços acima de 25.0 e que tenham nomes iniciando com "Tec".
Usando Parâmetros Nomeados
Uma funcionalidade extremamente importante nas consultas HQL do Hibernate são os parâmetros nomeados. Isto faz com que a escrita de consultas que aceitam entradas do usuário seja bastante simples e ainda defende nosso código contra ataques de SQL Injection. Se não utilizarmos parâmetros nomeados podemos estar sendo vulneráveis a esse tipo de ataque.
Parâmetros nomeados no Hibernate são iguais aos utilizados pela JDBC (?), porém o Hibernate é menos confuso. Na Listagem 2 temos um exemplo bastante simples, mas que demonstra de forma clara como podemos usar parâmetros nomeados no Hibernate
Listagem 2. Parâmetros nomeados
String hql = "from Produto where preco > :preco";
Query query = session.createQuery(hql);
query.setDouble("preco",25.0);
List results = query.list();
No exemplo acima, no lugar do valor 25.0 em query.setDouble("preco",25.0); provavelmente teríamos uma variável, pois não sabemos o valor que será inserido ali. Se já soubermos o valor podemos colocar ele diretamente dentro da String como na Listagem 3.
Listagem 3. Valor direto na String
String detalhesProdutoHQL = "from DetalhesProduto where nome=''Veja''";
Query detalhesProdutoQuery = session.createQuery(detalhesProdutoHQL);
DetalhesProduto detalhesProduto =
(DetalhesProduto) detalhesProdutoQuery.list().get(0);
Outra situação interessante é quando já temos um objeto e queremos fazer alguma consulta em cima deste objeto. Podemos imaginar a situação em que temos o objeto pesquisado no exemplo acima e vamos usar esse objeto para fazer outra pesquisa, como é exemplificado na Listagem 4.
Listagem 4. Nova pesquisa
String hql = "from Produto as produto where
produto.detalhes=:detalhesProduto";
Query query = session.createQuery(hql);
query.setEntity("detalhesProduto",detalhesProduto);
List results = query.list();
Paginação
A paginação é muito útil quando temos uma grande quantidade de informação retornada através de uma consulta e queremos exibir apenas algumas dessas informações. Isso é muito utilizado nas aplicações Web onde devemos construir uma página apropriada para o usuário sem poluir muito a tela.
Existem dois métodos na interface Query para paginação: setFirstResult() e setMaxResults(). O método setFirstResult() requer um inteiro que representa a primeira linha no result set, podendo iniciar na linha 0. O método setMaxResults() retorna um número fixo de objetos. Na Listagem 5 temos um exemplo de paginação no Hibernate.
Listagem 5. Paginação
Query query = session.createQuery("from Produto");
query.setFirstResult(1);
query.setMaxResults(2);
List results = query.list();
O Hibernate usa o comando SQL apropriado dependendo da base de dados, dessa forma, se o Banco de dados utilizado for Microsoft SQL Server ele usará o comando top, se estivermos usando o Banco de dados HSQLDB o Hibernate usará o comando top e limit. Devemos ficar alerta para possível problemas de performance na utilização de paginação.
Obtendo Resultados Únicos
A interface Query do HQL provê o método uniqueResult() que é utilizando para obter apenas um único objeto de uma consulta HQL, caso não seja retornado nenhum resultado teremos um null como retorno do método. Caso tenhamos mais de um objeto retornado teremos uma exceção NonUniqueResultException.
Nesse caso, poderíamos utilizar o método setMaxResults() como discutido na sessão anterior. Na Listagem 6 temos um exemplo da utilização de uniqueResult().
Listagem 6. Usando resultados únicos
String hql = "from Produto where preco > 25.0";
Query query = session.createQuery(hql);
query.setMaxResults(1);
Produto produto = (Produto) query.uniqueResult();
//testa resultados nulos se necessário
Ordenando Resultados
Para ordenar os resultados de uma consulta HQL podemos utilizar a clausula order by ascendente (asc) ou descendente (desc). Em HQL podemos fazer como no exemplo abaixo:
from Produto p where p.preco > 25.0 order by p.preco desc
Se quisermos um comando “order” para mais de uma propriedade basta separarmos por vírgulas como abaixo:
from Produto p order by p.nome asc, p.preco asc
Associações
Assim como no SQL, com HQL também podemos fazer relacionamentos entre classes (tabelas) utilizando a clausula join. O Hibernate também suporta cinco diferentes tipos de joins: inner join, cross join, left outer join, right outer join e full outer join.
Se utilizarmos o cross join não precisamos especificar nada, basta colocar as classes separadas por vírgulas após o from, como exemplificado abaixo:
from Produto p, DetalhesProduto d
Para utilizarmos o inner join devemos fazer da seguinte forma:
select d.descricao, p.nome, p.preco from Produto p
inner join p.detalhesProduto as d
Ou então da forma abaixo:
from Produto p inner join p.detalhesProduto as d
Como retorno teremos arrays de objetos retornados. Se quisermos melhorar performance ainda podemos utilizar o fetch para as consultas. Segue abaixo um exemplo:
from DetalhesProduto d inner join fetch d.produto as p
Dessa forma teremos como resultado apenas a classe DetalhesProduto e nenhum objeto Produto. Produto somente será carregado quando acessarmos a sua propriedade.
Agregação
Assim como no SQL, utilizando HQL também temos os métodos agregados. Segue abaixo os métodos de agregação disponíveis no Hibernate:
- avg(nome da propriedade): Retorna a média do valor da propriedade.
- count(nome da propriedade ou *): Retorna o número de vezes que a propriedade ocorre no resultado.
- max(nome da propriedade): O valor máximo do valor da propriedade.
- min(nome da propriedade): O valor mínimo do valor da propriedade.
- sum(nome da propriedade): A soma total dos valores da propriedade.
Segue abaixo um exemplo que determina os valores mínimo e máximo a serem retornados da consulta:
select min(produto.preco), max(produto.preco) from Produto produto
Com isso, neste artigo vimos como fazer consultas utilizando o HQL, como utilizar restrições com as consultas, como utilizar os parâmetros nomeados evitando ataques SQL Injection, como utilizar paginações e outras funcionalidades que fazem do HQL uma poderosa linguagem de consulta.
Bibliografia
[1]Hibernate - JBoss Community, disponível em www.hibernate.org/
[2]Documentação de Referência Hibernate, disponível em https://docs.jboss.org/hibernate/orm/3.5/reference/en/html/queryhql.html
[3] Introdução ao Hibernate, disponível em https://docs.jboss.org/hibernate/orm/6.3/introduction/html_single/Hibernate_Introduction.html
[4] Jeff Linwood and Dave Minter. An introduction to persistence using Hibernate 3.5, Second Edition. Apress.
[5] Steve Perkins. Hibernate Search by Example: Explore the Hibernate Search system and use its extraordinary search features in your own applications. Packt Publishing.