Multiple Bags Exception x Lazy Initialization Exception:

Pessoal,

Estou em um paradoxo muito louco.
Tenho uma entidade que possui duas Collections que eu preciso persistir no banco…

Se eu coloco uma das listas como Lazy eu recebo um Lazy Initialization Exception.
Se eu coloco as duas listas como Eager eu recebo um Multiple Bags Exception.

Segue a classe:

package br.fiocruz.dominio;

import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;

import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;

@Entity
@SequenceGenerator(name="seqGrupo")
@Inheritance(strategy=InheritanceType.JOINED)
public class GrupoVisitacao {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(generator="seqGrupo", strategy = GenerationType.AUTO)
	private Long id;
	
	private String nomeDescritivo;
	private String descricao;
	private int qtdTotal;
	//Se nao informar qtds abaixo será igual a metade da total;
	private int qtdMasc;
	private int qtdFem;
	private boolean visitantesExcepcionais;
	private String descricaoCasosExcepcionais;
	
	@Enumerated
	protected TipoGrupoVisitacao tipoGrupo;
	
	@ManyToMany(fetch = FetchType.EAGER)
	@JoinTable(name = "grupo_faixaEtaria")
	private List<FaixaEtaria> faixaEtaria;
	
	@ManyToOne(cascade=CascadeType.MERGE)
	private ResponsavelPorGrupoVisitacao responsavel;
	
	@ManyToOne
	private InstituicaoEnsino instituicao;
	
	@ManyToMany(fetch = FetchType.EAGER)
	@JoinTable(name = "grupo_serieEscolar")
	private List<SerieEscolar> serie;

	[...gets e sets...]
}

Ja pesquisei abeça estou com esse problema desde a semana passada e ja fiz algumas tentativas…

1º - Anotar as listas com @Fetch(FetchMode.SUBSELECT)
Erro: Multiple bags, parece que nao surtiu nenhum efeito.

2º - Colocar as duas listas como Lazy
Erro: Lazy Inicialisation Exception

3º - Trocar as duas Listas para Set como Eager
Erro: Lazy Inicialisation Exception

4º - Trocar as duas Listas para Set como Lazy
Erro: Lazy Inicialisation Exception

Fiz outras tentativas e ja li sobre algumas gambearras tb, mas nao quero fazer remendo…

Enfim, meu problema inicial é o LazyInicializationException, a aplicação é web e eu uso o JSF… O erro esta ocorrendo antes da faze de Invoke Application, provavelmente na Restore View…

Para resolver o Lazy eu cheguei a ler sobre criar um filtro para o hibernate e colocar ele am algum lugar no jsf, eu só nao entendi muito bem como funcionaria e preferi não seguir esse raciocinio… eu gostaria que minha camada de persistencia funcionasse independente da minha camada de visao, por isso deixei essa solução de lado, nao sei se estou falando besteira… Essa solução tinha até um nome: Open session in View e ate um link rsrs https://community.jboss.org/wiki/OpenSessionInView O.O

Sobre a escolha de usar eager ou lazy eu ja refleti a respeito e acho que não vai fazer muita diferença ja que QUASE sempre que eu usar esse grupo de visitação eu terei que exibir as informações dos objetos relacionados…

Esquece que carregar 2 ou mais listas como EAGER não tem como,

Existe algumas maneiras de contornar o problema de Lazy, eu uso o Seam Persistence e não tenho problema com isso mas assim vai:

Ou deixe como Lazy e crie um metodo que sempre q carregar sua entiadade carrega as listas junto,

Se eu não me engano, se seu EM estiver como PersistenceContext tbm resolve o problema de Lazy,

Mas a best pratice é https://community.jboss.org/wiki/OpenSessionInView

Abrcs,

Eu prefiro evitar o Lazy.

Esse post mostra como evitar o Layz: Quatro soluções para LazyInitializationException.

