Many to Many - org.hibernate.PersistentObjectException: detached entity passed to persist:

Galera, estou tentando salvar uma entidade com relacionamento ManyToMany com atributos…
Estou recebendo esta bendita exception…

Caused by: javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: pacote.Campo at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1360) at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1288) at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1294) at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:877) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:240) at $Proxy31.merge(Unknown Source) at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:345) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:334) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:319) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155) ... 61 more

Vamos aos mapeamentos…

Entidade que quero salvar:


@Table(name = "com_prospecto")
@Entity
public class Prospecto {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;

	@Column(name = "data_cadastramento")
	@Temporal(TemporalType.TIMESTAMP)
	private Date cadastramento;

	@ManyToMany
	@JoinTable(name = "com_historico_acoes", joinColumns = @JoinColumn(name = "id_prospecto"), inverseJoinColumns = @JoinColumn(name = "id_acao"))
	private List<Acao> acoes;

	@OneToMany(fetch = FetchType.EAGER, mappedBy = "prospecto")
	private List<ProspectoCampo> prospectoCampos;

Entidade que quero que também seja salva:


@Table(name = "com_prospecto_campo")
@Entity
@IdClass(ProspectoCampoId.class)
public class ProspectoCampo {

	@Id
	@ManyToOne
	@JoinColumn(name = "id_prospecto")
	private Prospecto prospecto;

	@Id
	@ManyToOne
	@JoinColumn(name = "id_campo")
	private Campo campo;

	private String valor;

Id da classe anterior:


public class ProspectoCampoId implements Serializable {

	private static final long serialVersionUID = 1L;

	private Integer prospecto;

