A utilização de linguagens alternativas ao SQL cresceu muito nos últimos anos, pois estas encapsulam o poder já existente do SQL adicionando uma maior quantidade de recursos. Neste artigo veremos algumas peculiaridades da linguagem HQL (Hibernate Query Language), uma das linguagens mais utilizadas quando falamos em consultas orientadas a objeto.

HQL é uma linguagem muito similar ao SQL (Structured Query Language), com a grande diferença de ser totalmente orientada a objeto, possibilitando o uso de noções básicas de OO como polimorfismo, herança e associação.

O mercado para o banco de dados Orientado a Objetos ainda é escasso e são muito poucos os interessados em migrar do tão famoso banco de dados relacional para o orientado a objetos, sendo assim surgem alternativas para possibilitar que o desenvolvedor consiga trabalhar com um banco de dados relacional de forma orientada a objetos, como é o caso da HQL.

Estruturando o projeto

Vamos começar a estruturar um projeto para trabalharmos com HQL de uma forma mais clara. Nosso projeto consistirá em quatro classes: NotaFiscal, ItemNotaFiscal, Produto e Cliente. A construção adequada deste irá nos possibilitar a criação de diversos HQL's para aprendizado. O código completo encontra-se na Listagem 1.


import java.util.Date;
import java.util.List;
 
public class NotaFiscal {
       
  private long numeroNota;
  private Date dataHoraEmissao;
  private String chaveAcesso;
  private List<ItemNotaFiscal> itensNota;
  public long getNumeroNota() {
       return numeroNota;
  }
  public void setNumeroNota(long numeroNota) {
       this.numeroNota = numeroNota;
  }
  public Date getDataHoraEmissao() {
       return dataHoraEmissao;
  }
  public void setDataHoraEmissao(Date dataHoraEmissao) {
       this.dataHoraEmissao = dataHoraEmissao;
  }
  public String getChaveAcesso() {
       return chaveAcesso;
  }
  public void setChaveAcesso(String chaveAcesso) {
       this.chaveAcesso = chaveAcesso;
  }

  public List<ItemNotaFiscal> getItensNota() {
       return itensNota;
  }
  public void setItensNota(List<ItemNotaFiscal> itensNota) {
       this.itensNota = itensNota;
  }
}
Listagem 1. Classe NotaFiscal

Nosso objetivo é usar esse cenário apenas para ter mais alternativas de criação de HQL's. Pedimos que você abstraia conceitos mais detalhados de Nota Fiscal, pois este não é nosso objetivo.

Note que na Listagem 1 temos o objeto Cliente e uma Coleção de “itensNota” do tipo ItemNotaFiscal. Fique atento a estes, pois serão usados no HQL.

O código da classe ItemNotaFiscal se encontra na Listagem 2.


public class ItemNotaFiscal {

  private NotaFiscal notaFiscal;
  private Produto produto;
  private int quantidade;
  private double valorDesconto;
  private double aliquotaIcms;
  public NotaFiscal getNotaFiscal() {
       return notaFiscal;
  }
  public void setNotaFiscal(NotaFiscal notaFiscal) {
       this.notaFiscal = notaFiscal;
  }
  public Produto getProduto() {
       return produto;
  }
  public void setProduto(Produto produto) {
       this.produto = produto;
  }
  public int getQuantidade() {
       return quantidade;
  }
  public void setQuantidade(int quantidade) {
       this.quantidade = quantidade;
  }
  public double getValorDesconto() {
       return valorDesconto;
  }
  public void setValorDesconto(double valorDesconto) {
       this.valorDesconto = valorDesconto;
  }
  public double getAliquotaIcms() {
       return aliquotaIcms;
  }
  public void setAliquotaIcms(double aliquotaIcms) {
       this.aliquotaIcms = aliquotaIcms;
  }
}
Listagem 2. Classe ItemNotaFiscal