Eu prefiro por query, open session in view permite um efeito chamado n+1 de queries. [=

Então Rafael,

Sobre a questão de List e Sets, realmente, eu usei o List por conta do costume, mas não faz nem sentido usar o List, por isso que da pau…

Eu estou usando o JPA e o JSF2…
Pelo que eu entendi depois de tudo que ja li o Open Session In View é uma solução pra quem utiliza hibernate, pois usando apenas o JPA esse conceito de sessão nem existe…

Eu estou començando a pensar em mudar a aplicação pro Hibernate, mas vai ser doloroso e fora esse problema eu nao tenho essa necessidade, antes disso estou procurando um modo de resolver o problema sem gambiarras…

A minha camada de persistencia hoje está assim:

classe JPAUtil

[code]
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class JPAUtil
{
/**
* Objeto do tipo Singleton para criar a EntityManagerFactory como instância
* única durante a inicialiação.
*/
private static EntityManagerFactory fabrica;
private static SessionFactory sessionFactory;

static
{
	JPAUtil.fabrica = Persistence.createEntityManagerFactory("banco");
	//JPAUtil.sessionFactory = new Configuration().configure( "META-INF/persistence.xml" ).buildSessionFactory();  
}

public static EntityManagerFactory getFactory()
{
	return JPAUtil.fabrica;
}

/**
 * Objeto que armazena um único valor para cada Thread individualmente. O
 * objetivo é criar apenas 1 objeto EntityManager para cada Thread, de forma
 * que os DAO's possam compartilhar o mesmo objeto.
 */
private static ThreadLocal<EntityManager> CACHE = new ThreadLocal<EntityManager>();

public static void limparCacheEntityManager()
{
	// tenta ler o EntityManager da Thread atual
	EntityManager em = CACHE.get();

	// fecha o EntityManager caso exista
	if (em != null)
		em.close();

	CACHE.remove();
}

public static EntityManager getEntityManager()
{
	// tenta ler o EntityManager da Thread atual
	EntityManager retorno = CACHE.get();

	// se tem um objeto e este objeto estiver fechado, deve descartá-lo e
	// criar outro
	if ((retorno != null) && (!retorno.isOpen()))
		retorno = null;

	// caso ainda não tenha sido criado, cria um novo e guarda reutilização
	if (retorno == null)
	{
		retorno = JPAUtil.fabrica.createEntityManager();
		// guarda o objeto para usar sempre o mesmo nesta Thread
		CACHE.set(retorno);
	}

	return retorno;
}

public static SessionFactory getSessionFactory() {
	return sessionFactory;
}	

}[/code]

Classe JPADAO - Minhas DAOs se especializam com essa classe

[code]
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;

public abstract class JpaDAO implements GenericDAO{
protected Class persistentClass;
private EntityManager manager;

/**
 * Método construtor que
 */
@SuppressWarnings("unchecked")
public JpaDAO()
{
	super();

	// pegar a classe persistente por reflexão
	Type tipo = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	this.persistentClass = (Class<T>) tipo;

	// pega o EntityManager padrão da ThreadAtual
	this.manager = JPAUtil.getEntityManager();
}

public JpaDAO(EntityManager manager)
{
	this();
	this.manager = manager;
}

@Override
public EntityManager getEntityManager()
{
	// caso o EntityManager tenha sido fechado, deve criar um novo
	if ((this.manager != null) && (!this.manager.isOpen()))
		this.manager = JPAUtil.getEntityManager();

	return this.manager;
}

@Override
public T lerPorId(Object id)
{
	return (T) this.getEntityManager().find(this.persistentClass, id);
}

@Override
public List<T> lerTodos()
{
	CriteriaBuilder cb = this.getEntityManager().getCriteriaBuilder();
	CriteriaQuery<T> c = cb.createQuery(this.persistentClass);
	c.select(c.from(this.persistentClass));
	
	List<T> resultado = this.getEntityManager().createQuery(c).getResultList();
	
	return resultado;
}

@Override
public T salvar(T objeto)
{
	boolean transacaoAtiva = this.getEntityManager().getTransaction().isActive();

	if (!transacaoAtiva)
		this.abrirTransacao();

	this.getEntityManager().merge(objeto);

	if (!transacaoAtiva)
		this.gravarTransacao();

	return objeto;
}

@Override
public void excluir(T objeto)
{
	boolean transacaoAtiva = this.getEntityManager().getTransaction().isActive();

	if (!transacaoAtiva)
		this.abrirTransacao();

	this.getEntityManager().remove(objeto);

	if (!transacaoAtiva)
		this.gravarTransacao();
}

@Override
public void abrirTransacao()
{
	this.getEntityManager().getTransaction().begin();
}

@Override
public void gravarTransacao()
{
	this.getEntityManager().flush();
	this.getEntityManager().getTransaction().commit();
}

@Override
public void desfazerTransacao()
{
	this.getEntityManager().getTransaction().rollback();
}

public ArrayList<T> lerTodosAtivos(){
	ArrayList<T> resultado;

	Query consulta = this.getEntityManager().createQuery("from " + this.persistentClass.getName() + " a " +
															"where a.habilitada = :habilitada");
	consulta.setParameter("habilitada", true);

	try
	{
		resultado = (ArrayList<T>) consulta.getResultList();
	}
	catch (NoResultException e)
	{
		resultado = null;
	}

	return resultado;
}

}[/code]

Exemplo de uma classe DAO - o CRUD ja esta todo na JPADAO, aqui eu faço as consultas especificas dessa entidade

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;

import br.fiocruz.dominio.AreaVisitacao;
import br.fiocruz.dominio.Atividade;
import br.fiocruz.servico.util.JpaDAO;

public class AtividadeDAO extends JpaDAO<Atividade> {

	public AtividadeDAO() {
		super();
	}

	public AtividadeDAO(EntityManager manager) {
		super(manager);
	}
	
	public List<Atividade> lerPorArea(AreaVisitacao area) {
		List<Atividade> resultado;

		Query consulta = this.getEntityManager().createQuery("from Atividade a where a.area.id = :idArea");
		consulta.setParameter("idArea", area.getId());

		try
		{
			resultado = (List<Atividade>) consulta.getResultList();
		}
		catch (NoResultException e)
		{
			resultado = null;
		}

		return resultado;
	}
	
		
}

[quote=jakefrog]Eu prefiro evitar o Lazy.

Esse post mostra como evitar o Layz: Quatro soluções para LazyInitializationException.

Eu prefiro por query, open session in view permite um efeito chamado n+1 de queries. [=[/quote]

Excelente post, na empresa usamos por query tbm, mas o problema do n+1 pode ser amenizado com @Batchsize

O único problema do @Batchsize é que ele é do Hibernate né? =/

Quem usa outra implementação se lasca. =D

Mas um OpenSessionInView bem utilizado, nunca vai ter o n+1. [=

To Lascado entao! rsrs

O Open Session In View é possivel com JPA?

[quote=mauriciojdsantos]To Lascado entao! rsrs

O Open Session In View é possivel com JPA?[/quote]Você leu o post?

Estou lendo… e concordo com o rafael, muito bom!

Uma duvida se não for abusar… Voce sabe como a anotação @Resource faz o JPA pegar a sessão?

Tenta algo do tipo:

[code]
package br.fiocruz.dominio;

import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;

import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;

@Entity
@SequenceGenerator(name=“seqGrupo”)
@Inheritance(strategy=InheritanceType.JOINED)
public class GrupoVisitacao {
private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(generator="seqGrupo", strategy = GenerationType.AUTO)
private Long id;

private String nomeDescritivo;
private String descricao;
private int qtdTotal;
//Se nao informar qtds abaixo será igual a metade da total;
private int qtdMasc;
private int qtdFem;
private boolean visitantesExcepcionais;
private String descricaoCasosExcepcionais;

@Enumerated
protected TipoGrupoVisitacao tipoGrupo;

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "grupo_faixaEtaria")
[b]@IndexColumn(name="CAMPO_CHAVE_DA_TABELA_FAIXA_ETARIA")[/b]
private List<FaixaEtaria> faixaEtaria;

@ManyToOne(cascade=CascadeType.MERGE)
// ADICIONE A LINHA ABAIXO SUBSTITUINDO "??????????" PELO NOME DO CAMPO QUE É CHAVE PRIMÁRIA DA TABELA RELACIONADA
@IndexColumn(name="??????????")
private ResponsavelPorGrupoVisitacao responsavel;

@ManyToOne
// ADICIONE A LINHA ABAIXO SUBSTITUINDO "??????????" PELO NOME DO CAMPO QUE É CHAVE PRIMÁRIA DA TABELA RELACIONADA
@IndexColumn(name="??????????")
private InstituicaoEnsino instituicao;

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "grupo_serieEscolar")
// ADICIONE A LINHA ABAIXO SUBSTITUINDO "??????????" PELO NOME DO CAMPO QUE É CHAVE PRIMÁRIA DA TABELA RELACIONADA
@IndexColumn(name="??????????")
private List<SerieEscolar> serie;

[...gets e sets...]

}[/code]

Usando EAGER em tudo.

[quote=mauriciojdsantos]Uma duvida se não for abusar… Voce sabe como a anotação @Resource faz o JPA pegar a sessão?[/quote]Você pesquisou no google? Oq ele te falou? =D

OBS.: Falo isso para todos os meus alunos, não é perseguição. [=

Jakefrog voce nao sabe a loucura que esta aqui, mas sem duvida uma hora eu vou pesquisar, preciso muito entender melhor JPA e Hibernate, to com uma lista de curiosidades e duvidas aqui… Só tentei aproveitar esse papo descontraido aqui rs…

Bem, fiz o que voce me endicou nas anotações e nada… Coloquei essa coleção ai tanto como Set quanto como List e o erro em ambos o caso foi de Lazy… O que estou achando muito estranho é o fato de eu estar definindo como Eager e esse erro acontecer…

Eu li no seu post que para relacionamentos anotados como *ToMany o JPA usa o Lazy como default, mas se eu aletrar isso na anotação ele considera o que eu anotei ou ele me ignora e usa o default?

Segue o codigo com as anotações atualizadas:

@Entity
@SequenceGenerator(name="seqGrupo")
public class GrupoVisitacao {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(generator="seqGrupo", strategy = GenerationType.AUTO)
	private Long id;
	
	private String nomeDescritivo;
	private String descricao;
	private int qtdTotal;
	//Se nao informar qtds abaixo será igual a metade da total;
	private int qtdMasc;
	private int qtdFem;
	private boolean visitantesExcepcionais;
	private String descricaoCasosExcepcionais;
	
	@Enumerated
	protected TipoGrupoVisitacao tipoGrupo;
	
	@ManyToMany(fetch = FetchType.EAGER)
	@JoinTable(name = "grupo_faixaEtaria")
	@IndexColumn(name="Id")
	private List<FaixaEtaria> faixaEtaria;
	
	@ManyToOne(cascade=CascadeType.MERGE)
	@IndexColumn(name="Id")
	private ResponsavelPorGrupoVisitacao responsavel;
	
	@ManyToOne
	@IndexColumn(name="Id")
	private InstituicaoEnsino instituicao;
	
	@ManyToMany(fetch = FetchType.EAGER)
	@JoinTable(name = "grupo_serieEscolar")
	@IndexColumn(name="Id")
	private List<SerieEscolar> serie;

Então, tentei fazer o opensessioninview…

Primeiro tentei criar o filtro mas por algum motivo a aplicação não sobe quando eu coloco as configurações do filtro no web.xml

Depois tenatei fazer em um phaselistener e ele deu nullpointer no atributo utx (que seia carregado pelo @Resourse)

Continuo na batalha…

[ATUALIZANDO]

No caso do filter a aplicação não esta subindo por conta do UserTransaction utx…

Quando eu comento esse atributo no código (só para testar) a aplicação sobe e o filtro funciona… Se eu deixo la eu tenho a seguinte exceção:
javax.naming.NameNotFoundException: Name br.fiocruz.filtros.ConnectionFilter is not bound in this Context

Isso aqui resolve o seu problema, não me pergunte como, depois vc pensa em retactory,

// uma vez q vc tenha o id da sua entidade recuperado através de uma lista por exemplo,
// vc pode carregar a entidade só com o id e fazer essa gambis aqui, mas tem q estar como Lazy:
public loadWithLists(Entity ID) {
	Entity e = repository.find(ID);
	// se não for nulo faz:
	System.out.println(e.getList1);
	System.out.println(e.getList2);
	// retorna o objeto para seu controller
	return e;	
}

Ou fazer um join fetch em sua listas

Mais elegante porém mais custoso, ou siga o tuto do jakefrog,

O melhor mesmo seria Open Session in View, mas lembre-se, deixar todas suas lista como Lazy poderá acarretar no problema n+1,

Deixar como Eager é horrível pra desempenho,

O nullpointer é o pq seu em não esta instanciado, eu uso CDI só dou um @Inject no EntityManager e ja era

OLha iso que eu achei

http://www.thedevelopersconference.com.br/tdc/2010/sp/videos/dicas-e-truques-performance-javaee-jpa-jsf