[RESOLVIDO] Erro no JPA com Hibernate OneToMany

Estou desenvolvendo uma aplicação de testes com JSF Facelet + JPA (hibernate) com mySQL e glassfish. O relacionamento entre as tabelas é o seguinte:
Categoria -> Produtos (OneToMany)
Produto -> Itens (OneToMany)
Endereço -> Itens (OneToMany)
Estou recebendo um erro quando o aplicativo vai carregar a página com detalhes de uma categoria selecionada, sendo que a categoria possui uma lista de produtos associados.
A mensagem é a seguinte:

exception

javax.servlet.ServletException: failed to lazily initialize a collection of role: com.pet.model.Category.productCollection, no session or session was closed

root cause

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.pet.model.Category.productCollection, no session or session was closed

A definição da bean categoria:

@Entity
@Table(name = "category")
@NamedQueries({
    @NamedQuery(name = "Category.findAll", query = "SELECT c FROM Category c"),
    @NamedQuery(name = "Category.findById", query = "SELECT c FROM Category c WHERE c.id = :id"),
    @NamedQuery(name = "Category.findByName", query = "SELECT c FROM Category c WHERE c.name = :name"),
    @NamedQuery(name = "Category.findByDescription", query = "SELECT c FROM Category c WHERE c.description = :description"),
    @NamedQuery(name = "Category.findByImageurl", query = "SELECT c FROM Category c WHERE c.imageurl = :imageurl")
})
public class Category implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @Column(name = "id")
    private Long id;
    @Basic(optional = false)
    @Column(name = "name")
    private String name;
    @Basic(optional = false)
    @Column(name = "description")
    private String description;
    @Column(name = "imageurl")
    private String imageurl;
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "categoryId")
    private Collection<Product> productCollection;

A definição da bean produto:

@Entity
@Table(name = "product")
@NamedQueries({
    @NamedQuery(name = "Product.findAll", query = "SELECT p FROM Product p"),
    @NamedQuery(name = "Product.findById", query = "SELECT p FROM Product p WHERE p.id = :id"),
    @NamedQuery(name = "Product.findByName", query = "SELECT p FROM Product p WHERE p.name = :name"),
    @NamedQuery(name = "Product.findByDescription", query = "SELECT p FROM Product p WHERE p.description = :description"),
    @NamedQuery(name = "Product.findByImageurl", query = "SELECT p FROM Product p WHERE p.imageurl = :imageurl")
})
public class Product implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @Column(name = "id")
    private Long id;
    @Basic(optional = false)
    @Column(name = "name")
    private String name;
    @Basic(optional = false)
    @Column(name = "description")
    private String description;
    @Column(name = "imageurl")
    private String imageurl;
    @JoinColumn(name = "category_id", referencedColumnName = "id")
    @ManyToOne(optional = false)
    private Category categoryId;
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "productId")
    private Collection<Item> itemCollection;

Alguém tem idéia do que pode estar acontecendo?

Encontrei uma solução parcial.
Coloquei um fetch=FetchType.EAGER em categoria e consegui mostrar os produtos relacionados:

public class Category implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @Column(name = "id")
    private Long id;
    @Basic(optional = false)
    @Column(name = "name")
    private String name;
    @Basic(optional = false)
    @Column(name = "description")
    private String description;
    @Column(name = "imageurl")
    private String imageurl;
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "category", fetch=FetchType.EAGER)
    private Collection<Product> productCollection;

Porém quando fiz o mesmo para produtos em relação a itens deu erro já no deploy:
[list]
java.lang.RuntimeException: javax.persistence.PersistenceException: [PersistenceUnit: facePetsPU] Unable to build EntityManagerFactory
at com.sun.enterprise.web.WebModuleListener.loadPersistenceUnits(WebModuleListener.java:193)
at com.sun.enterprise.web.WebModuleListener.lifecycleEvent(WebModuleListener.java:168)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:159)

Caused by: javax.persistence.PersistenceException: [PersistenceUnit: facePetsPU] Unable to build EntityManagerFactory
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:677)
at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:132)
at com.sun.enterprise.server.PersistenceUnitLoaderImpl.load(PersistenceUnitLoaderImpl.java:149)