	private Integer campo;

OBS: este mapeamento foi produto da leitura do seguinte blog do jakefrog (H. Coelho)!

E estou tentando salvar (na verdade é editar, mas isto é indiferente pois o Spring Data faz um save or update):

public String editar(){
		prospecto = prospectoService.buscarProspectoComAcoes(prospecto.getId()); //isto é por causa do lazy loading...
		
		for(ProspectoHelper hp : ProspectoHelper.extrairDadoDoModelo(dynaFormModelUniao)){ //ignorem esta linha
			prospecto.adicionarCampo(hp.getPropertyFilter().getCampo(), hp.getPropertyFilter().getValue());   //criei um metodo no modelo para adicionar o campo... ja vou mostrar
		}
		
		prospecto.adicionarAcao(acaoSelecionada);
		
		try{
			prospectoService.save(prospecto);
		}catch(Exception e){
			log.error("Erro ao editar Prospecto!", e);		
			messageUtil.sendErrorMessageToUser("global.error", "global.insert.error", e.getMessage());
			return null;
		}
		
		return getRadarPath();
	}

O método que criei no modelo:

public void adicionarCampo(Campo campo, Object value) {
		ProspectoCampo encontrado = encontrarProspectoCampo(campo);
		
		if(encontrado == null){
			ProspectoCampo pc = new ProspectoCampo();
                        pc.setProspecto(this);
			pc.setCampo(campo);
			pc.setValor(value.toString());
		}else{
			encontrado.setValor(value.toString());			
		}
	}

Tente adicionar o cascade = CascadeType.ALL:

@OneToMany(fetch = FetchType.EAGER, mappedBy = "prospecto", cascade = CascadeType.ALL) private List&lt;ProspectoCampo&gt; prospectoCampos;

[quote=Rafael Guerreiro]Tente adicionar o cascade = CascadeType.ALL:

@OneToMany(fetch = FetchType.EAGER, mappedBy = "prospecto", cascade = CascadeType.ALL) private List&lt;ProspectoCampo&gt; prospectoCampos;[/quote]

Ja tentei fazer isso…
Mas li num post que o Lucas Cavalcanti disse que o cascade não é aplicacável a variaveis que são ‘mappedBy’…
Mas vou adicionar, dar clean em tudo e testar… só pra garantir!!
vlwww

**EDIT

continua com o mesmo erro :frowning:

tenta fazer uma consulta a esse objeto antes de seta-lo em outro para então mandar
salvar.

Não! Eu falei besteira… :oops:

Se fosse pra fazer, seria aqui…

@Id @ManyToOne(cascade=CascadeType.ALL) @JoinColumn(name = "id_campo") private Campo campo;
Tenha certeza de que o CAMPO que você está adicionando já está salvo no banco.

[quote=Rafael Guerreiro]Não! Eu falei besteira… :oops:

Se fosse pra fazer, seria aqui…

@Id @ManyToOne(cascade=CascadeType.ALL) @JoinColumn(name = "id_campo") private Campo campo;
Tenha certeza de que o CAMPO que você está adicionando já está salvo no banco.[/quote]

Infelizmente,
mesmo erro :frowning:

[quote=d34d_d3v1l][quote=Rafael Guerreiro]Não! Eu falei besteira… :oops:

Se fosse pra fazer, seria aqui…

@Id @ManyToOne(cascade=CascadeType.ALL) @JoinColumn(name = "id_campo") private Campo campo;
Tenha certeza de que o CAMPO que você está adicionando já está salvo no banco.[/quote]

Infelizmente,
mesmo erro :([/quote]

Tentou fazer uma consulta a esse objeto e setá-lo novamente
neste objeto que vc está tentando salvar?

[quote=kleberdamasco]
Tentou fazer uma consulta a esse objeto e setá-lo novamente
neste objeto que vc está tentando salvar?[/quote]

Ok, vou fazer isso que vc falou…
Mas não vejo mto sentido, porque esta EAGER o fetchType…
Mas vou testar e já posto o resultado

[quote=d34d_d3v1l][quote=kleberdamasco]
Tentou fazer uma consulta a esse objeto e setá-lo novamente
neste objeto que vc está tentando salvar?[/quote]

Ok, vou fazer isso que vc falou…
Mas não vejo mto sentido, porque esta EAGER o fetchType…
Mas vou testar e já posto o resultado
[/quote]

Pelo que sei, quando você faz uma consulta a um objeto o estado do objeto
está como transient, qndo é encerrada a sessão daquela consulta
é alterado o estado para detached por isso não é possível salvá-lo novamente.

Detached é um objeto que tem ID, mas não está sendo gerenciado pelo Hibernate/JPA.

Precisa fazer um merge para ele passar para “managed”… Ou fazer um get, caso você não queira mudar seus valores.

Kleber, quer que eu execute uma consulta pra buscar os Campos, que nem eu fiz com a ação? Tipo:

	@Query("select p from Prospecto p left join fetch p.acoes where p.id = ?1")
	public Prospecto buscarProspectoComAcoes(Integer idProspecto);

Como eu faço aqui:

		prospecto = prospectoService.buscarProspectoComAcoes(prospecto.getId());

Não é isso?

Bom , não consigo buscar 2 Lists com a mesma consulta, pq da aquele erro do hibernate, fetch multiple bags…
Algo assim…

Então o que sugerem?
Ou entendi errado?

abraços

[quote=Rafael Guerreiro]Detached é um objeto que tem ID, mas não está sendo gerenciado pelo Hibernate/JPA.

Precisa fazer um merge para ele passar para “managed”… Ou fazer um get, caso você não queira mudar seus valores.[/quote]

fazer um merge, caso tenha sido alterado, ou uma consulta para pegá-lo novamente.

Se isso aqui é por causa do Lazy Loading... Um Hibernate.initialize(prospecto.getList()); resolve.

Se isso aqui é por causa do Lazy Loading… Um Hibernate.initialize(prospecto.getList()); resolve.

ainda nao entendi…

tenho que buscar a coleção de campos… setar no objeto… adicionar os que eu quero
e depois salvar o prospecto… é isso?

Mas “Campo” em “ProspectoCampo” não é coleção.

você tem que fazer um Hibernate.initialize(prospecto.getProspectoCampos());

[quote=Rafael Guerreiro]Mas “Campo” em “ProspectoCampo” não é coleção.

você tem que fazer um Hibernate.initialize(prospecto.getProspectoCampos());[/quote]

ProspectoCampos já esta inicializado.

Acho que o problema é que ele esta tentando salvar as mudanças na entidade Campo também.
Mas não quero que faça isso!

Então, nesse caso, você não deve usar o Cascade.

Você vai precisar fazer um get (ou load) e setar com o objeto que veio do Hibernate.

[quote=Rafael Guerreiro]Então, nesse caso, você não deve usar o Cascade.

Você vai precisar fazer um get (ou load) e setar com o objeto que veio do Hibernate.[/quote]

Então, remove is cascade…

e fiz assim:

public String editar(){
		Prospecto prospecto = ProspectoHelper.extrairProspecto(dynaFormModelUniao);
		prospecto = prospectoService.buscarProspectoComAcoes(prospecto.getId());
		
		
		for(ProspectoHelper hp : ProspectoHelper.extrairDadoDoModelo(dynaFormModelUniao)){
			prospecto.adicionarCampo(campoService.findOne(hp.getPropertyFilter().getCampo().getId()), hp.getPropertyFilter().getValue());
		}
		
		prospecto.adicionarAcao(acaoSelecionada);
		
		try{
			prospectoService.save(prospecto);
		}catch(Exception e){
			log.error("Erro ao editar Prospecto!", e);		
			messageUtil.sendErrorMessageToUser("global.error", "global.insert.error", e.getMessage());
			return null;
		}
		
		return getRadarPath();
	}

Mas mesmo assim deu erro!!! Será que eu não posso construir o objeto ProspectoCampo dentro da propria entidade? Não faz sentido né…

Galera,

tentei fazer assim:


	public String editar(){
		Prospecto prospecto = ProspectoHelper.extrairProspecto(dynaFormModelUniao);
		prospecto = prospectoService.buscarProspectoComAcoes(prospecto.getId());
		
		log.debug("Antes: "+prospecto.getProspectoCampos().size());
		
		List<ProspectoCampo> prospectoCampos = new ArrayList<ProspectoCampo>();
		
		for(ProspectoHelper hp : ProspectoHelper.extrairDadoDoModelo(dynaFormModelUniao)){
			ProspectoCampo pc = new ProspectoCampo();
			pc.setCampo(campoService.findOne(hp.getPropertyFilter().getCampo().getId()));
			pc.setValor(hp.getPropertyFilter().getValue().toString());
			pc.setProspecto(prospecto);
			prospectoCampos.add(pc);
		}
		
		
		prospecto.setProspectoCampos(prospectoCampos);

		log.debug("Depois: "+prospecto.getProspectoCampos().size());
		
		prospecto.adicionarAcao(acaoSelecionada);
		
		try{
			prospectoService.save(prospecto);
		}catch(Exception e){
			log.error("Erro ao editar Prospecto!", e);		
			messageUtil.sendErrorMessageToUser("global.error", "global.insert.error", e.getMessage());
			return null;
		}
		
		return getRadarPath();
	}

Mas infelizmente mesmo erro!!!
LOG:

13:59:09 (ProspectoRadarController.java:150) S-DEBUG - Antes: 3

--mensagens do hibernate

13:59:09 (ProspectoRadarController.java:165) S-DEBUG - Depois: 4

--mais mensagens do hibernate

13:59:09 (ProspectoRadarController.java:172) S-ERROR - Erro ao editar Prospecto!
org.springframework.orm.jpa.JpaSystemException: org.hibernate.PersistentObjectException: detached entity passed to persist: