Inserção duplicada ao usar EntityLifeCycleListener

5 respostas
joaosavio

Olá pessoas!
Estou com um erro estranho de inserção duplicada em minha aplicacao. Vou resumir o problema e depois coloco os codigos.

Tenho as seguintes classes:

  • TestEntity - entidade simples com um metodo @PrePersist que está permanecendo na transação apos ela ser comitada pelo container
  • Auditoria - entidade de auditoria
  • Dataset - interface do DatasetBean
  • DatasetBean - Stateless bean que implementa Dataset
  • DatasetFactory - instancia um EJB de dataset (faz um lookup)
  • PersistenceLifeCycleListener - um listener do ciclo de vida das entidades, definido no arquivo ORM.xml (segundo o site http://docs.jboss.org/hibernate/core/4.0/hem/en-US/html/listeners.html)

Coloquei o problema em um teste junit (estou usando Glassfish embedded):

@Test
public void test() throws NamingException {
    Dataset<TestEntity> dataset = this.lookupBy(DatasetBean.class);
    Assert.assertNotNull(dataset);

    TestEntity t = new TestEntity();        
    t.setName(UUID.randomUUID().toString());

    dataset.inserir(t);
    System.out.println("fim");
}

O fluxo do teste é o seguinte:

  1. Após ter um objeto Dataset realizo a inserção
@Stateless
@EJB(name = "java:global/br/com/joaosavio/dataset/Dataset", beanInterface = Dataset.class)
public class DatasetBean<T> implements Dataset<T> {

    @PersistenceContext(type = PersistenceContextType.TRANSACTION)
    private EntityManager entityManager;
    
    @Override
    public void inserir(T entidade) {
        LOG.info("Inserting: " + entidade);
        entityManager.persist(entidade);
    }
...
}
  1. Após a inserção, o listener PostPersist é chamado, e tento inserir uma entidade de auditoria. Aqui, se eu descomentar //dataset.getEntityManager().clear(), o teste passa. Caso contrário dá erro (log abaixo).
public class PersistenceLifeCycleListener {

    public void postPersist(Entidade entidade) {
        LOG.info("Post-persist event");
        if (!entidade.getClass().isAnnotationPresent(ClasseNaoAuditavel.class)) {
            Dataset<Auditoria> dataset = DatasetFactory.criarDataset(); // basicamente faz um lookup
            //dataset.getEntityManager().clear(); 
            Auditoria auditoria = new Auditoria();
            auditoria.setIdEntidade(String.valueOf(entidade.getId()));
            dataset.inserir(auditoria);
        }

    }
public class DatasetFactory {
    public static Dataset criarDataset() {
        try {
            return (Dataset) new InitialContext().lookup("java:global/br/com/joaosavio/dataset/Dataset");
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}

Log - reparem nas linhas 8 e 12, o hibernate insere a mesma entidade:

...
INFO: embedded was successfully deployed in 28.963 milliseconds.
PlainTextActionReporterSUCCESSDescription: deploy AdminCommandApplication deployed with name embedded.
    [name=embedded
2012-01-05 01:32:47,726 [main] INFO  com.joaosavio.model.db.DatasetBean (DatasetBean.java:32) - Inserting: TestEntity{id=null, name=221918b3-747f-4b7e-96c7-51d80e81d1ee}
2012-01-05 01:32:48,129 [main] INFO  com.joaosavio.event.PersistenceLifeCycleListener (PersistenceLifeCycleListener.java:26) - Pre-persist event
Hibernate: select max(testentity0_.id) as col_0_0_ from TestEntity testentity0_
Hibernate: insert into TestEntity (name, id) values (?, ?)
2012-01-05 01:32:49,932 [main] INFO  com.joaosavio.event.PersistenceLifeCycleListener (PersistenceLifeCycleListener.java:30) - Post-persist event
2012-01-05 01:32:49,935 [main] INFO  com.joaosavio.model.db.DatasetBean (DatasetBean.java:32) - Inserting: Auditoria{id=null, idEntidade=100}
2012-01-05 01:32:49,937 [main] INFO  com.joaosavio.event.PersistenceLifeCycleListener (PersistenceLifeCycleListener.java:26) - Pre-persist event
Hibernate: insert into TestEntity (name, id) values (?, ?)
05/01/2012 01:32:49 com.sun.ejb.containers.BaseContainer postInvoke
AVISO: A system exception occurred during an invocation on EJB DatasetBean method public void com.joaosavio.model.db.DatasetBean.inserir(java.lang.Object)
javax.ejb.TransactionRolledbackLocalException: Exception thrown from bean
	at com.sun.ejb.containers.BaseContainer.checkExceptionClientTx(BaseContainer.java:5049)
	at com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:4884)
	at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:2039)
	at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1990)
	at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:222)
	at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:88)
	at $Proxy121.inserir(Unknown Source)
	at com.joaosavio.event.PersistenceLifeCycleListener.postPersist(PersistenceLifeCycleListener.java:36)
	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:597)
	at org.hibernate.ejb.event.ListenerCallback.invoke(ListenerCallback.java:48)
	at org.hibernate.ejb.event.EntityCallbackHandler.callback(EntityCallbackHandler.java:110)
	at org.hibernate.ejb.event.EntityCallbackHandler.postCreate(EntityCallbackHandler.java:83)
	at org.hibernate.ejb.event.EJB3PostInsertEventListener.onPostInsert(EJB3PostInsertEventListener.java:49)
	at org.hibernate.action.internal.EntityInsertAction.postInsert(EntityInsertAction.java:145)
	at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:123)
	at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:273)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:265)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:186)
	at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:323)
	at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:52)
	at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1081)
	at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:315)
	at org.hibernate.engine.transaction.synchronization.internal.SynchronizationCallbackCoordinatorImpl.beforeCompletion(SynchronizationCallbackCoordinatorImpl.java:104)
	at org.hibernate.engine.transaction.synchronization.internal.RegisteredSynchronization.beforeCompletion(RegisteredSynchronization.java:53)
	at com.sun.enterprise.transaction.JavaEETransactionImpl.commit(JavaEETransactionImpl.java:435)
	at com.sun.enterprise.transaction.JavaEETransactionManagerSimplified.commit(JavaEETransactionManagerSimplified.java:852)
	at com.sun.ejb.containers.BaseContainer.completeNewTx(BaseContainer.java:5114)
	at com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:4879)
	at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:2039)
	at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1990)
	at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:222)
	at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:88)
	at $Proxy121.inserir(Unknown Source)
	at com.joaosavio.ContainerTest.test(ContainerTest.java:81)
	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:597)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
	at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:35)
	at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:115)
	at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:97)
	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:597)
	at org.apache.maven.surefire.booter.ProviderFactory$ClassLoaderProxy.invoke(ProviderFactory.java:103)
	at $Proxy0.invoke(Unknown Source)
	at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:150)
	at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:91)
	at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69)
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Violation of PRIMARY KEY constraint 'PK__TestEntity__52442E1F'. Cannot insert duplicate key in object 'dbo.TestEntity'.
	at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1356)
	at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1284)
     ...

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="simulajava" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>jdbc/trimpaper</jta-data-source>
    <class>com.joaosavio.model.vo.TestEntity</class>
    <class>com.joaosavio.model.vo.Auditoria</class>
    <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.SQLServerDialect"/>
      <property name="hibernate.current_session_context_class" value="jta"/>
      <property name="hibernate.session_factory_name" value="java:global/hibernate/SessionFactory"/>
      <property name="hibernate.archive.autodetection" value="class"/>
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
    </properties>
  </persistence-unit>
