Uma técnica muito utilizada na persistência de dados com Hibernate ou JPA é o uso de Proxys que torna o uso de Lazy loading's possíveis. Neste artigo explicaremos como funcionam os Proxys juntamente com Hibernate e porque eles são necessários.

Se quiser ver como configurar o framework hibernate, não deixe de conferir nesse link.

Antes de tudo, é importante entender o Lazy Loading é o carregamento tardio de determinadas propriedades dentro do objeto, ou seja, de forma sucinta, é quando temos um objeto com muitos relacionamentos, sejam eles OneToMany, ManyToOne ou ManyToMany, e desejamos optar por não carregar essas propriedades ao retornar o objeto do banco. Porém, quase que “magicamente”, ao realizar um “get()” na propriedade Lazy do objeto você consegue ver todos os dados mesmo sem ter carregado estes do banco em primeira instância, pois é exatamente neste ponto que explicaremos o uso do Proxy.

Para exemplificar o uso de Proxys com Hibernate, veja a Figura 1.

Figura 1. Uso de proxys

E logo em seguida temos a classe Person na Listagem 1.

Listagem 1. Classe Person

  import java.util.Set;
   
  public class Person {
    private Long id;
    private String name;
    private Person boss;
    private Set employees;
    
    public Long getId() {
         return id;
    }
    public void setId(Long id) {
         this.id = id;
    }
    public String getName() {
         return name;
    }
    public void setName(String name) {
         this.name = name;
    }
    public Person getBoss() {
         return boss;
    }
    public void setBoss(Person boss) {
         this.boss = boss;
    }
    public Set getEmployees() {
         return employees;
    }
    public void setEmployees(Set employees) {
         this.employees = employees;
    }
  }

O que ocorre se carregarmos a linha “Vincent” através de um HQL? O Hibernate irá mapear o id da tabela com o id da classe Person e name da tabela com o name da classe Person, mas ao tentar fazer o mesmo com a propriedade “boss” ele encontrará um problema: a propriedade boss é do tipo “Person”, então exatamente neste momento o Hibernate irá criar um novo objeto Person e setar apenas o id deste objeto, referente à propriedade “boss”.

Então o que temos até o momento? Dois objetos Person, sendo um objeto populado com o id = 2 e name = “Vincent”, e temos também outro objeto Person populado apenas com o id = 1. Quando você fizer “person.getBoss()” , o Hibernate “por trás dos panos” irá capturar o id, doa propriedade boss (que não foi inicializada) e busca no banco, através deste id os valores referente a ele, como se ele fizesse algo assim: “SELECT * FROM Person WHERE id = 1”.

O cenário descrito é um mecanismo de Proxy e todo esse processo é feito por ele, pois ele é o responsável por tratar desses objetos não inicializados, evitando assim um possível “LazyInitializationException”.

Entendendo mais a fundo o Proxy

Dada a introdução, você já deve ter entendido como funciona o Proxy, e que é ele o responsável por fazer a “mágica” de carregar seus componentes que estão com Lazy Initialization. Mas vamos ser mais específicos e entender o funcionamento técnico de tal mecanismo.

Primeiramente entenda que há diversos tipos de Proxys, então vamos trabalhar aqui com o CGLIB para efeito de estudos, mas você pode utilizar outro como o Javassist. Então quando você invoca o método get da propriedade que deve ser carregada, o Hibernate através do proxy CGLIB cria uma subclasse dinâmica da sua classe, em nosso caso uma subclasse de Person. Veja na Figura 2 como funciona.

Figura 2. Classe Person dinamicamente criada

Perceba que a nossa propriedade “boss” é do tipo “Person$$EnhancerByCGLIB”, ou seja, ela foi inicializada pelo Proxy CGLIB e é ele quem a gerencia. Perceba que ainda na propriedade boss tem-se a propriedade id que diz respeito ao id da nossa Person relacionada a propriedade boss. Enfim, temos a propriedade target que é onde ficará a nossa propriedade boss após carregada, ou seja, quando você realizar “getBoss()” a propriedade target será carregada com os valores reais de Person vindas diretamente do banco. Interessante não? Na verdade tudo é controlado através dessa classe dinâmica, que tem flags necessárias para dizer quem deve carregar, quando, onde e como.

O mecanismo de proxy é muito importante, na verdade essencial para o bom funcionamento da sua aplicação e entender ele é mais do que necessário. Infelizmente muito só buscam aperfeiçoamento em tal assunto quando se deparam com erros como “LazyInitializationException” e não tem ideia por onde começar, pois na maioria dos fóruns e artigos você encontrará referências a proxys apontando como principais causadores de tal erro, mas como resolver sem saber o que é Proxy? Por isso este artigo é tão importante.

