Lazy do inglês “preguiçoso, lento” e Eager que significa “ansioso, impaciente” serão nossos objetos de estudo neste artigo. Explicaremos como tais conceitos se aplicam a persistência de dados utilizando frameworks de persistência em Java, como é o caso do Hibernate.

Antes de qualquer coisa é importante salientar que estes são conceitos a aplicados a computação em geral e não simplesmente a uma tecnologia, como o Hibernate. Isso significa, em outras palavras, que a aplicação deste artigo expande-se para as mais diversas áreas aplicadas principalmente a Engenharia de Software. Colocamos o foco no Hibernate apenas para exemplificar e tornar o artigo mais didático, assim poderemos trabalhar na prática com tal conceito.

Lazy Loading – Carregamento 'on demand'

Vamos começar estudando o Lazy loading que é o mais comum de ser encontrado nos mapeamentos realizados pelo Hibernate, afinal, todos os mapeamentos do Hibernate são Lazy por padrão, se você não especificar o tipo de “fetch” (busca) que será realizada.

Enfim, o Lazy Loading faz com que determinados objetos não sejam carregados do banco até que você precise deles, ou seja, são carregados 'on demand' (apenas quando você solicitar explicitamente o carregamento destes). Vamos imaginar um cenário ilustrativo para entender melhor tal técnica: você tem uma classe Funcionario com diversos campos, com vários relacionamentos com outros campos, ou seja, apenas um objeto Funcionario pode ter diversos dados. Veja a Listagem 1.

Listagem 1. Classe FuncionarioLazy

   @Entity
  @Table(name = "funcionario")
  public class FuncionarioLazy {
   
         @Column
         private String nome;
         
         @Column
         private long idade;
         
         @ManyToOne(fetch = FetchType.LAZY)
         @JoinColumn(name = "id_endereco")
         private Endereco endereco;
         
         @ManyToOne(fetch = FetchType.LAZY)
         @JoinColumn(name = "id_contato")
         private Contato contato;
         
         @ManyToOne(fetch = FetchType.LAZY)
         @JoinColumn(name = "id_funcao")
         private Funcao funcao;
         
         @ManyToOne(fetch = FetchType.LAZY)
         @JoinColumn(name = "id_departamento")
         private Departamento departamento;
         
         @OneToMany(fetch = FetchType.LAZY)      
         private List<HistoricoPonto> batidasDePonto;
   
         public String getNome() {
               return nome;
         }
   
         public void setNome(String nome) {
               this.nome = nome;
         }
   
         public long getIdade() {
               return idade;
         }
   
         public void setIdade(long idade) {
               this.idade = idade;
         }
   
         public Endereco getEndereco() {
               return endereco;
         }
   
         public void setEndereco(Endereco endereco) {
               this.endereco = endereco;
         }
   
         public Contato getContato() {
               return contato;
         }
   
         public void setContato(Contato contato) {
               this.contato = contato;
         }
   
         public Funcao getFuncao() {
               return funcao;
         }
   
         public void setFuncao(Funcao funcao) {
               this.funcao = funcao;
         }
   
         public Departamento getDepartamento() {
               return departamento;
         }
   
         public void setDepartamento(Departamento departamento) {
               this.departamento = departamento;
         }
   
         public List<HistoricoPonto> getBatidasDePonto() {
               return batidasDePonto;
         }
   
         public void setBatidasDePonto(List<HistoricoPonto> 
         batidasDePonto) {
               this.batidasDePonto = batidasDePonto;
         }
   }

Perceba que em nossos relacionamentos OneToMany e ManyToOne definimos uma propriedade “fetch” = FetchType.LAZY. Isso significa que ao realizarmos um “SELECT * from FuncionarioLazy” teremos todos os campos retornados, mas os campos com a propriedade FetchType.LAZY estarão nulos, mesmo que eles existam no banco. Essa é uma forma de não sobrecarregar sua aplicação com dados inúteis que não serão utilizados, tornando-a rápida e performática.

Voltando ao nosso cenário de exemplo, temos então a classe FuncionarioLazy acima, e precisamos de uma lista com todos os nomes e idades dos funcionários da empresa. Faremos um simples “SELECT * FROM FuncionarioLazy” e temos todos os dados em mãos, nosso objeto estará carregado apenas com o nome e idade, todos os outros campos estarão nulos.

Supondo agora que precisemos da propriedade “contato” dentro do objeto funcionário. Basta realizarmos um “getContato()” e o Hibernate irá realizar um consulta (transparente para você, ou seja, você não verá nenhum SQL no console) buscando o contato pelo Id, é algo que na verdade o Proxy faz mas esse não é objeto de estudo para este artigo, nosso foco é aprender o funcionamento do Lazy e do Eager Loading.