O mesmo se aplica a Listagem 2, onde apenas os atributos mais importantes foram adicionados. Temos referência a dois objetos: NotaFiscal e Produto, onde um ItemNotaFiscal é de uma NotaFiscal e um ItemNotaFiscal está associado a um Produto.


public class Produto {
   
  private int codigo;
  private String descricao;
  private double valorUnitario;

  public int getCodigo() {
     return codigo;
  }
  public void setCodigo(int codigo) {
     this.codigo = codigo;
  }
  public String getDescricao() {
     return descricao;
  }
  public void setDescricao(String descricao) {
     this.descricao = descricao;
  }
  public double getValorUnitario() {
     return valorUnitario;
  }
  public void setValorUnitario(double valorUnitario) {
     this.valorUnitario = valorUnitario;
  }
}
Listagem 3. Classe Produto

A classe da Listagem 3 é uma classe “Fim”, não utilizando nenhuma outra classe, apenas outras utilizam ela.

NamedQueries

Estruturado o projeto com as classes necessárias podemos começar a trabalhar com o HQL.

Vamos relembrar o relacionamento das classes mostradas nas listagens anteriores: uma Nota Fiscal (NotaFiscal) possui diversos Itens de Nota Fiscal (ItemNotaFiscal), um ItemNotaFiscal possui um Produto e pertence a uma NotaFiscal.

Uma NamedQuery, como o nome já sugere, é uma Query nomeada, que foi criada com o intuito de possibilitar praticidade, produtividade e organização ao código. Uma query HQL comum pode estar em qualquer parte do código, mas uma NamedQuery só pode estar em dois lugares: em uma anotação ou em um XML.

NamedQuery no XML


<!-- nota_fiscal.hbm.xml -->
  <hibernate-mapping>
      <class name="br.com.projeto.NotaFiscal" table="nota_fiscal" ...>
          <id name="numeroNota" type="java.lang.Integer">
              <column name="numero_nota" />
              <generator class="identity" />
          </id>
          ...
      </class>
   
      <query name="findAllNotas">
          <![CDATA[from NotaFiscal nf]]>
      </query>
   
</hibernate-mapping>
Listagem 4. HQL NamedQuery no XML

Na Listagem 4 temos a definição do mapeamento da nossa classe NotaFiscal no arquivo nota_fiscal.hbm.xml. Não é o foco mostrar os detalhes de como realizar o mapeamento então colocamos apenas uma parte e pulamos direto para a parte onde temos o seguinte código:


<query name="findAllNotas">
    <![CDATA[SELECT nf FROM NotaFiscal nf]]>
</query>

Perceba que estamos nomeando nossa Query “from Nota Fiscal nf” com o nome “findAllNotas” (mais a frente veremos que podemos utilizar este nome para que o Hibernate consiga localizar a query de maneira simples, sem precisar redigitar toda a query).

O “CDATA” faz um papel importante no XML, pois ele evita que o parser não consiga validar o XML devido ao HQL contido dentro da , colocando o “CDATA” estamos explicitamente dizendo ao parser para ignorar o que há dentro dos colchetes. Se tivéssemos uma query mais elaborada com caracteres especiais, tais como “<”, “>” e etc., o parser ia mostrar erros devido à presença destes.

Outro ponto importante é que a tag deve estar depois da tag e não antes, e a lógica para tal organização é simples: as queries só podem ser definidas depois da definição dos atributos da classe, caso contrário, o hibernate não tem como saber se o atributo que você definiu no HQL existe de fato naquela determinada classe.


<!-- nota_fiscal.hbm.xml -->
<hibernate-mapping>
    <class name="br.com.projeto.NotaFiscal" table="nota_fiscal" ...>
        <id name="numeroNota" type="java.lang.Integer">
            <column name="numero_nota" />
            <generator class="identity" />
        </id>
        ...
    </class>
 
    <sql-query name="findAllNotas">
         <return alias="notaFiscal" class="br.com.projeto.NotaFiscal"/>
        <![CDATA[SELECT * from nota_fiscal nf]]>
    </sql-query>
 
