JPA - dificuldades com relacionamento ManyToMany

14 respostas
Ady_Junior

Opa galera blz?

Travei em uma parte de um projeto, se alguem poder me ajudar ficarei grato.

Minhas duvidas são as seguintes :

1 )
Tenho uma classe TAREFA e outra FUNCIONÁRIO.

E o relacionamento entre elas é de MUITOS PARA MUITOS.

Na hora de recuperar os dados da classe TAREFA uma exceção é lançada -> LazyInitializationException.

Isso estava acontecendo tbm com a classe FUNCIONÁRIO, só que eu adicionei um
fetch= FetchType.EAGER
na anotação @ManyToMany , e isso resolveu o problema.

Pensei que a solução da classe TAREFA era fazer a msm coisa, mais não era pois outro tipo de exceção é lançada -> MultipleBagFetchException

Qual seria a solução para este problema ?

2)
Em um relacionamento muitos para muitos o correto é a criação de 3 tabelas, mais o meu projeto ta criando 4 tabelas no banco de dados
que são :

tarefa
funcionário
tarefa_funcionário
funcionário_tarefa

Eu anotei minha classe de forma errada ?

Segue abaixo as msgs de exceções na pilha e o código para os 2 casos :

EXCEÇÃO .LazyInitializationException

Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: civil.modelo.entidade.Tarefa.funcionarios, no session or session was closed
	at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:383)
	at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:375)
	at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:368)
	at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:111)
	at org.hibernate.collection.PersistentBag.iterator(PersistentBag.java:272)
	at civil.modelo.entidade.TesteControle.recuperaTarefa(TesteControle.java:72)
	at civil.modelo.entidade.TesteControle.executa(TesteControle.java:147)
	at civil.modelo.entidade.Main.main(Main.java:49)
EXCEÇÃO MultipleBagFetchException
Exception in thread "main" javax.persistence.PersistenceException: [PersistenceUnit: PersistenciaCivilPU] Unable to build EntityManagerFactory
	at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:915)
	at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:57)
	at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:48)
	at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:32)
	at civil.modelo.entidade.TesteControle.<init>(TesteControle.java:19)
	at civil.modelo.entidade.Main.main(Main.java:49)
Caused by: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
	at org.hibernate.loader.BasicLoader.postInstantiate(BasicLoader.java:94)
	at org.hibernate.loader.entity.EntityLoader.<init>(EntityLoader.java:119)
	at org.hibernate.loader.entity.EntityLoader.<init>(EntityLoader.java:71)
	at org.hibernate.loader.entity.EntityLoader.<init>(EntityLoader.java:54)
	at org.hibernate.loader.entity.BatchingEntityLoader.createBatchingEntityLoader(BatchingEntityLoader.java:133)
	at org.hibernate.persister.entity.AbstractEntityPersister.createEntityLoader(AbstractEntityPersister.java:1914)
	at org.hibernate.persister.entity.AbstractEntityPersister.createEntityLoader(AbstractEntityPersister.java:1937)
	at org.hibernate.persister.entity.AbstractEntityPersister.createLoaders(AbstractEntityPersister.java:3205)
	at org.hibernate.persister.entity.AbstractEntityPersister.postInstantiate(AbstractEntityPersister.java:3191)
	at org.hibernate.persister.entity.SingleTableEntityPersister.postInstantiate(SingleTableEntityPersister.java:728)
	at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:348)
	at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1872)
	at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:906)

CLASSE TAREFA

@Entity
@Table(name="tarefa")
public class Tarefa implements Serializable {
    
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name="id_Tarefa", unique=true, nullable=false)
    private Long id;    
    
    @Column(length=100)
    private String nomeTarefa;
    
    @Temporal(javax.persistence.TemporalType.DATE)
    private Date horarioInico;
   
    @ManyToMany
    private List<Funcionario> funcionarios;
//getter e setters
}

CLASSE FUNCIONÁRIO

@Entity
@Table(name="funcionario")
public class Funcionario implements Serializable {
    
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name="id_funcionario", unique=true, nullable=false)
    private Long id;
    
    @Column(length=255, unique=true, nullable=false)
    private String nome;
    
    @ManyToMany(fetch= FetchType.EAGER)
    @JoinTable(
                name="funcionario_tarefa",
                joinColumns={
                    @JoinColumn(name="id_funcionario",
                                referencedColumnName="id_funcionario"
                                )
                            },
                inverseJoinColumns={
                    @JoinColumn(name="id_tarefa",
                                referencedColumnName="id_tarefa"
                                )
                    }
            )
    private List<Tarefa> tarefas;

//gatters e setters
}

METODO DE QUE PERSISTE FUNCIONARIO

