Problema de Design com JPA... :(

Estou passando por um problema, provavelmente de design, e gostaria de alguma sugestão. Estou implementando um sistema utilizando JPA e EJB3, até aí tudo bem, vou ter diversas classes que serão persistidas em banco através do uso de JPA. Abaixo seguem duas dessas classes, que é onde está o foco do meu problema:

Campanha

package org.it4services.contactprovider.to;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.OneToMany;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;

import static javax.persistence.FetchType.EAGER;
import static javax.persistence.CascadeType.ALL;

@Entity
public class Campanha implements Serializable {

	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue
	private Integer id;
	private String nome;

	@OneToMany(mappedBy = "campanha", cascade = ALL)
	private List<Tabulacao> tabulacaoList = new ArrayList<Tabulacao>();

	@ManyToMany(fetch=EAGER)
	@JoinTable(inverseJoinColumns = @JoinColumn(name="id_usuario", referencedColumnName = "id"), joinColumns = @JoinColumn(name="id_campanha", referencedColumnName = "id"))
	private List<Usuario> usuarioList = new ArrayList<Usuario>();

	private Boolean ativo;

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getNome() {
		return nome;
	}

	public void setNome(String nome) {
		this.nome = nome;
	}

	public List<Tabulacao> getTabulacaoList() {
		return tabulacaoList;
	}

	public void setTabulacaoList(List<Tabulacao> tabulacaoList) {
		this.tabulacaoList = tabulacaoList;
	}

	public List<Usuario> getUsuarioList() {
		return usuarioList;
	}

	public void setUsuarioList(List<Usuario> usuarioList) {
		this.usuarioList = usuarioList;
	}

	public Boolean getAtivo() {
		return ativo;
	}

	public void setAtivo(Boolean ativo) {
		this.ativo = ativo;
	}
	
	public String toString() {
		return nome;
	}

}

Tabulação

package org.it4services.contactprovider.to;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;

import org.it4services.contactprovider.type.TipoTabulacao;
import javax.persistence.ManyToOne;
import javax.persistence.JoinColumn;
import javax.persistence.Column;

@Entity
public class Tabulacao implements Serializable {

	private static final long serialVersionUID = 1L;
	
	@Id
	@GeneratedValue
	private Integer id;
	
	@ManyToOne
	@JoinColumn(name="id_campanha", referencedColumnName = "id")
	private Campanha campanha = new Campanha();
	
	@ManyToOne
	@JoinColumn(name="id_status", referencedColumnName = "id")
	private Status status = new Status();
	
	@Column(name="tipo_tabulacao")
	private TipoTabulacao tipoTabulacao = TipoTabulacao.COMUM;
	
	@Column(name="tempo_agendamento_automatico")
	private Integer tempoAgendamentoAutomatico;
	
	private Boolean visivel;
	
	private Boolean ativo;

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}
	
	public void setCampanha(Campanha campanha) {
		this.campanha = campanha;
	}

	public Campanha getCampanha() {
		return campanha;
	}

	public Status getStatus() {
		return status;
	}

	public void setStatus(Status status) {
		this.status = status;
	}

	public TipoTabulacao getTipoTabulacao() {
		return tipoTabulacao;
	}

	public void setTipoTabulacao(TipoTabulacao tipoTabulacao) {
		this.tipoTabulacao = tipoTabulacao;
	}

	public Integer getTempoAgendamentoAutomatico() {
		return tempoAgendamentoAutomatico;
	}
	
	public void setTempoAgendamentoAutomatico(Integer tempoAgendamentoAutomatico) {
		this.tempoAgendamentoAutomatico = tempoAgendamentoAutomatico;
	}
	
	public Boolean getVisivel() {
		return visivel;
	}
	
	public void setVisivel(Boolean visivel) {
		this.visivel = visivel;
	}

	public Boolean getAtivo() {
		return ativo;
	}

	public void setAtivo(Boolean ativo) {
		this.ativo = ativo;
	}
	
}

Uma campanha possui N tabulações.

Para a implementação dos EJB’s, eu criei uma interface base:

BaseSessionFacade

package org.it4services.contactprovider.service.facade;