Cuidados com Proxy

Em alguns momentos você pode se deparar com problemas comuns ao utilizar Proxys.Nesta seção descreveremos como contorná-los baseado no conhecimento adquirido nas seções anteriores.

Um erro muito comum é no uso do equals() para realizar comparações entre objetos, então vamos supor a implementação da Listagem 2 do método equals() para nossa classe Person.

Listagem 2. Implementação do método equals()

    @Override
    public boolean equals(Object obj) {
      if (obj == null) {
        return false;
      }
      if (obj == this) {
        return true;
      }
      if (!(obj instanceof Person)) {
        return false;
      }
      return name.equals((Person)obj).name);
    }

Supondo que a variável “obj” seja nossa propriedade “boss”, você percebeu acima que todos os valores são nulos, apenas o que está dentro da propriedade target será válido, pois é assim que o proxy trabalha. Então ao tentar realizar a comparação acessando os campos diretamente (obj.name) você encontrará um problema, pois obj.name é nulo, mas obj.getName() não é nulo, mas porque ? O proxy, como falado anteriormente, carrega sua classe ao usar os métodos getters e setters, então usá-los diretamente irá causar problemas na sua aplicação. Opte por usar sempre os get ou set nessas ocasiões.

Diferenças entre load() e get()

Além de utilizar o get() para carregar as propriedades do seu objeto, você pode optar por utilizar o load() que também as carregará, mas de forma distinta, vamos ver nessa seção a diferença entre ambos.

Quando você utiliza o load() você tem que ter certeza que o objeto existe no banco, caso contrário o mesmo retornará uma exception. Além disso, o load() retorna um proxy apenas o id populado e o mesmo só será verdadeiramente carregado quando você fizer uso do proxy. Por outro lado, o get() retorna nulo caso o objeto não exista no banco e no momento do “session.get()” o objeto é consultado e retornado por completo na propriedade. Veja nas Listagens 3 e 4 os exemplos de get() e load().

Listagem 3. Usando get()

   Stock stock = (Stock)session.get(Stock.class, new Integer(2));
             StockTransaction stockTransactions = new StockTransaction();
             //set stockTransactions detail
             stockTransactions.setStock(stock);        
             session.save(stockTransactions);
             
             Saída
             Hibernate:
                   select ... from myproject.stock stock0_
                   where stock0_.STOCK_ID=?
               Hibernate:
                   insert into myproject.stock_transaction (...)
                   values (?, ?, ?, ?, ?, ?)

Listagem 4. Usando load()

  Stock stock = (Stock)session.load(Stock.class, new Integer(2));
             StockTransaction stockTransactions = new StockTransaction();
             //set stockTransactions detail
             stockTransactions.setStock(stock);        
             session.save(stockTransactions);
             
             Saída:
             Hibernate:
                   insert into myproject.stock_transaction (...)
                   values (?, ?, ?, ?, ?, ?)

Então a diferença fica bem nítida com o exemplo acima, na verdade o load() não faz a consulta no banco procurando pelo objeto, ele apenas cria uma instância do mesmo carregada com o ID necessário, diferente do get() que vai até o banco procurar todas as propriedades do objeto.

Listagem 5. Exception no Load

  Stock stock = (Stock)session.load
  (Stock.class, new Integer(100)); //proxy
   
   //initialize proxy, no row for id 100, 
   throw ObjectNotFoundException
   System.out.println(stock.getStockCode());
   
  org.hibernate.ObjectNotFoundException: 
  No row with the given identifier exists:
  [com.myproject.common.Stock#100]
  

Na Listagem 5 criamos o objeto através do load, passando o ID = 100. Mas ao tentar realizar o get() neste objeto você receberá um exception, pois o objeto não existe no banco.

Este assunto está ligado inteiramente ao proxy, pois tudo depende dele, seja usando load() ou get(), o que mudará é o possível retorno de ambos os casos.

Com isso, o artigo explicou o uso de Proxys no Hibernate, assunto muito importante para tratamento de Lazy initializations. É muito importante entender o funcionamento de tal mecanismo, e saber também que este mecanismo não se trata apenas de uso no Hibernate. Este na verdade é um “Design Pattern” que pode ser aplicado nas mais diversas áreas e foi usado como principal mecanismo de lazy loading no Hibernate, ideia genial ao se aproveitar conceitos já existentes e consistentes. Além disso, é muito importante ficar de olho nas “armadilhas” que o uso do proxy indevido pode trazer, pois uma falha dessas (citadas acima) pode trazer grandes problemas e dores de cabeça no projeto de um software.