Removing a detached instance (Spring + Hibernate/JPA)

Olá Senhores,

Recentemente adotei a JPA para abstrair os meus DAO’s. E o primeiro problema que estou tendo, é ao excluir um Entity,
sempre recebo a exception : org.springframework.dao.InvalidDataAccessApiUsageException: Removing a detached instance x.x.x.

Já tentei resgatar o Entity pela ID e depois excluir, já tentei dar um merge… e simplesmente nao exlcui!.

Estou usando um DaoGenerico e tambem a JPADaoSupport do Spring, segue detalhes do meu código:

Pais

@Entity
@Table(name = "SAVIOR_PAIS")
public class Pais
{
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID_PAIS", unique = true, nullable = false)
	private int idPais;
	
	@Column(name = "NM_PAIS", nullable = false, length = 50)
	private String nmPais;
	
	@Column(name = "SG_PAIS", nullable = false, length = 3)
	private String sgPais;
	
	@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "pais")
	private Set<Estado> estados;
....

DAOGenerico

public abstract class AbstractGenericJpaDao<DomainObject> extends JpaDaoSupport implements IGenericDao<DomainObject>
{

	
	private Class<DomainObject> clazz;


	

	@SuppressWarnings("unchecked")
	public AbstractGenericJpaDao() 
	{
		this.clazz = (Class<DomainObject>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}
	
	public AbstractGenericJpaDao(Class<DomainObject> clazz) 
	{
		this.clazz = clazz;
	}

	
	/**
	 * Remove uma lista de DomainObject (Entities)
	 * 
	 * @param List<DomainObject>
	 */
	public void remove(List<DomainObject> listObj)
	{	
		log.info("Removendo lista de objetos" + listObj);
		
		for (DomainObject obj : listObj)
		{
			log.debug("Removendo o objeto "+obj.getClass().getName());
			super.getJpaTemplate().remove(obj);
		}
	}
	
	/**
	 * Remove um DomainObject (Entity)
	 * 
	 * @param DomainObject
	 */
	public void remove(DomainObject obj)
	{
		log.info("Removendo o objeto "+obj.getClass().getName());
		
		super.getJpaTemplate().remove(obj);
	}
....
	

ImplementacaoDao

@Repository(value="paisDao")
public class PaisDaoImpl extends AbstractGenericJpaDao<Pais> implements PaisRepository
{

}

TesteUnitario


public class PaisServiceImplTests extends AbstractTestConfigurable
{
	@Autowired
	private PaisService paisService;
	private Pais pais;

	@Test
	public void testRemove()
	{
		this.pais = new Pais();
		this.pais.setIdPais(2);
		
		this.pais = this.paisService.findById(pais);
		this.paisService.remove(pais);
	}
...
}

StackTrace

28-04-2008 18:36:45 [ERROR] com.apolloti.Savior.infrastructure.hibernate.dao.endereco.PaisDaoImpl - Ocorreu um erro desconhecido.
org.springframework.dao.InvalidDataAccessApiUsageException: Removing a detached instance com.apolloti.Savior.domain.entity.endereco.Pais#2; nested exception is java.lang.IllegalArgumentException: Removing a detached instance com.apolloti.Savior.domain.entity.endereco.Pais#2
	at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:269)
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:97)
	at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:212)
	at org.springframework.orm.jpa.JpaAccessor.translateIfNecessary(JpaAccessor.java:152)
	at org.springframework.orm.jpa.JpaTemplate.execute(JpaTemplate.java:189)
	at org.springframework.orm.jpa.JpaTemplate.remove(JpaTemplate.java:284)
	at com.apolloti.Savior.infrastructure.dao.AbstractGenericJpaDao.remove(AbstractGenericJpaDao.java:81)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:310)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
	at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:54)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
	at $Proxy40.remove(Unknown Source)
	at com.apolloti.Savior.domain.service.endereco.PaisServiceImpl.remove(PaisServiceImpl.java:45)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:310)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:198)
	at $Proxy41.remove(Unknown Source)
	at com.apolloti.Savior.test.service.PaisServiceImplTests.testRemove(PaisServiceImplTests.java:54)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.test.context.junit4.SpringTestMethod.invoke(SpringTestMethod.java:163)
	at org.springframework.test.context.junit4.SpringMethodRoadie.runTestMethod(SpringMethodRoadie.java:233)
	at org.springframework.test.context.junit4.SpringMethodRoadie$RunBeforesThenTestThenAfters.run(SpringMethodRoadie.java:333)
	at org.springframework.test.context.junit4.SpringMethodRoadie.runWithRepetitions(SpringMethodRoadie.java:217)
	at org.springframework.test.context.junit4.SpringMethodRoadie.runTest(SpringMethodRoadie.java:197)
	at org.springframework.test.context.junit4.SpringMethodRoadie.run(SpringMethodRoadie.java:143)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.invokeTestMethod(SpringJUnit4ClassRunner.java:142)
	at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51)
	at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:44)
	at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:26)
	at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:36)
	at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:42)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: java.lang.IllegalArgumentException: Removing a detached instance com.apolloti.Savior.domain.entity.endereco.Pais#2
	at org.hibernate.ejb.event.EJB3DeleteEventListener.performDetachedEntityDeletionCheck(EJB3DeleteEventListener.java:45)
	at org.hibernate.event.def.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:86)
	at org.hibernate.event.def.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:52)
	at org.hibernate.impl.SessionImpl.fireDelete(SessionImpl.java:766)
	at org.hibernate.impl.SessionImpl.delete(SessionImpl.java:744)
	at org.hibernate.ejb.AbstractEntityManagerImpl.remove(AbstractEntityManagerImpl.java:253)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:357)
	at $Proxy55.remove(Unknown Source)
	at org.springframework.orm.jpa.JpaTemplate$7.doInJpa(JpaTemplate.java:286)
	at org.springframework.orm.jpa.JpaTemplate.execute(JpaTemplate.java:184)
	... 48 more

...

Bom… se alguem tiver alguma idéia, é bem vinda!

Abraços e obrigado pela atenção.
\o/

Se vc quiser usar o remove do Jpa faça da seguinte maneira…

getJpaTemplate().remove(getJpaTemplate().getReference(bean.getClass(), serializable));Onde serializable será o Long, Integer que contém o valor da pk…

Agora se preferir via Hibernate ficaria assim…

Session session = (Session) getJpaTemplate().getEntityManagerFactory().createEntityManager().getDelegate(); session.delete(bean); session = null;

Poiseh, eu já havia tentado com getReference, e o resutlado é mais estranho ainda:

Hibernate: select pais0_.ID_PAIS as ID1_8_0_, pais0_.DT_ALTERACAO as DT2_8_0_, pais0_.DT_CADASTRO as DT3_8_0_, pais0_.NM_PAIS as NM4_8_0_, pais0_.SG_PAIS as SG5_8_0_ from SAVIOR_PAIS pais0_ where pais0_.ID_PAIS=?

Hibernate: select estados0_.FK_PAIS as FK6_1_, estados0_.ID_ESTADO as ID1_1_, estados0_.ID_ESTADO as ID1_7_0_, estados0_.DT_ALTERACAO as DT2_7_0_, estados0_.DT_CADASTRO as DT3_7_0_, estados0_.NM_ESTADO as NM4_7_0_, estados0_.FK_PAIS as FK6_7_0_, estados0_.SG_ESTADO as SG5_7_0_ from SAVIOR_ESTADO estados0_ where estados0_.FK_PAIS=?

Faz 2 selects e nenhum delete.

No ManyToOne do Pais mude para…

cascade = CascadeType.REMOVE, fetch = FetchType.EAGERE no Estado no OneToOne coloque…

cascade = {CascadeType.PERSIST, CascadeType.REMOVE} Tenta ae aqui eu uso assim e funciona.

Apesar de eu não entender o porque de mudar de Lazy para Eager , fiz como vc disse:

PAIS

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID_PAIS", unique = true, nullable = false)
	private int idPais;
	
	@Column(name = "NM_PAIS", nullable = false, length = 50)
	private String nmPais;
	
	@Column(name = "SG_PAIS", nullable = false, length = 3)
	private String sgPais;
	
	@OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, mappedBy = "pais")
	private Set<Estado> estados;

ESTADO

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID_ESTADO", unique = true, nullable = false)
	private int idEstado;
	
	@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE}  )
	@JoinColumn(name = "FK_PAIS", nullable = false)
	private Pais pais;
	
	@Column(name = "NM_ESTADO", nullable = false, length = 50)
	private String nmEstado;
	
	@Column(name = "SG_ESTADO", nullable = false, length = 3)
	private String sgEstado;
	
	@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "estado")
	private Set<Cidade> cidades;

E o resultado é que ele soh faz um select, devido a nao entrar a id neste metodo:

	public void remove(DomainObject obj)
	{
		log.info("Removendo o objeto "+obj.getClass().getName());
		
		obj = getJpaTemplate().getReference(clazz, ((Pais)obj).getIdPais());
		super.getJpaTemplate().remove(obj);
	}

Object antes de receber o getReference, está com uma Id setada, e depois da atribuicão, o objeto fica com todos os valores nulos.

Detalhe, meu ID, é primitivo do tipo int será que tem algo haver?

Abraços

\o

Véio acho que o problema está nas Annotations não tenho certeza masss segue exemplo do código que uso aqui e funciona normalmente…

[code]
@Entity(name = “tb_pessoa”)
@Inheritance(strategy = InheritanceType.JOINED)
public class PessoaBean implements Bean {

@OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, mappedBy = "pessoaBean")
public List<PessoaFisicaBean> getListPessoaFisica() {
	return listPessoaFisica;
}

@ManyToMany
	@JoinTable(name="TB_PESSOA_LOGRADOURO",
           joinColumns={@JoinColumn(name="ID_PESSOA")},
           inverseJoinColumns={@JoinColumn(name="ID_LOGRADOURO")})
public List<LogradouroBean> getListLogradouro() {
	return listLogradouro;
}

}