</hibernate-mapping>
Listagem 5. SQL NamedQuery no XML

Um ponto muito interessante da NamedQuery que vemos na Listagem 5 é que ela aceita não apenas HQL, mas também SQL. Isso nos abre um leque de possibilidades imensas, transformando todas nossas consultas em uma abstração ainda maior, isso porque só referenciamos o nome dela, o seu conteúdo pode ser alterado em um ponto apenas, afetando todo sistema.

NamedQueries com anotação

Agora veremos como usar as namedqueries em anotações diretamente nas classes Java.

São duas as anotações que devemos conhecer para este contexto: @NamedQueries e @NamedQuery.

A anotação @NamedQueries é um contêiner para a anotação @NamedQuery, onde esta última é quem de fato armazena o nome da query e o seu conteúdo, como mostra a Listagem 6.


@Entity
@NamedQueries({
       @NamedQuery(name = "findAllNotas", query = "SELECT nf FROM NotaFiscal nf")       
})
@Table(name = "nota_fiscal")
public class NotaFiscal {
Listagem 6. NamedQuery HQL em anotação

A nossa classe NotaFiscal foi anotada com as duas anotações citadas e nomeamos a query com o mesmo nome que fizemos anteriormente no XML e também com o mesmo conteúdo. Assim como no XML, na anotação também é possível usar SQL e não apenas HQL, com algumas pequenas alterações.


@Entity
@NamedNativeQueries({
   @NamedNativeQuery(name = "findAllNotas", query = "SELECT * FROM nota_fiscal nf", 
   resultClass = NotaFiscal.class)  
})
@Table(name = "nota_fiscal")
public class NotaFiscal {
Listagem 7. NamedQuery SQL em anotação

A anotação da Listagem 7 agora possui a palavra “Native”, mudando para @NamedNativeQueries e @NamedNativeQuery, por tratar-se especificamente de uma query nativa em SQL.

Usando as NamedQueries

Vimos como criar agora veremos como usar essas queries e setar seus parâmetros, caso hajam. Veja na Listagem 8 como chamar a query findAllNotas em HQL (Listagem 6) no Hibernate.

Listagem 8. Chamando a query findAllNotas


Query query = session.getNamedQuery("findAllNotas");
List<NotaFiscal> returnList = (List<NotaFiscal>) query.getResultList();
Listagem 1. NOME

Criando NamedQueries HQL mais complexas

Agora vamos começar a complicar um pouco mais com queries mais complexas para exercitar o que aprendemos. Vamos verificar algumas peculiaridades provenientes do HQL.

As @NamedQueries aceitam várias @NamedQuery, então vamos usar este recurso para criar diversas queries para a nossa classe NotaFiscal.

Veja na Listagem 9 veremos NotaFiscal com seus itens e o produto de cada itemnotafiscal através do número da Nota.


@NamedQuery(name = "findNotaCompletaByNumero",
  query = "SELECT DISTINCT(nf) " +
    "FROM NotaFiscal nf " +
    "JOIN FETCH nf.itensNota item " +
    "JOIN FETCH item.produto " +
    "WHERE nf.numeroNota = :numeroNota")
Listagem 9. findNotaCompletaByNumero

Temos uma query com o nome findNotaCompletaByNumero com as seguintes características:

