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
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Vídeo