Caused by: org.hibernate.HibernateException: cannot simultaneously fetch multiple bags
at org.hibernate.loader.BasicLoader.postInstantiate(BasicLoader.java:66)
at org.hibernate.loader.entity.EntityLoader.(EntityLoader.java:75)
at org.hibernate.loader.entity.EntityLoader.(EntityLoader.java:43)
at org.hibernate.loader.entity.EntityLoader.(EntityLoader.java:33)
at org.hibernate.loader.entity.BatchingEntityLoader.createBatchingEntityLoader(BatchingEntityLoader.java:103)

[/list]

http://www.guj.com.br/posts/list/138649.java
:wink:

olha cara eu tenho um palpite

isso pelo erro, por que cabei nem olhando os seus beans ainda, fora o fato de que quando você muda pra EAGER funciona.

o Hibernate fecha automaticamente a sessão quando vc da o commit, sendo assim, quando vc da o select de categorias ( e o commit em seguida) a conexão fecha, ai depois da o select dos produtos (comportamento do lazy) e a conexão ta fechada… por isso que funciona se você mudar pra EAGER (vai seleciona tudo usando um left outer join da vida, pegando as categorias e seus respectivos produtos).

o que esta escrito no link que o bronx mando corrige, pelo que eu dei uma lida rapida enquanto escrevia essa resposta, resumidamente:

crie um filtro, você vai abrir a sessão do hibernate no filtro, antes de da o select na pagina, coloca a sessão do hibernate na sessão do http (ou algum lugar que você possa acessar depois no controller), ai vc manda essa sessão pro dao fazer oq ue tiver que fazer, e o filtro depois da requisição deve fechar a sessão do hibernate (tirando essa responsabilidade do dao).

aliais o jsf tem o PhaseListener (ja que vc ta usandoo jsf), da uma olhada nesse exemplo de como fazer para um login… cria uma classe

http://www.rodrigolazoti.com.br/?p=56

eu indicaria cria uma classe sua que implemente a PhaseListener, e outra pra seta e pegar a sessão do hibernate da httpSession… ou algo do tipo

boa sorte

Pessoal, obrigado pelas respostas… foram úteis para o aprendizado.
Implementei o filtro conforme sugerido, o filtro foi acionado e até criou e recuperou as sessões armazenadas no httpSession de uma página de consulta para outra.
Ótima lição… mas o problema continuava… foi quando percebi que no método de pesquisa das entidades havia um “close” no EntityManager (gerado automaticamente pelo netbeans).
Bingo… comentei o código e tudo funcionou perfeitamente.

    public Item findItem(Long id) {
        EntityManager em = getEntityManager();
        try {
            return em.find(Item.class, id);
        } finally {
            System.out.println("era um close no em");
            //em.close();
        }
    }

Existe aí então uma diferença nas implementações de persistência do TopLink (default sugerido pelo netbeans) e do Hibernate que estou usando agora?

:wink:

Opa. Estou com um problema parecido.
Pelo que pude pesquisar e estudar e ler e coisa e tal, isso acontece porque o commit acontece e a sua view não foi renderizada ainda. Por isso, quando você comenta a linha do close funciona: ele não fecha a conexão e a view ainda pode renderizar. O problema é que você não fecha a conexão mesmo depois da view renderizada. Por isso do filtro que o maior_abandonado (preciso saber o nome deste usuário) falou (e é a ‘resposta’, ou uma das respostas corretas para o seu problema). Dependendo do que você usa (se usa Spring, EJB ou Serlvet ou coisa assim) fica bem fácil de configurar (é só colocar no web.xml, na verdade). Eu estou usando Blazeds com Java, Tomcat e Hibernate e não consegui achar uma solução, mas amanhã vou tentar mais um pouco e achar uma (se não tiver, vou ter que criar :p).

Um link que recomendo você a ler é o Open Session in View, do site do Hibernate. Eu cheguei nele depois de ver que muita gente teve esse mesmo problema.

Agora, se não fechar a conexão é algo aconselhável… Eu diria que não é.

Sobre a diferença, acho que existe sim. Eu acredito que o Hibernate seja mais completo do que qualquer outra implementação da JPA…

Abraço.