import java.util.List;

public interface BaseSessionFacade <E, I> {
	
	public List<E> getAll();
	public E getByID(I id);
	public void save(E entity);
	public void delete(E entity);

}

… e uma interfece para tratar de cada uma das minhas entidades persistentes:

CampanhaSessionFacade

package org.it4services.contactprovider.service.facade;

import javax.ejb.Local;

import org.it4services.contactprovider.to.Campanha;

@Local
public interface CampanhaSessionFacade extends BaseSessionFacade<Campanha, Integer> {

}

TabulacaoSessionFacade

package org.it4services.contactprovider.service.facade;

import javax.ejb.Local;

import org.it4services.contactprovider.to.Tabulacao;

@Local
public interface TabulacaoSessionFacade extends BaseSessionFacade<Tabulacao, Integer> {

}

… seguem as implementações dessas duas interfaces:

CampanhaSessionFacadeImpl

package org.it4services.contactprovider.service;

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.it4services.contactprovider.service.facade.CampanhaSessionFacade;
import org.it4services.contactprovider.to.Campanha;

@Stateless
public class CampanhaSessionFacadeImpl implements CampanhaSessionFacade {
	
	@PersistenceContext(unitName = "ContactProviderJPA")
	private EntityManager entityManager;

	@SuppressWarnings("unchecked")
	public List<Campanha> getAll() {
		return entityManager.createQuery("from Campanha c").getResultList();
	}

	public Campanha getByID(Integer id) {
		return null;
	}

	public void save(Campanha campanha) {
		entityManager.persist(campanha);
	}
	
	public void delete(Campanha campanha) {
		entityManager.remove(campanha);
	}

}

TabulacaoSessionFacadeImpl

package org.it4services.contactprovider.service;

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.it4services.contactprovider.service.facade.TabulacaoSessionFacade;
import org.it4services.contactprovider.to.Tabulacao;

@Stateless
public class TabulacaoSessionFacadeImpl implements TabulacaoSessionFacade {
	
	@PersistenceContext(unitName = "ContactProviderJPA")
	private EntityManager entityManager;

	@SuppressWarnings("unchecked")
	public List<Tabulacao> getAll() {
		return entityManager.createQuery("from Campanha c").getResultList();
	}

	public Tabulacao getByID(Integer id) {
		return null;
	}

	public void save(Tabulacao tabulacao) {
		entityManager.persist(tabulacao);
	}
	
	public void delete(Tabulacao tabulacao) {
		entityManager.remove(tabulacao);
	}

}

… o meu grande problema está sendo o seguinde, quando executo o método getAll() através de um ManagedBean para listar as campanhas na minha página, é disparada a seguinte exceção:

javax.servlet.ServletException: failed to lazily initialize a collection of role: org.it4services.contactprovider.to.Campanha.tabulacaoList, no session or session was closed

Minha dúvida é a seguinte:

Isso ocorre porque meu EJB que cuida da persistência da Campanha não é o mesmo que cuida da persistência da Tabulação? Eu teria que colocar todos os métodos das entidades que possuem um forte relacionamento entre si num mesmo EJB para que “compartilhem a mesma sessão”, isso se o q estou dizendo tem alguma procedência… como poderia resolver isso?

Como vocês podem ver estou meio perdido e confuso. Por favor me ajudem.

Valeu!!!

Cara … acho que seu problema não é design e sim gerenciamento de objetos na sessão do hibernate.

Você pode usar algo como o carregamento de todos as tabulações da coleção no momento que vc carregar a campanha.

Use algo como: fetch = FetchType.EAGER


@OneToMany(mappedBy = "campanha", cascade = ALL, fetch = FetchType.EAGER )  
private List&lt;Tabulacao&gt; tabulacaoList = new ArrayList&lt;Tabulacao&gt;();  

Existe também um outro método, que eu não tenho ainda muito conhecimento, mas já li a respeito e sei que é uma implementação muito útil. Pesquise sobre “JPA open session in view”

[]'s