  • Usamos o “JOIN FETCH” para que os itens da nota sejam carregados dentro da Nota Fiscal, caso contrário, não conseguiríamos acessar os itens;
  • Fizemos um alias chamado “item” para depois fazer “JOIN FETCH item.produto” e trazer o produto carregado dentro do ItemNotaFiscal;
  • O uso do “FETCH” é necessário sempre que desejarmos carregar um objeto ou uma coleção de objetos dentro de outro objeto;
  • O “:numeroNota” indica que este é um parâmetro que possui o nome “numeroNota”, que não tem nenhuma relação direta com o campo “nf.NumeroNota”. Poderíamos mudar o nome do parâmetro para “:aquiEntraONumeroDaNota” e não teríamos problema algum;
  • O uso do DISTINCT logo após o SELECT, garante que não teremos duplicações de Notas no resultado. O Hibernate, ao tratar uma Collection dentro de um Objeto, retorna o objeto principal na quantidade de vezes que sua collection pede, e o DISTINCT elimina este problema. Por exemplo, se tivéssemos uma Notafiscal com três ItemNotaFiscal, então o retorno do Hibernate para a query acima seria três NotaFiscal, cada uma com três ItemNotaFiscal, sendo estas três NotaFiscal idênticos.

Agora na Listagem 10 buscamos os itens da notafiscal pelo número.


@NamedQuery(name = "findItensDaNotaByNumero",
  query = "SELECT item "
  + "FROM NotaFiscal nf "
  + "JOIN nf.itensNota item "
  + "JOIN FETCH item.produto "
  + "WHERE nf.numeroNota = :numeroNota")
Listagem 10. findItensDaNotaByNumero

Por que não usamos o “FETCH” desta vez? Porque não precisamos carregar os itensNota dentro de NotaFiscal, já que o retornado será o próprio ItemNotaFiscal e não a NotaFiscal. O JOIN ainda faz-se necessário, pois ele tem a mesma função do JOIN no SQL, que é realizar o produto cartesiano entre as tabelas, porém o fetch tem uma função extra de popular o objeto. Veja que continuamos usando o FETCH para o “item.produto”.

Na Listagem 11 veremos o retorno da nota e valor total.


@NamedQuery(name = "findTotalNotaByBean",
query = "SELECT new Map(nf.numeroNota as numero, " +
"sum(item.produto.valorUnitario * item.quantidade) as valorTotal) "
+ "FROM NotaFiscal nf "
+ "JOIN nf.itensNota item "
+ "JOIN item.produto "
+ "WHERE nf = :notaFiscalBean " +
"GROUP BY nf.numero")
Listagem 11. findTotalNotaByBean

Na Listagem 11, o “new Map” nos possibilita criar um objeto Map com atributos customizados. Neste caso estamos querendo retornar um Map com o número da nota e o valor total dela.

O “GROUP BY nf.numero” garante que apenas as notas de mesmo número serão agrupadas e então podemos somar o valor unitário de cada produto multiplicado pela quantidade que está possui em cada ItemNotaFiscal.

Outro ponto importante é o uso “WHERE nf = :notaFiscalBean”, pois o HQL aceita que seja passado um objeto como parâmetro e não apenas tipos primitivos, como int, char, boolean e etc.

Veja na Listagem 12 como ficou nossa classe NotaFiscal anotada com as três @NamedQuery citadas anteriormente.


package br.com.cardoso.teste;

import java.util.Date;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@NamedQueries({
  @NamedQuery(name = "findNotaCompletaByNumero", query = "SELECT DISTINCT(nf) "
    + "FROM NotaFiscal nf "
    + "JOIN FETCH nf.itensNota item "
    + "JOIN FETCH item.produto "
    + "WHERE nf.numeroNota = :numeroNota"),
  @NamedQuery(name = "findItensDaNotaByNumero", query = "SELECT item "
    + "FROM NotaFiscal nf " + "JOIN nf.itensNota item "
    + "JOIN FETCH item.produto "
    + "WHERE nf.numeroNota = :numeroNota"),
  @NamedQuery(name = "findTotalNotaByBean", query = "SELECT new Map(nf.numeroNota as numero, "
    + "sum(item.produto.valorUnitario * item.quantidade) as valorTotal) "
    + "FROM NotaFiscal nf "
    + "JOIN nf.itensNota item "
    + "JOIN item.produto "
    + "WHERE nf = :notaFiscalBean "
    + "GROUP BY nf.numero")

})
@Table(name = "nota_fiscal")
public class NotaFiscal {

  private long numeroNota;
  private Date dataHoraEmissao;
  private String chaveAcesso;
  private List<ItemNotaFiscal> itensNota;