public void create(Funcionario funcionario) {
        if (funcionario.getTarefas() == null) {
            funcionario.setTarefas(new ArrayList<Tarefa>());
        }
        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            List<Tarefa> attachedTarefas = new ArrayList<Tarefa>();
            for (Tarefa tarefasTarefaToAttach : funcionario.getTarefas()) {
                tarefasTarefaToAttach = em.getReference(tarefasTarefaToAttach.getClass(), tarefasTarefaToAttach.getId());
                attachedTarefas.add(tarefasTarefaToAttach);
            }
            funcionario.setTarefas(attachedTarefas);
            em.persist(funcionario);
            for (Tarefa tarefasTarefa : funcionario.getTarefas()) {
                tarefasTarefa.getFuncionarios().add(funcionario);
                tarefasTarefa = em.merge(tarefasTarefa);
            }
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

METODO QUE PERSISTE TAREFA

public void create(Tarefa tarefa) {
        if (tarefa.getFuncionarios() == null) {
            tarefa.setFuncionarios(new ArrayList<Funcionario>());
        }
        EntityManager em = null;
        try {
            em = getEntityManager();
            em.getTransaction().begin();
            List<Funcionario> attachedFuncionarios = new ArrayList<Funcionario>();
            for (Funcionario funcionariosFuncionarioToAttach : tarefa.getFuncionarios()) {
                funcionariosFuncionarioToAttach = em.getReference(funcionariosFuncionarioToAttach.getClass(), funcionariosFuncionarioToAttach.getId());
                attachedFuncionarios.add(funcionariosFuncionarioToAttach);
            }
            tarefa.setFuncionarios(attachedFuncionarios);
            em.persist(tarefa);
            for (Funcionario funcionariosFuncionario : tarefa.getFuncionarios()) {
                funcionariosFuncionario.getTarefas().add(tarefa);
                funcionariosFuncionario = em.merge(funcionariosFuncionario);
            }
            em.getTransaction().commit();
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

Foi mal pelo post gigante, mais faz tempo que eu travei nessa parte e não achei soluções, vlw galera.

14 Respostas

drsmachado

Pesquise pelo hibernate.initialize. Provavelmente irá resolver teu problema.

Ady_Junior

Vlw pela dica drsmachado.

Na busca sobre a informação eu vi que teria que usar um Hibernate.initialize() antes de fexar a sessão.

Mais meu metodo de procura não me da essa opção, esse metodo foi gerado pelo netbeans, JPAController:

public Tarefa findTarefa(Long id) { EntityManager em = getEntityManager(); try { return em.find(Tarefa.class, id); } finally { em.close(); } }

alguma sugestão?

obrigado.

Masami

conseguiu resolver o problema? estou passando pelo mesmo…

Ady_Junior

Masami

Resolvi sim, qual dos 2 problemas que eu citei que vc tbm teve ?

Sua classe DAO do JPA foi gerada pelo netbeans ?

Masami

os dois, o LazyInitializationException ocorre quando clico na pagina 2 de um p:dataTable, dai se eu mudo o mapeamento para EAGER ocorre MultipleBagFetchException.

Sim, são Facade geredos pelo netbeans para EJB+JPA que eu modifiquei para Spring 3+JPA

Ady_Junior

Então é o seguinte, o seu metodo de busca deve estar assim ?

public Tarefa findTarefa(Long id) {  
        EntityManager em = getEntityManager();  
        try {  
            return em.find(Tarefa.class, id);  
        } finally {  
            em.close();  
        }  
    }

Vc deve deixar seu atributo que representa o ManyToMany deve ser mapeado assim -> @ManyToMany(fetch= FetchType.LAZY)

Isso pq vc recuperara a lista de objetos depois da busca principal, então o metodo fica assim :

public Tarefa buscar(Long id){
        Tarefa t = em.find(Tarefa.class, id);
        
        Query q = em.createQuery("select Ta.funcionarios from Tarefa as Ta");
        
        t.setFuncionarios(q.getResultList());
        
        return t;
    }}

Faz uma busca da entidade, depois busca a lista do outro Objeto e seta ele.

Deu pra entender ?

Tem mais algum problema acontecendo ?

Eu to com alguns problemas aki tbm, diferentes desse , talvez vc possa me ajudar tbm kk.

vlw

Ady_Junior

Acabei de descobrir uma maneira bem mais facil aqui lendo sobre JPA.

O método FIND () tem comportamento EAGER e o GET R EFERENCE () tem comportamento LAZY.

então é só mapear o atributo como LAZY e o metodo fica :

public Funcionario buscar(Long id){
        Funcionario f = em.getReference(Funcionario.class, id);

        return f;
    }

_________________________________________
O meu metodo de atualizar não esta funcionando o seu está ?

e nele vc usa merge ?

Masami

meu metodo é diferente, mas entendi como vc resolveu, só que eu não preciso fazer isso, o Spring carrega “automaticamente” os objetos com FetchType.LAZY pra mim.

so que estou tendo um problema ao usar a paginação do p:dataTable do PrimeFaces, quando clico nas 2ª,3ª,… pagina é lançado a exceção:

e como não consegui resolver este problema que acredito ser do Spring, mudei o mapeamento para FetchType.EAGER.
mas acho inviavel fazer de uma maneira parecida com a que vc fez, pois minha busca retorna um ‘List’ e não apenas um ‘ServicosExec’

Masami

uso merge, veja meu "dao":

@Transactional
public abstract class AbstractFacade<T> {

    private Class<T> entityClass;
    private static final Logger logger = Logger.getLogger(AbstractFacade.class.getName());

    public AbstractFacade(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    protected abstract EntityManager getEntityManager();

    public void create(T entity) {
        getEntityManager().persist(entity);
        logger.log(Level.INFO, "{0}: {1}", new Object[]{entityClass.getName(), entity.toString()});
    }

    public void edit(T entity) {
        getEntityManager().merge(entity);
        logger.log(Level.INFO, "{0}: {1}", new Object[]{entityClass.getName(), entity.toString()});
    }

    public void remove(T entity) {
        getEntityManager().remove(getEntityManager().merge(entity));
        logger.log(Level.INFO, "{0}: {1}", new Object[]{entityClass.getName(), entity.toString()});
    }

    public T find(Object id) {
        logger.log(Level.INFO, "{0}: {1}", new Object[]{entityClass.getName(), id.toString()});
        return getEntityManager().find(entityClass, id);
    }

    public List<T> findAll() {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        logger.log(Level.INFO, entityClass.getName());
        return getEntityManager().createQuery(cq).getResultList();
    }

    public List<T> findRange(int[] range) {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        q.setMaxResults(range[1] - range[0]);
        q.setFirstResult(range[0]);
        logger.log(Level.INFO, entityClass.getName());
        return q.getResultList();
    }

    public int count() {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        javax.persistence.criteria.Root<T> rt = cq.from(entityClass);
        cq.select(getEntityManager().getCriteriaBuilder().count(rt));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        logger.log(Level.INFO, entityClass.getName());
        return ((Long) q.getSingleResult()).intValue();
    }
}
@Repository
@Transactional
public class ServicosExecFacade extends AbstractFacade<ServicosExec> {

    protected EntityManager entityManager;
    private static final Logger logger = Logger.getLogger(ServicosExecFacade.class.getName());

    @PersistenceContext(unitName = "gsmPU")
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Override
    public EntityManager getEntityManager() {
        return entityManager;
    }

    public ServicosExecFacade() {
        super(ServicosExec.class);
    }
    
    @Transactional
    public Date findMaxDataExecucao() {
        Query query = getEntityManager().createNamedQuery("ServicosExec.findMaxDataExecucao");
        logger.log(Level.INFO, query.getSingleResult().toString());
        return (Date) query.getSingleResult();
    }
}
Ady_Junior

Seu problema é bem mais complexo :S

O método que vc ta usando que ta lançando exceção é esse aqui ?

public List<T> findAll() {  
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();  
        cq.select(cq.from(entityClass));  
        logger.log(Level.INFO, entityClass.getName());  
        return getEntityManager().createQuery(cq).getResultList();  
    }

Um método como esse não resolveria seu problema não ?

public List<Pessoa> umMetodoQualquer() {
Query query = manager.createNamedQuery("SELECT p FROM Pessoa p");
List<Pessoa> pessoas = query.getResultList();

return pessoas;
}

O seu merge ta funcionando cara?

Masami

o merge esta funcionando perfeitamente.

o metodo não lança a exceção, o problema é saber trabalhar da maneira correta com FetchType.LAZY/FetchType.EAGER e/ou PrimeFaces+Spring

Masami
Ady_Junior:
Um método como esse não resolveria seu problema não ?
public List<Pessoa> umMetodoQualquer() {
Query query = manager.createNamedQuery("SELECT p FROM Pessoa p");
List<Pessoa> pessoas = query.getResultList();

return pessoas;
}

pior q não, usando criteria ou JPQL o problema continua

Ady_Junior

Que foda :S

Kdê os caras que sacam de JPA do GUJ ? kk

Eu tenho uma apostila féra de JPA aqui, to tirando minhas duvidas por ela, e tenho uma de JSF com JPA tbm, vc quer que eu te mande elas ?

se quiser passa seu email.

flw ;]

Masami

[email removido]
mande ai pra eu ver.

vlw

Criado 10 de julho de 2011
Ultima resposta 18 de jul. de 2011
Respostas 14
Participantes 3