Removing a detached instance (Spring + Hibernate/JPA)

7 respostas
R

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/

7 Respostas

R

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;

R

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.

R

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.

R

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

R
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...
@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 {
}
As annotations dos relacionamentos, agora o teste unitário...
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("[telefone removido]"));
			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;
		}
	}

}
No console printou as seguintes querys...
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=?
Verifica se pode te ajudar esse exemplo, falow!!!

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

R

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?

R

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

Criado 28 de abril de 2008
Ultima resposta 29 de abr. de 2008
Respostas 7
Participantes 2