Pense, por exemplo, no campo “batidasDePonto” que possui todos os registros de ponto de um funcionário. Se o funcionário tiver 10 anos de empresa ele terá muitos registros (aproximadamente 9600 registros), então se você tiver 400 funcionários e cada funcionário tiver pelo menos 300 batidas de ponto (um número muito baixo de batidas apenas para você ver o quanto pode ser penoso um Lazy errado), fazendo o produto cartesiano disso você terá 120 mil registros apenas de batidasDePonto. Nesse momento é extremamente importante que esses registros fiquem como Lazy e sejam carregados apenas quando de fato necessários, se não você encontrará problemas como “stackOverflow”, “permGem” e assim por diante.

Eager Loading

Oposto ao Lazy Loading, o Eager Loading carrega os dados mesmo que você não vá utilizá-los, mas é óbvio que você só utilizará esta técnica se de fato você for precisar com muita frequência dos dados carregados.

O Eager pode ser feito de 2 formas: através de anotação na classe ou através do HQL, vamos ver as duas.

Usando Eager através de anotação

Este método não é recomendável, na verdade muito raro de ser visto. Pois se você anota uma propriedade do seu objeto como “Eager”, o mesmo será carregado mesmo que você não o faça através do HQL, isso pode ser ruim na maioria dos casos. Veja na Listagem 2 nossa classe FuncionarioEager.

Listagem 2. Classe FuncionarioEager

   @Entity
  @Table(name = "funcionario")
  public class FuncionarioEager {
   
         @Column
         private String nome;
         
         @Column
         private long idade;
         
         @ManyToOne(fetch = FetchType.EAGER)
         @JoinColumn(name = "id_endereco")
         private Endereco endereco;
         
         @ManyToOne(fetch = FetchType.EAGER)
         @JoinColumn(name = "id_contato")
         private Contato contato;
         
         @ManyToOne(fetch = FetchType.EAGER)
         @JoinColumn(name = "id_funcao")
         private Funcao funcao;
         
         @ManyToOne(fetch = FetchType.EAGER)
         @JoinColumn(name = "id_departamento")
         private Departamento departamento;
         
         @OneToMany(fetch = FetchType.LAZY)      
         private List<HistoricoPonto> batidasDePonto;
   
         public String getNome() {
               return nome;
         }
   
         public void setNome(String nome) {
               this.nome = nome;
         }
   
         public long getIdade() {
               return idade;
         }
   
         public void setIdade(long idade) {
               this.idade = idade;
         }
   
         public Endereco getEndereco() {
               return endereco;
         }
   
         public void setEndereco(Endereco endereco) {
               this.endereco = endereco;
         }
   
         public Contato getContato() {
               return contato;
         }
   
         public void setContato(Contato contato) {
               this.contato = contato;
         }
   
         public Funcao getFuncao() {
               return funcao;
         }
   
         public void setFuncao(Funcao funcao) {
               this.funcao = funcao;
         }
   
         public Departamento getDepartamento() {
               return departamento;
         }
   
         public void setDepartamento(Departamento departamento) {
               this.departamento = departamento;
         }
   
         public List<HistoricoPonto> getBatidasDePonto() {
               return batidasDePonto;
         }
   
         public void setBatidasDePonto(List<HistoricoPonto> 
           batidasDePonto) {
               this.batidasDePonto = batidasDePonto;
         }
        
  }

A estrutura da classe não muda em nada, e nem deve, você apenas muda o “fetch” para FetchType.LAZY. Perceba que não colocamos “Eager” no “batidasDePonto” pois como já explicamos isso traria diversos problemas de memória e lentidão a nossa aplicação. Você entenderá no próximo tópico por que não é aconselhável utilizar a anotação Eager direto na classe.

Usando Eager através de HQL

Esse é o mais comum e aconselhável método para trabalhar com propriedades que devem ser Eager. Suponha o HQL abaixo:

  “SELECT * FROM Funcionario f
           JOIN FETCH f.endereco
           JOIN FETCH f.contato
           JOIN FETCH f.funcao
           JOIN FETCH f.departamento”

O HQL abaixo faz exatamente a mesma função da Listagem 2, onde você explicitamente anotou as propriedades com Eager, ou seja, estamos carregando essas propriedades com Eager mas via HQL. A vantagem disso é notória, pois se quisermos agora carregar os Funcionários sem a propriedade “funcao” não precisamos mudar código da aplicação e fazer o “deploy” novamente, apenas criamos uma nova query. Além disso, se você defini Eager na anotação, fica óbvio que em toda parte da sua aplicação a propriedade será carregada independente do HQL que você use, ou seja, você não tem segunda opção.

Porém, podem existir casos que o Eager na anotação seja necessário, afinal senão houvesse casos ele não seria criado como opção para anotação de propriedades. O importante é você saber que pode utilizá-lo tanto como anotação, como HQL, mas de preferência sempre ao uso direto no HQL, assim sua aplicação torna-se mais flexível.

Com isso, o uso do Lazy e o do Eager Loading são assuntos extremamente importantes para manipulação de dados, isso porque o uso inadequado destas propriedades pode trazer muitos problemas para sua aplicação. Muitos profissionais apenas se deparam com tal assunto quando percebem que por motivos inesperados sua aplicação está lenta e travando com muita frequência, então estes percebem que alguma medida para tornar a aplicação mais performática deve ser tomada e com urgência.

Até a próxima!!