Hibernate version:
3.2.4.sp1
Mapping documents:
Code:
<class name="Component" table="COMPONENTS">
<id name="id" column="ID" type="long" unsaved-value="0">
<generator class="native" />
</id>
<property name="businessKey" column="BUSINESS_KEY" type="long" not-null="true" unique="true" />
<many-to-one name="container" column="CONTAINER_ID" class="Container" not-null="true" />
</class>
<!-- element holder -->
<class name="Container" table="CONTAINERS">
<id name="id" column="ID" type="long" unsaved-value="0">
<generator class="native" />
</id>
<set name="components" cascade="all-delete-orphan" inverse="true">
<cache usage="read-write" />
<key column="CONTAINER_ID" on-delete="cascade" not-null="true" />
<one-to-many class="Component" />
</set>
</class>
Code between sessionFactory.openSession() and session.close():Code:
Transaction tx = session.beginTransaction();
Container cont = new Container();
cont.setComponents(new HashSet<Component>());
Component c1 = new Component();
c1.setBusinessKey(1);
c1.setContainer(cont);
cont.getComponents().add(c1);
session.save(cont);
tx.commit();
tx = session.beginTransaction();
cont.getComponents().clear();
Component c2 = new Component();
c2.setBusinessKey(1);
c2.setContainer(cont);
cont.getComponents().add(c2);
tx.commit();
Full stack trace of any exception that occurs:Code:
org.hibernate.exception.ConstraintViolationException: could not insert: [org.foo.Component]
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:71)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:40)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2158)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2638)
at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:48)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:298)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:181)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:107)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:187)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:172)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:94)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:507)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:499)
at org.hibernate.engine.CascadingAction$1.cascade(CascadingAction.java:218)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:268)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:216)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296)
at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:219)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
at org.hibernate.event.def.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:131)
at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:122)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:65)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at org.foo.test.CollectionTest.test1(CollectionTest.java:59)
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:585)
at junit.framework.TestCase.runTest(TestCase.java:154)
at junit.framework.TestCase.runBare(TestCase.java:127)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:118)
at junit.framework.TestSuite.runTest(TestSuite.java:208)
at junit.framework.TestSuite.run(TestSuite.java:203)
at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:130)
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: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: CONSTRAINT_INDEX_0 ON PUBLIC.COMPONENTS(BUSINESS_KEY) [23001-46]
at org.h2.message.Message.getSQLException(Message.java:65)
at org.h2.message.Message.getSQLException(Message.java:47)
at org.h2.index.Index.getDuplicateKeyException(Index.java:61)
at org.h2.index.TreeIndex.add(TreeIndex.java:53)
at org.h2.table.TableData.addRow(TableData.java:77)
at org.h2.command.dml.Insert.update(Insert.java:88)
at org.h2.command.CommandContainer.update(CommandContainer.java:64)
at org.h2.command.Command.executeUpdate(Command.java:120)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:127)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:116)
at org.hibernate.id.IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(IdentityGenerator.java:73)
at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:33)
... 48 more
The generated SQL (show_sql=true):Code:
Hibernate: insert into CONTAINERS (ID) values (null)
Hibernate: insert into COMPONENTS (ID, BUSINESS_KEY, CONTAINER_ID) values (null, ?, ?)
Hibernate: insert into COMPONENTS (ID, BUSINESS_KEY, CONTAINER_ID) values (null, ?, ?)
23:20:12,578 WARN [org.hibernate.util.JDBCExceptionReporter.logExceptions:77] SQL Error: 23001, SQLState: 23001
23:20:12,578 ERROR [org.hibernate.util.JDBCExceptionReporter.logExceptions:78] Unique index or primary key violation: CONSTRAINT_INDEX_0 ON PUBLIC.COMPONENTS(BUSINESS_KEY) [23001-46]
This problem I see for several years since v. 2.1.
There are several workarounds to overcome this insert-before-delete problem including
- removing unique constrant(not very good idea from consistency point of view),
- flusing session between deleting elements and adding new one (alsa clumsy thing since collection manipulation occurs in service layer, not persistence one, have to add special dao method to handle collection mutation),
- making collection of components instead of entity (not work in cases of component entity inheritance mapping)
...
not great solutions but help to solve problem, all impose some restrictions and break unintrusive persistence idea of hibernate.
Why do hibernate have to insert delete-orphan cascaded association before removing orphaned children anyway?