@Entity(name = “tb_pessoa_fisica”)
public class PessoaFisicaBean implements Bean {
@OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
@JoinColumn(name = “id_pessoa”, referencedColumnName = “id_pessoa”)
public PessoaBean getPessoaBean() {
return pessoaBean;
}
}

@Entity(name = “logradouro”)
public class LogradouroBean implements Bean {
}
[/code]As annotations dos relacionamentos, agora o teste unitário…[code]
public class RemoveCascadeTest extends TestCase {

private ApplicationContext context;

protected void setUp() throws Exception {
	context = new ClassPathXmlApplicationContext("applicationContext.xml");
	super.setUp();
}

public void testCreateAndRemove() throws Exception {
	try {
		context = new ClassPathXmlApplicationContext("applicationContext.xml");
		GenericBo genericBo = (GenericBo) context.getBean("genericBo");
		PessoaBean pessoaBean = new PessoaBean();
		
		//Create
		pessoaBean.setNome("Teste Pessoa");
		PessoaFisicaBean pessoaFisicaBean = new PessoaFisicaBean();
		pessoaFisicaBean.setPessoaBean(pessoaBean);
		pessoaFisicaBean.setCpf(new BigInteger("31076947790"));
		genericBo.create(pessoaFisicaBean);
		System.out.println("ID_PESSOA_FISICA = "+pessoaFisicaBean.getPessoaFisicaPK());
		
		// Remove all
		genericBo.remove(pessoaBean, pessoaBean.getPessoaPK());
		assertEquals(pessoaBean.getPessoaPK() != null, true);
	} catch(Exception e) {
		e.printStackTrace();
		throw e;
	}
}

}
[/code]No console printou as seguintes querys…

[quote]
Hibernate: select this_.id_pessoa as id1_21_1_, this_.nome as nome21_1_, this_1_.dt_nascimento as dt2_22_1_, this_1_.email as email22_1_, this_1_.rg as rg22_1_, case when this_1_.id_pessoa is not null then 1 when this_.id_pessoa is not null then 0 end as clazz_1_, listpessoa2_.id_pessoa as id3_3_, listpessoa2_.id_pessoa_fisica as id1_3_, listpessoa2_.id_pessoa_fisica as id1_26_0_, listpessoa2_.cpf as cpf26_0_, listpessoa2_.id_pessoa as id3_26_0_ from tb_pessoa this_ left outer join tb_dados_pessoais this_1_ on this_.id_pessoa=this_1_.id_pessoa left outer join tb_pessoa_fisica listpessoa2_ on this_.id_pessoa=listpessoa2_.id_pessoa where lower(this_.nome) like ?
Hibernate: insert into tb_pessoa (nome) values (?)
Hibernate: insert into tb_pessoa_fisica (cpf, id_pessoa) values (?, ?)
ID_PESSOA_FISICA = 4
Hibernate: select pessoabean0_.id_pessoa as id1_21_1_, pessoabean0_.nome as nome21_1_, pessoabean0_1_.dt_nascimento as dt2_22_1_, pessoabean0_1_.email as email22_1_, pessoabean0_1_.rg as rg22_1_, case when pessoabean0_1_.id_pessoa is not null then 1 when pessoabean0_.id_pessoa is not null then 0 end as clazz_1_, listpessoa1_.id_pessoa as id3_3_, listpessoa1_.id_pessoa_fisica as id1_3_, listpessoa1_.id_pessoa_fisica as id1_26_0_, listpessoa1_.cpf as cpf26_0_, listpessoa1_.id_pessoa as id3_26_0_ from tb_pessoa pessoabean0_ left outer join tb_dados_pessoais pessoabean0_1_ on pessoabean0_.id_pessoa=pessoabean0_1_.id_pessoa left outer join tb_pessoa_fisica listpessoa1_ on pessoabean0_.id_pessoa=listpessoa1_.id_pessoa where pessoabean0_.id_pessoa=?
Hibernate: delete from TB_PESSOA_LOGRADOURO where ID_PESSOA=?
Hibernate: delete from tb_pessoa_fisica where id_pessoa_fisica=?
Hibernate: delete from tb_pessoa where id_pessoa=?
[/quote] Verifica se pode te ajudar esse exemplo, falow!!!

Ahhh lembrando que se for base mysql a tabela tem que ser do tipo InnoDb…

Poiseh amigo, o relacionamento de seus objetos é um tanto diferente do meu, mas acredito que não seja o mapemaneto!

pois nem ser apenas um objeto independente, ele não exclui!

Alguém tem alguma outra idéia?

Bom, resolvi não do jeito que ei queria.

Antes apenas estendia de JpaDaoSupport, e teoricamente o Spring gerenciava o EntityManager, só que acho que ele estava sempre gerando uma nova instancia do EntityManager, logo nunca estava sincronizado, com isso, apenas fiz uso da annotation @PersistenceContext.

	@PersistenceContext
	public void setEntityManager(EntityManager entityManager)
	{
		log.debug("Setando a instancia do entityManager.");
		this.entityManager = entityManager;
	}

Abraços.
\o