Lazy e Eager Loading com Hibernate

Veja neste artigo como funcionam as técnicas de Lazy Loading e Eager Loading aplicados ao Framework Hibernate em Java.

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!!

Artigos relacionados