Então cara… eu tentei utilizar o EAGER também, mas quando eu faço isso… dá um erro bizarro no momento em que eu subo o JBoss… a meleka é que não estou com o projeto aqui no trampo… deixei em casa… se não eu postaria os erros que aparecem no momento do deploy pra ver se alguém pode me dar uma luz… mas de qualquer maneira vou dar uma pesquisada nesse “JPA open session in view”… valeu!!!

Olha só… quando eu ativo o EAGER… dá esse monte de pau quando é feito o deploy da aplicação:

20:28:39,796 ERROR [AbstractKernelController] Error installing to Start: name=persistence.units:ear=ContactProviderEAR.ear,jar=ContactProviderJPA.jar,unitName=ContactProviderJPA state=Create
javax.persistence.PersistenceException: [PersistenceUnit: ContactProviderJPA] Unable to build EntityManagerFactory
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:676)
at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:132)
at org.jboss.ejb3.entity.PersistenceUnitDeployment.start(PersistenceUnitDeployment.java:259)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.jboss.reflect.plugins.introspection.ReflectionUtils.invoke(ReflectionUtils.java:56)
at org.jboss.reflect.plugins.introspection.ReflectMethodInfoImpl.invoke(ReflectMethodInfoImpl.java:110)
at org.jboss.joinpoint.plugins.BasicMethodJoinPoint.dispatch(BasicMethodJoinPoint.java:66)
at org.jboss.kernel.plugins.dependency.KernelControllerContextAction$JoinpointDispatchWrapper.execute(KernelControllerContextAction.java:214)
at org.jboss.kernel.plugins.dependency.ExecutionWrapper.execute(ExecutionWrapper.java:45)
at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.dispatchExecutionWrapper(KernelControllerContextAction.java:108)
at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.dispatchJoinPoint(KernelControllerContextAction.java:69)
at org.jboss.kernel.plugins.dependency.LifecycleAction.installActionInternal(LifecycleAction.java:221)
at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.installAction(KernelControllerContextAction.java:135)
at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.installAction(KernelControllerContextAction.java:46)
at org.jboss.dependency.plugins.action.SimpleControllerContextAction.simpleInstallAction(SimpleControllerContextAction.java:62)
at org.jboss.dependency.plugins.action.AccessControllerContextAction.install(AccessControllerContextAction.java:71)
at org.jboss.dependency.plugins.AbstractControllerContextActions.install(AbstractControllerContextActions.java:51)
at org.jboss.dependency.plugins.AbstractControllerContext.install(AbstractControllerContext.java:327)
at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:1309)
at org.jboss.dependency.plugins.AbstractController.incrementState(AbstractController.java:734)
at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:862)
at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:784)
at org.jboss.dependency.plugins.AbstractController.change(AbstractController.java:622)
at org.jboss.dependency.plugins.AbstractController.change(AbstractController.java:411)
at org.jboss.system.ServiceController.doChange(ServiceController.java:659)
at org.jboss.system.ServiceController.start(ServiceController.java:431)
at org.jboss.system.deployers.ServiceDeployer.start(ServiceDeployer.java:150)
at org.jboss.system.deployers.ServiceDeployer.deploy(ServiceDeployer.java:108)
at org.jboss.system.deployers.ServiceDeployer.deploy(ServiceDeployer.java:46)
at org.jboss.deployers.spi.deployer.helpers.AbstractSimpleRealDeployer.internalDeploy(AbstractSimpleRealDeployer.java:65)
at org.jboss.deployers.spi.deployer.helpers.AbstractRealDeployer.deploy(AbstractRealDeployer.java:50)
at org.jboss.deployers.plugins.deployers.DeployerWrapper.deploy(DeployerWrapper.java:169)
at org.jboss.deployers.plugins.deployers.DeployersImpl.doInstallParentFirst(DeployersImpl.java:853)
at org.jboss.deployers.plugins.deployers.DeployersImpl.doInstallParentFirst(DeployersImpl.java:874)
at org.jboss.deployers.plugins.deployers.DeployersImpl.install(DeployersImpl.java:794)
at org.jboss.dependency.plugins.AbstractControllerContext.install(AbstractControllerContext.java:327)
at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:1309)
at org.jboss.dependency.plugins.AbstractController.incrementState(AbstractController.java:734)
at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:862)
at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:784)
at org.jboss.dependency.plugins.AbstractController.change(AbstractController.java:622)
at org.jboss.dependency.plugins.AbstractController.change(AbstractController.java:411)
at org.jboss.deployers.plugins.deployers.DeployersImpl.process(DeployersImpl.java:498)
at org.jboss.deployers.plugins.main.MainDeployerImpl.process(MainDeployerImpl.java:506)
at org.jboss.system.server.profileservice.ProfileServiceBootstrap.loadProfile(ProfileServiceBootstrap.java:246)
at org.jboss.system.server.profileservice.ProfileServiceBootstrap.start(ProfileServiceBootstrap.java:131)
at org.jboss.bootstrap.AbstractServerImpl.start(AbstractServerImpl.java:408)
at org.jboss.Main.boot(Main.java:208)
at org.jboss.Main$1.run(Main.java:534)
at java.lang.Thread.run(Thread.java:595)
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)
at org.hibernate.persister.entity.AbstractEntityPersister.createEntityLoader(AbstractEntityPersister.java:1748)
at org.hibernate.persister.entity.AbstractEntityPersister.createEntityLoader(AbstractEntityPersister.java:1752)
at org.hibernate.persister.entity.AbstractEntityPersister.createLoaders(AbstractEntityPersister.java:2984)
at org.hibernate.persister.entity.AbstractEntityPersister.postInstantiate(AbstractEntityPersister.java:2977)
at org.hibernate.persister.entity.SingleTableEntityPersister.postInstantiate(SingleTableEntityPersister.java:690)
at org.hibernate.impl.SessionFactoryImpl.(SessionFactoryImpl.java:290)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1294)
at org.hibernate.cfg.AnnotationConfiguration.buildSessionFactory(AnnotationConfiguration.java:918)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:668)
… 52 more
20:28:39,859 INFO [RARDeployment] Required license terms exist, view vfsfile:/C:/Java/jboss/appServer/jboss-5.0.0.Beta4/server/default/deploy/quartz-ra.rar/META-INF/ra.xml
20:28:39,953 INFO [SimpleThreadPool] Job execution threads will use class loader of thread: main
20:28:39,968 INFO [QuartzScheduler] Quartz Scheduler v.1.5.2 created.
20:28:39,968 INFO [RAMJobStore] RAMJobStore initialized.
20:28:39,968 INFO [StdSchedulerFactory] Quartz scheduler ‘DefaultQuartzScheduler’ initialized from default resource file in Quartz package: 'quartz.properties’
20:28:39,968 INFO [StdSchedulerFactory] Quartz scheduler version: 1.5.2
20:28:39,968 INFO [QuartzScheduler] Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
20:28:40,000 INFO [TomcatDeployment] deploy, ctxPath=/, vfsUrl=ROOT.war
20:28:41,187 ERROR [ProfileServiceBootstrap] Failed to load profile: Summary of incomplete deployments (SEE PREVIOUS ERRORS FOR DETAILS):