</persistence>

orm.xml

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm
                 http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
                 version="2.0">
    <persistence-unit-metadata>
        <persistence-unit-defaults>
            <entity-listeners>
                <entity-listener class="com.joaosavio.event.PersistenceLifeCycleListener">
                    <pre-persist method-name="prePersist"/>
                    <post-persist method-name="postPersist"/>
                </entity-listener>
            </entity-listeners>
        </persistence-unit-defaults>
    </persistence-unit-metadata>
</entity-mappings>

TestEntity

@Entity
public class TestEntity implements Entidade {
    @Id
    private Integer id;
    private String name;
    // sets e gets   
    
    @PrePersist
    public void preencherId() {
        if (getId() == null || getId() == 0) {
            Dataset d = DatasetFactory.criarDataset();
            Integer i = (Integer) d.fetchJPQLFirstResult("SELECT MAX(te.id) FROM TestEntity te", null);

            if (i == null || i < 100) {
                setId(100);
            } else {
                setId(i + 1);
            }
        }
    }
}

Auditoria

@ClasseNaoAuditavel
@Entity
public class Auditoria implements Entidade {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String idEntidade;
    //sets e gets

Entidade

public interface Entidade extends Serializable {
    Integer getId();
}

Uma gambiarra para resolver o problema é dar um clear no EntityManager. O que estou fazendo de errado?

5 Respostas

Hebert_Coelho

Faça um teste troque para auto:
@GeneratedValue(strategy = GenerationType.AUTO)

joaosavio

Mesmo erro :frowning:

joaosavio

Atualizei a descricao com o meu persistence.xml e retornei o método @PrePersist (original do problema). Tanto de um jeito quanto de outro da o mesmo problema

Hebert_Coelho

Agora que você falou desse @PrePersist que eu fui reparar.

Sua entidade está com @GeneratedValue(strategy = GenerationType.IDENTITY) e vc seta o ID dele na unha assim mesmo?
Você sabe como funciona o @GeneratedValue?
Retire essa linha e faça o teste.

joaosavio

Nao nao, o @PrePersist é so na TestEntity, e ela nao esta com @GeneratedValue :slight_smile:

absss

Criado 4 de janeiro de 2012
Ultima resposta 5 de jan. de 2012
Respostas 5
Participantes 2