Entendendo o Lazy e o Eager Load da JPA

Um dos pontos mais importantes para se analisar em um software é a performance e otimizar as queries SQL ao maximo, pode resultar em um ganho considerável de performance. Levando isso em consideração vamos ver como funciona o Lazy e o Eager Load da JPA (Java Persistence API), pois o uso deles de maneira incorreta ocasiona uma perda enorme na performance de uma aplicação, forçando o banco de dados a querys desnecessárias.

Quando modelamos um sistema usando orientação à objetos criamos vários relacionamentos que podem ser @OneToOne, @ManyToOne, @OneToMany ou @ManyToMany e para o banco de dados cada relacionamento é uma nova tabela para consultar.

Suponha que temos duas entidades, Aluno e Matricula:

@Entity
public class Aluno {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String nome;
    @OneToOne
    private Matricula matricula;

    // getters e setters omitidos
}
@Entity
public class Matricula {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true, nullable = false)
    private String codigo;

    // getters e setters omitidos
}

Por padrão, quando o relacionamento está anotado com @OneToOne ou @ManyToOne ele é carregado de modo Eager ou seja, quando fizemos qualquer tipo de busca na entidade como por exemplo um find(Aluno.class, 1) ele será carregado junto com a entidade e nesse caso a JPA executará uma query só.

No exemplo acima colocamos anotação @OneToOne no atributo Matricula, faz sentido? Não, pois geralmente um aluno tem varias matriculas, então vamos alterar a anotação do relacionamento da classe Aluno para @OneToMany e o tipo para List:

@Entity
public class Aluno {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String nome;
    @OneToMany
    private List<Matricula> matriculas;

    // getters e setters omitidos
}
@Entity
public class Matricula {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true, nullable = false)
    private String codigo;

    // getters e setters omitidos
}

Por padrão quando o relacionamento está anotado com @OneToMany ou @ManyToMany ele é carregado de modo Lazy ou seja, quando fizemos qualquer tipo de busca na entidade como por exemplo um find(Aluno.class, 1) ele não será carregado junto com a entidade, somente quando executarmos o comando getMatriculas() o relacionamento será carregado.

Por o modo Lazy só carregar as informações quando executamos um getter fazer um aluno.getMatriculas().getCodigo() dentro de um for para pegar o código de todas as matriculas do aluno pode trazer problema de performance à aplicação, pois a JPA executará várias queries.

for(Aluno aluno : alunos) {
    for(Matricula matricula : aluno.getMatriculas(){
          System.out.println(matricula.getCodigo());
    }
}

O código de dentro do for será executado o tamanho da lista vezes, por exemplo se o tamanho da lista for N, o código será executado N vezes então a JPA executará N queries no banco de dados, podemos contar também a query para carregar a entidade. Esse problema é conhecido como [N+1] queries, um dos problemas a serem evitados no uso da JPA e do Hibernate.

Além do problema citado acima temos que tomar cuidado com a LazyInitializationException. No post Enfrentando a LazyInitializationException no Hibernate o Paulo Silveira explica como lidar com esse problema.

Como vimos neste post o carregamento das entidades de modo incorreto pode ocasionar perda de performance, portanto lembre-se de considerar a melhor estratégia para o carregamento dos seus relacionamentos.

16 Comentários

  1. Rafael Ponte 09/12/2014 at 11:11 #

    Muito bom o post, Felipe.

    Entender como o carregamento dos relacionamentos das entidades funciona é importante quando o assunto é performance e quantidade de hits no banco.

    Só um detalhe, mesmo em relacionamentos Eager o problema do Select N+1 ainda pode ocorrer.

    Um abraço!

  2. Felipe Oliveira 09/12/2014 at 14:08 #

    Rafael, muito obrigado por complementar o post.

  3. Bruno 12/12/2014 at 15:13 #

    Felipe, acho que seu exemplo não esta correto. Cada vez que vc fizer matricula.getCodigo(), o hibernate nao vai carregar a matricula de novo, pois vc ja carregou todas elas quando fez aluno.getMatriculas(). Entao foi uma query pro aluno e outra para as matriculas.
    O exemplo que vc queria nao seria um for sobre alunos? E para cada aluno fazer um aluno.getMatriculas()?

    Abs

  4. Felipe Oliveira 12/12/2014 at 22:11 #

    Isso mesmo Bruno muito obrigado! Vou arrumar o exemplo.

    Abraços

  5. Felipe Oliveira 12/12/2014 at 22:24 #

    Bruno, o exemplo está arrumado mais uma vez muito obrigado!

  6. Rafael 19/12/2014 at 12:30 #

    Boa tarde amigos,
    Pelo que entendi, o Lazy só carrega os dados (executa queries) quando solicitado, via método getAlgumaCoisa.
    Podendo-se usar de exemplo um funcionário possui N dependentes ao buscar esse funcionário só será executada a query buscando seus dados e não de seus dependentes, o que melhora a performance caso eu só queira ler os dados do funcionário, não me importando seus dependentes.
    Enquanto o Eager ao ler o objeto “principal”, executam-se N queries para preencher essa lista, o que se torna pesado.

    Qual a melhor opção para uma lista grande?

    Grato!

  7. Felipe Oliveira 19/12/2014 at 12:58 #

    Boa tarde Rafael,

    O Eager não executará N queries, executará somente uma query, porém carregará mais informações.

    A melhor opção para uma lista grande seria fazer Lazy com Batch.

    https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html

  8. Fred 06/01/2015 at 11:08 #

    Fala Felipe, ótimo post!

    Apenas uma observação, eu prefiro utilizar o método utilitário Hibernate.initialize(lista) ao invés de utilizar um for, acho que o código fica mais clean.

    E sim, para listas grande o ideal é quebrar em batch, reduzindo a quantidade de selects a um número ideal.

    Abs.

  9. Sidney Amaral 15/01/2015 at 12:11 #

    Ótimo assunto. Muitos desenvolvedores têm dificuldades com as nuances por trás de eager e lazy. Mas uma dúvida: como assim não faz sentido um aluno ter apenas uma matrícula?

  10. Felipe Oliveira 15/01/2015 at 15:45 #

    Boa tarde Sidney,

    um aluno pode sim ter uma única matricula, isso é só uma questão de modelagem de sistema, se para o seu sistema fizer sentido um aluno ter uma única matrícula você pode criar a Classe Aluno com uma única matrícula.

  11. Marcos 14/12/2015 at 09:37 #

    Ótimo post, mas fiquei com uma duvida, se não for informado o tipo de fetch (Lazy ou eager) na notação @OneToOne, @ManyToOne, @OneToMany, qual será por padrão ? lazy ou eager, pelo seu post eu entendi que seria eager, é isso mesmo?

  12. Felipe Oliveira 14/12/2015 at 11:01 #

    Oi Marcos, por padrão as annotations que terminam com One (@OneToOne e @ManyToOne) são eager e as que terminam com Many (@OneToMany e @ManyToMany) são lazy.

    Abraços

  13. Gavin King 02/04/2016 at 09:17 #

    É isso ae meu garoto, seu post fez com que eu compreendesse um conceito pelo qual estava com dúvida. Continue assim, sendo sucinto e explicito em seus posts .. abraços !

  14. Ivan Huo 19/10/2016 at 08:10 #

    Vaelu ai Felipe, o seu post foi de grande valia para mim. Gostaria de ver um acerca de join fetch, fetch, left join etc.. Desde já obrigado

Deixe uma resposta