*** CONTEXTS MISSING DEPENDENCIES: Name -> Dependency{Required State:Actual State}

jboss.j2ee:ear=ContactProviderEAR.ear,jar=ContactProviderJPA.jar,name=CampanhaSessionFacadeImpl,service=EJB3
-> {Described:** UNRESOLVED Demands 'jboss.ejb:service=EJBTimerService }
-> {Described:
UNRESOLVED Demands 'persistence.units:ear=ContactProviderEAR.ear,jar=ContactProviderJPA.jar,unitName=ContactProviderJPA **}

jboss.j2ee:ear=ContactProviderEAR.ear,jar=ContactProviderJPA.jar,name=MailingSessionFacadeImpl,service=EJB3
-> {Described:** UNRESOLVED Demands 'persistence.units:ear=ContactProviderEAR.ear,jar=ContactProviderJPA.jar,unitName=ContactProviderJPA }
-> {Described:
UNRESOLVED Demands 'jboss.ejb:service=EJBTimerService **}

jboss.j2ee:ear=ContactProviderEAR.ear,jar=ContactProviderJPA.jar,name=StatusSessionFacadeImpl,service=EJB3
-> {Described:** UNRESOLVED Demands 'persistence.units:ear=ContactProviderEAR.ear,jar=ContactProviderJPA.jar,unitName=ContactProviderJPA }
-> {Described:
UNRESOLVED Demands 'jboss.ejb:service=EJBTimerService **}

jboss.j2ee:ear=ContactProviderEAR.ear,jar=ContactProviderJPA.jar,name=TabulacaoSessionFacadeImpl,service=EJB3
-> {Described:** UNRESOLVED Demands 'jboss.ejb:service=EJBTimerService }
-> {Described:
UNRESOLVED Demands 'persistence.units:ear=ContactProviderEAR.ear,jar=ContactProviderJPA.jar,unitName=ContactProviderJPA **}

jboss.j2ee:ear=ContactProviderEAR.ear,jar=ContactProviderJPA.jar,name=UsuarioSessionFacadeImpl,service=EJB3
-> {Described:** UNRESOLVED Demands 'jboss.ejb:service=EJBTimerService }
-> {Described:
UNRESOLVED Demands 'persistence.units:ear=ContactProviderEAR.ear,jar=ContactProviderJPA.jar,unitName=ContactProviderJPA **}

*** CONTEXTS IN ERROR: Name -> Error

persistence.units:ear=ContactProviderEAR.ear,jar=ContactProviderJPA.jar,unitName=ContactProviderJPA -> org.hibernate.HibernateException: cannot simultaneously fetch multiple bags

-> ** UNRESOLVED Demands 'persistence.units:ear=ContactProviderEAR.ear,jar=ContactProviderJPA.jar,unitName=ContactProviderJPA **

20:28:41,250 INFO [Http11Protocol] Starting Coyote HTTP/1.1 on http-127.0.0.1-8080
20:28:41,406 INFO [AjpProtocol] Starting Coyote AJP/1.3 on ajp-127.0.0.1-8009
20:28:41,421 INFO [ServerImpl] JBoss (Microcontainer) [5.0.0.Beta4 (build: SVNTag=JBoss_5_0_0_Beta4 date=200802091115)] Started in 1m:12s:625ms

Estava eu pesquisando no fórum para ver se alguém já teve um problema parecido com esse e encontrei um tópico onde é relatado que nas ocasiões onde existe mais de um atributo de uma classe anotado com a opção EAGER dá pau na hora de fazer deploy, e pra meu espanto, eu fiz esse teste na minha aplicação removendo um dos dois atributos presentes em minha classe e funcionou… Alguém sabe pq acontece isso?
Um dos caras que respondeu neste tópico disse que resolveu isso utilizando a interface Set para armazenar dados de coleções ao invés de List ou Collection por exemplo.

A exceção em questão é a seguinte:

 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.<init>(EntityLoader.java:75)
at org.hibernate.loader.entity.EntityLoader.<init>(EntityLoader.java:43)
at org.hibernate.loader.entity.EntityLoader.<init>(EntityLoader.java:33)
at org.hibernate.loader.entity.BatchingEntityLoader.createBatchingEntityLoader(BatchingEntityLoader.java:103)
at org.hibernate.persister.entity.AbstractEntityPersister.createEntityLoader(AbstractEntityPersister.java:174
at org.hibernate.persister.entity.AbstractEntityPersister.createEntityLoader(AbstractEntityPersister.java:1752)
at org.hibernate.persister.entity.AbstractEntityPersister.createLoaders(AbstractEntityPersister.java:2984)
at org.hibernate.persister.entity.AbstractEntityPersister.postInstantiate(AbstractEntityPersister.java:2977)
at org.hibernate.persister.entity.SingleTableEntityPersister.postInstantiate(SingleTableEntityPersister.java:690)
at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:290)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1294)
at org.hibernate.cfg.AnnotationConfiguration.buildSessionFactory(AnnotationConfiguration.java:91
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:66
... 52 more

Flw!!!

Cara… tu não pode declarar o fetch mais do que uma vez… tu tens é que inicializar a tua List.
Ex.:
List list = ne List();