Hibernate version: 2.x
Mapping documents:
Category->Product relation (parent->child)
<set name="products" lazy="true" inverse="true"
cascade="all-delete-orphan" sort="unsorted">
<key column="CATEGORY_FK"/>
<one-to-many class="xxx.Product"/>
</set>
Product->Category (child->parent)
<many-to-one name="category" class="xxx.Category"
cascade="none" outer-join="auto" update="true"
insert="true" column="CATEGORY_FK"
foreign-key="FK_PRODUCT_BELONGS_TO_CATEGORY"/>
Code between sessionFactory.openSession() and session.close():
Whats actually happens is:
//detach product from oldParent
oldParent.getProducts().remove(product);
product.setCategory(null);
//attach product to newParent category
newParent.getProducts().add(product);
product.setCategory(this);
[//flush it
flush()]
Summery and question:
Actually what I am trying to do is moving a product from one category to another. While moving a top-level product (parent-category=null) to
a category works well, moving a product to anywhere fails big time.
I guess the problem is that I have to remove the product from it's former category first and therfore the orphan is consider to get deleted too.
Hibernate seams to bind the orphan very stricktly to its parent following the composition assoziation with bound livecycle, once removed from parent it will stop existing.
So I am looking for a way to tell Hibernate that an adoptation is also occuring. You know one parent gives it child to another one and all three are living happily until the end of days.
Since this isn't covered in detail by the book (Hibernate in Action of cause) , I guess I have to ask the forum folks to help me.
Using anything other than the orphan cascade setting would force me to work through the entire object subgraph of the category and remove all children by myself if a delete occurs. Since the subgraph is quite huge this isn't a suitable option for me.
Another idea would be writing a SQL statement instead of using hibernate. Since a move action results in a rather simple update command (just category_fk=newValue) it is a no brainer. But I would like to keep things as abstract as possible, since this was what I thought I will get from Hibernate.
I have isolated the problem by a unit test (sure I did) and inlined the following code to proof the sql solution being a real solution for the problem:
Statement statement=getSession().connection().createStatement();
int affectedRowCount=statement.executeUpdate("update PRODUCTS set CATEGORY_FK="
+newParent.getId()+" where PRODUCT_ID="+product.getId()+" and "
+"CATEGORY_FK="+oldParent.getId());
assertEquals("One row should be affected by the statement",1,affectedRowCount);
I didn't used prepared statements for now, but I surely will if this enters production code. As long as it is on the unit test wildfarm it's ok. After calling the statement all affected elements have to be reloaded to be updated and before the statement, I flush the whole session. I believed I read a section about calling native SQL statments within the book but I only found the 'native SQL query' section... .
The sql solution seams to work and does only introduce minor problems. But one headache remains, it is not abstract nor simply to documentate, since it may raise many strange issues once the product is in production use.
So does anyone has a real 'Hibernate-solution' for my move orphans problem?
Full stack trace of any exception that occurs:
org.springframework.orm.hibernate.HibernateObjectRetrievalFailureException: deleted object would be re-saved by cascade (remove deleted object from associations): 13, of class: org.ocsn.domain.Category; nested exception is net.sf.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): 13, of class: org.ocsn.domain.Category
at org.springframework.orm.hibernate.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:421)
at org.springframework.orm.hibernate.HibernateTransactionManager.convertHibernateAccessException(HibernateTransactionManager.java:480)
at org.springframework.orm.hibernate.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:398)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:376)
at org.springframework.transaction.interceptor.TransactionAspectSupport.doCommitTransactionAfterReturning(TransactionAspectSupport.java:278)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:67)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:140)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:153)
at $Proxy0.setParent(Unknown Source)
... 36 more
Caused by: net.sf.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): 13, of class: org.ocsn.domain.Category
at net.sf.hibernate.impl.SessionImpl.forceFlush(SessionImpl.java:752)
at net.sf.hibernate.impl.SessionImpl.save(SessionImpl.java:730)
at net.sf.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:1376)
at net.sf.hibernate.engine.Cascades$4.cascade(Cascades.java:114)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:436)
at net.sf.hibernate.engine.Cascades.cascadeCollection(Cascades.java:526)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:452)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:503)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:482)
at net.sf.hibernate.impl.SessionImpl.preFlushEntities(SessionImpl.java:2673)
at net.sf.hibernate.impl.SessionImpl.flushEverything(SessionImpl.java:2250)
at net.sf.hibernate.impl.SessionImpl.flush(SessionImpl.java:2239)
at net.sf.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:61)
at org.springframework.orm.hibernate.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:386)
Name and version of the database you are using:
Mysql + HSQL-DB + Spring
|