Hi people!
I have a weird error of double insert in the DB. I'll summarize the problem and then put the codes.
I have the following classes:
- TestEntity - entity with a @PrePersist method. This entity is staying in the transaction after it is committed by the container
- Auditing - audit entity
- Dataset<T> - interface of DatasetBean<T>
- DatasetBean<T> - Stateless bean which implements Dataset
- DatasetFactory - instance an EJB of Dataset (lookup)
- PersistenceLifeCycleListener - an entity lifecycle listener, defined in orm.xml file (following the example in
http://docs.jboss.org/hibernate/core/4. ... eners.html)
I put the problem in a junit test (I'm using embedded Glassfish):
Code:
@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.insert(t);
System.out.println("end");
}
The flow test is the following:
1. After gettin a Dataset object, I try to insert a TestEntity object
Code:
@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 insert(T entity) {
LOG.info("Inserting: " + entity);
entityManager.persist(entity);
}
...
}
2. After insertion, the PostPersist listener is called, and I try to insert an auditing entity. Here, if I uncomment //dataset.getEntityManager().clear(), the test passes. Otherwise I got an error (log below).
Code:
public class PersistenceLifeCycleListener {
public void postPersist(MyEntity entity) {
LOG.info("Post-persist event");
if (!entity.getClass().isAnnotationPresent(ClassNotAuditable.class)) {
Dataset<Auditing> dataset = DatasetFactory.createDataset(); // basically a lookup
//dataset.getEntityManager().clear();
Auditing auditing = new Auditing();
auditing.setIdEntity(String.valueOf(entity.getId()));
dataset.insert(auditing);
}
}
...
}
Code:
public class DatasetFactory {
public static Dataset createDataset() {
try {
return (Dataset) new InitialContext().lookup("java:global/br/com/joaosavio/dataset/Dataset");
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
Log - look out lines 7 and 11, hibernate inserted the same entity twice:
Code:
...
INFO: embedded was successfully deployed in 16.336 milliseconds.
2012-01-05 02:53:27,068 [main] INFO com.joaosavio.model.db.DatasetBean (DatasetBean.java:30) - Inserting: TestEntity{id=null, name=035a2be3-211f-4d08-b7ad-146c006ea4ea}
2012-01-05 02:53:27,560 [main] INFO com.joaosavio.event.PersistenceLifeCycleListener (PersistenceLifeCycleListener.java:25) - 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 02:53:29,099 [main] INFO com.joaosavio.event.PersistenceLifeCycleListener (PersistenceLifeCycleListener.java:29) - Post-persist event
2012-01-05 02:53:29,102 [main] INFO com.joaosavio.model.db.DatasetBean (DatasetBean.java:30) - Inserting: Auditoria{id=null, idEntidade=100}
2012-01-05 02:53:29,103 [main] INFO com.joaosavio.event.PersistenceLifeCycleListener (PersistenceLifeCycleListener.java:25) - Pre-persist event
Hibernate: insert into TestEntity (name, id) values (?, ?)
05/01/2012 02:53:29 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.insert(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)
2012-01-05 02:53:29,108 [main] WARN org.hibernate.engine.jdbc.spi.SqlExceptionHelper (SqlExceptionHelper.java:143) - SQL Error: 2627, SQLState: 23000
at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:222)
2012-01-05 02:53:29,109 [main] ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper (SqlExceptionHelper.java:144) - Violation of PRIMARY KEY constraint 'PK__TestEntity__65570293'. Cannot insert duplicate key in object 'dbo.TestEntity'.
at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:88)
at $Proxy111.insert(Unknown Source)
at com.joaosavio.event.PersistenceLifeCycleListener.postPersist(PersistenceLifeCycleListener.java:35)
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 $Proxy111.insert(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__65570293'. 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
Code:
<?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.Auditing</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.show_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>
orm.xml
Code:
<?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
Code:
@Entity
public class TestEntity implements MyEntity {
@Id
private Integer id;
private String name;
// sets and gets
@PrePersist
public void fillId() {
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);
}
}
}
}
Auditing
Code:
@ClassNotAuditable
@Entity
public class Auditing implements MyEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String idEntity;
//sets and gets
MyEntity
Code:
public interface MyEntity extends Serializable {
Integer getId();
}
What am I doing wrong?