Olá Pessoal, tudo bom?
Hoje iremos falar sobre um erro de Mapeamento de Entidades JPA, mais especificamente da exception failed to lazily initialize a collection of role: [package].[classe].[collection], could not initialize proxy – no Session.
Descrição do Erro
Dentro da especificação do JPA toda Collection, ou seja, associações OneToMany ou ManyToMany, são carregadas de forma Lazy, ou seja,o JPA não traz a collection totalmente carregada, mas sim um objeto chamado PersistentBag. Esse objeto é uma lista que “indica” que existem objetos remotos. Caso a sessão com o banco de dados ainda esteja aberta, o JPA irá resgatar esse objetos e preencher a collection com os objetos reais. Senão estiver aberto, irá ocasionar o erro acima.
Assim vamos supor o seguinte exemplo do código abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package br.com.mauda.example.model; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; @Entity public class Artista implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String nome; @OneToMany(mappedBy = "artista") private List<Musica> musicas = new ArrayList<>(); public Artista(){ } //outros atributos, métodos e construtores } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package br.com.mauda.example.model; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Musica implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String nome; @ManyToOne @JoinColumn(name="ID_ARTISTA") private Artista artista; //outros atributos, métodos e construtores } |
Por fim, o próximo trecho de código representa uma instância de musica, a qual já foi persistida no banco de dados. Essa Essa instância foi recuperada do banco, via select. As linhas seguintes tentam acessar o atributo artista dentro dessa musica e depois as musicas desse artista.
1 2 3 4 5 6 7 8 |
//Obtem uma musica do banco de dados Musica musica = musicaBC.findById(35L); //Obtem o artista dessa musica - aqui não ocorre erro Artista artista = musica.getArtista(); //Tenta obter as musicas desse artista - org.hibernate.LazyInitializationException List<Musica> musicas = artista.getMusicas(); |
Como a conexão com o banco de dados não está aberta, ocorrerá a exception com a seguinte stack trace:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: br.com.mauda.example.model.Artista.musicas, could not initialize proxy – no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:569)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:188)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:548)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:126)
Solução que não deve ser utilizada!
Adicionar o fetch type EAGER ao seu Mapeamento, como mostra o código abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
package br.com.mauda.example.model; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; @Entity public class Artista implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String nome; @OneToMany(mappedBy = "artista", fetch=FetchType.EAGER) private List<Musica> musicas = new ArrayList<>(); public Artista(){ } //outros atributos, métodos e construtores } |
Ao adicionar o EAGER, sempre sua collection estará acordada, com todos as instâncias carregadas. Se o JPA / Hibernate não traz as instâncias em um primeiro momento, significa que ele está preservando memória e processamento de seu sistema. Assim não é interessante em toda a busca trazer todas as informações.
Solução
Existem diversas soluções para esse problema. Mas o ponto principal aqui é saber o momento de utilizar algum desses artifícios para “acordar” a collection requerida. Suponha o exemplo passado Artista-Musica. É necessário que em toda a busca de Artista trazer todas as musicas deste? Ou somente em uma parte específica do seu sistema que serão exibidas as musicas de um determinado artista? Assim todas as soluções aqui são válidas, MAS o interessante é ter a sabedoria de aplicar o carregamento dessas informações em um momento requerido.
Todas as soluções aqui devem ser aplicadas dentro de uma classe que realize a busca de informações no banco de dados, com a sessão ainda aberta, senão o JPA não irá conseguir realizar o carregamento correto das informações. No caso do nosso exemplo iremos criar método dentro da uma classe chamada MusicaDAO, que possui a responsabilidade de buscar e armazenar dados relativos a tabela de Musica.
1) Criar um método específico e dentro deste chamar algum método da collection que deseja ser “acordada”:
O exemplo a seguir mostra um método chamado findByIdComMusicas() o qual possui a responsabilidade de buscar uma musica, com o seu artista carregado com todas as suas musicas. Para realizar esse efeito, repare na linha 20, o qual chama o método size() da collection musicas. Ao realizar essa chamada, o JPA acaba tendo que acordar o objeto musicas, que por enquanto ainda é uma PersistentBag, para que este indique qual é o tamanho desta lista. Desta forma acaba carregando a lista com todas as instâncias de musica. Não é a forma mais perspicaz de fazer isso, pois fica atrelado a um determinado método específico de uma classe/interface específica.
O problema principal dessa abordagem é que serão realizados N selects a mais no banco para inicializar cada lista, aqui seria apenas uma chamada a mais , visto que restringimos a query pelo ID, mas se fosse uma busca de 10 musicas distintas, seriam 10 chamadas a mais, a fim de inicializar a lista de musicas de todos os artistas.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
package br.com.mauda.example.dao; import org.apache.logging.log4j.Logger; import org.hibernate.Query; import org.hibernate.Session; import br.com.mauda.example.dao.util.HibernateUtil; import br.com.mauda.example.model.Musica; public class MusicaDAO { protected static Logger LOGGER; public Musica findByIdComMusicas(Long id) { Session session = HibernateUtil.getSession(); try { Query byIdQuery = session.createQuery("FROM Musica as m WHERE m.id = :id"); byIdQuery.setParameter("id", id); Musica musica = (Musica) byIdQuery.uniqueResult(); musica.getArtista().getMusicas().size(); return musica; } catch (Exception e) { LOGGER.error("Exception no findByIdComMusicas", e); throw e; } finally { session.close(); } } } |
2) Criar um método específico e dentro deste chamar o método initialize() da classe org.hibernate.Hibernate passando a collection que deseja ser “acordada”:
Outra forma, que não fica restrito especificamente ao tipo da classe e método, é utilizar o método initialize() da classe org.hibernate.Hibernate. Só que nesse caso, não é necessário chamar o método size() e sim passar somente a collection, como mostra a linha 21. As mesmas chamadas a N selects do caso anterior se aplicam aqui.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package br.com.mauda.example.dao; import org.apache.logging.log4j.Logger; import org.hibernate.Hibernate; import org.hibernate.Query; import org.hibernate.Session; import br.com.mauda.example.dao.util.HibernateUtil; import br.com.mauda.example.model.Musica; public class MusicaDAO { protected static Logger LOGGER; public Musica findByIdComMusicas(Long id) { Session session = HibernateUtil.getSession(); try { Query byIdQuery = session.createQuery("FROM Musica as m WHERE m.id = :id"); byIdQuery.setParameter("id", id); Musica musica = (Musica) byIdQuery.uniqueResult(); Hibernate.initialize(musica.getArtista().getMusicas()); return musica; } catch (Exception e) { LOGGER.error("Exception no findByIdComMusicas", e); throw e; } finally { session.close(); } } } |
3) Criar um método específico e neste método criar uma query específica que possua JOIN FETCH com a tabela que deseja ser “acordada”:
Por fim, existe um Join especial dentro do JPA que é o JOIN FETCH, o qual inicializa um determinado bag de acordo com o JPAQL, como demonstra a linha 17. O ponto ruim aqui é que é necessário um conhecimento a mais em JPAQL e JOINS a fim de construir uma query que atenda a necessidade. O ponto positivo é que um select resolve tudo, não necessitando mais chamadas ao banco de dados.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package br.com.mauda.example.dao; import org.apache.logging.log4j.Logger; import org.hibernate.Query; import org.hibernate.Session; import br.com.mauda.example.dao.util.HibernateUtil; import br.com.mauda.example.model.Musica; public class MusicaDAO { protected static Logger LOGGER; public Musica findByIdComMusicas(Long id) { Session session = HibernateUtil.getSession(); try { Query byIdQuery = session.createQuery("FROM Musica as m JOIN FETCH m.artista as a JOIN FETCH a.musicas WHERE m.id = :id"); byIdQuery.setParameter("id", id); return (Musica) byIdQuery.uniqueResult(); } catch (Exception e) { LOGGER.error("Exception no findByIdComMusicas", e); throw e; } finally { session.close(); } } } |
O mesmo cenário pode ser aplicado a API de Criteria, tendo o mesmo resultado de se executar apenas 1 select no banco de dados.
finally{
Caso você achou mais alguma solução para essa exception, por favor deixe nos comentários abaixo!
Duvidas ou sugestões? Deixe seu feedback! Isso ajuda a saber a sua opinião sobre os artigos e melhorá-los para o futuro! Isso é muito importante!
Até um próximo post!
1 |
Leave a Reply