  public long getNumeroNota() {
     return numeroNota;
  }

  public void setNumeroNota(long numeroNota) {
     this.numeroNota = numeroNota;
  }

  public Date getDataHoraEmissao() {
     return dataHoraEmissao;
  }

  public void setDataHoraEmissao(Date dataHoraEmissao) {
     this.dataHoraEmissao = dataHoraEmissao;
  }

  public String getChaveAcesso() {
     return chaveAcesso;
  }

  public void setChaveAcesso(String chaveAcesso) {
     this.chaveAcesso = chaveAcesso;
  }

  public List<ItemNotaFiscal> getItensNota() {
     return itensNota;
  }

  public void setItensNota(List<ItemNotaFiscal> itensNota) {
     this.itensNota = itensNota;
  }

}
Listagem 12. Classe NotaFiscal com três @NamedQuery

Vantagens e Desvantagens

A vantagem notável em se utilizar NamedQueries é a organização que se cria de forma natural no projeto, pois todos obrigatoriamente irão criar suas queries, sejam elas em HQL ou SQL, em um ponto centralizado. Se você quer achar as queries sobre a entidade Cliente, então provavelmente elas estarão na classe “Cliente.java”, assim como as queries que dizem respeito a entidade “Produto” devem estar na classe “Produto.java”. Uma outra vantagem é a reusabilidade e flexibilidade oferecidas pelo uso de NamedQueries, isso ocorre porque a qualquer momento podemos corrigir um erro na query “findAll” direto na sua anotação e todo sistema que faz referência ao nome “findAll” será afetado.

Por exemplo, imagine que você usa o “findAllNotas” em 120 pontos diferentes do Sistema e agora você precisa adicionar mais um FETCH para uma nova propriedade chamada “cliente” que está dentro da classe NotaFiscal: para isso você precisará apenas alterar a @NamedQuery de nome “findAllNotas” e todos que a referenciam já estarão corrigidos automaticamente.

Uma das desvantagens de utilizar-se NamedQueries é o fato de que se uma dessas queries estiverem erradas, a aplicação não inicia até a correção da mesma e isso pode ser muito problemático quando trabalhamos em equipe.

Por exemplo, imagine que você trabalha em um módulo A e um outro desenvolvedor trabalha no módulo B, ambos no mesmo projeto. O desenvolvedor do módulo B adicionou uma NamedQuery com erros ao projeto e você precisa testar o módulo A, mas quando tenta iniciar o servidor de aplicação é apresentando uma exceção apontando que a query do módulo B está errada. Você então terá que aguardar a correção dela para poder iniciar o servidor. Além disso, temos ainda um fato importante a ser notado que é a necessidade de reiniciar a aplicação (por aplicação entenda o servidor de aplicação) toda vez que uma query é alterada, ou seja, se tivermos uma alteração urgente em uma query no meio do expediente, o servidor de aplicação deverá ser reiniciado para que a mesma tenha efeito, e isso pode ser problemático do ponto de vista logístico, já que há operações que não podem parar em determinados instantes do dia.

Uma outra desvantagem no uso de NamedQueries é a poluição visual presente nas entidades (Beans). Uma entidade Cliente que geralmente tem muitas queries pode ficar muito “suja” devido à grande quantidade de informações, onde antes existia apenas get e set.

Com isso, neste artigo vimos como trabalhar com NamedQueries com HQL e SQL, dando maior enfoque ao HQL. Mostramos três queries com níveis de complexidade distintos para forçar a você leitor uma análise mais profunda e crítica do problema e não ficar apenas no “SELECT nf FROM NotaFiscal nf”. As três queries fazem uso de recursos distintos e que são essenciais para o dia a dia.

Por fim demonstramos as vantagens e desvantagens e usar NamedQueries e cabe somente a você avaliar se é viável o uso de tal recurso para o seu modelo de negócio. Há casos em que NamedQueries aumentam muito a produtividade, assim como há casos em que elas podem trazer o “caos” a um projeto.