-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 posts ] 
Author Message
 Post subject: list-index column not updated on delete
PostPosted: Thu Aug 14, 2008 4:10 pm 
Newbie

Joined: Thu Aug 14, 2008 3:47 pm
Posts: 4
Hibernate version: 3.2.6
MySQL version: 5.0.51a-community-nt

I'm having a problem with Hibernate not maintaining the list-index column in a many-to-many relationship. I've reproduced the problem in this simple test case:

I have a many-to-many relationship between Gang and Biker (I know: in real life, a biker would never belong to more than one gang; please suspend disbelief ;) The Gang side of the relationship uses a List of Biker objects, as the Bikers must be ordered within a given Gang. The biker side uses a Set of gangs. The latter side is marked inverse=true.

When a biker record is deleted using Session.delete(), the appropriate record is deleted from the join table, but the list-index column is not re-ordered, so that when a gang that formerly had that Biker as a member is accessed, a NullPointerException is thrown when iterating through that Gang's Biker List.

Here are my tables:

Code:
gang:
- id
- name

biker:
- id
- name

gang2biker:
- gang_id
- biker_id
- ordinal


And my mappings:

Code:
<class name="test.Gang"
   table="gang"
   lazy="true">
...
   <list name="bikers" lazy="true" table="gang2biker" cascade="save-update">
      <key column="gang_id"/>
      <list-index column="ordinal"/>
      <many-to-many column="biker_id" class="test.Biker"/>
   </list>            
</class>


<class name="test.Biker"
   table="biker"
   lazy="true">
...         
   <set name="gangs" lazy="true" table="gang2biker" cascade="save-update" inverse="true">
      <key column="biker_id"/>
      <many-to-many column="gang_id" class="test.Gang"/>
   </set>
</class>


Data in the join table prior to deleting might look like this:

Code:
gang_id        biker_id        ordinal
1                  1                  0
1                  2                  1
1                  3                  2


Now let's delete biker number 2:

Code:
session.delete(biker2);
tx.commit();


The data in the join table now looks like this:

Code:
gang_id        biker_id        ordinal
1                  1                  0
1                  3                  2


There is now a gap between the values in the "ordinal" (the list-index) column. The NPE is then thrown when iterating through List of Biker objects in a gang that formerly had that biker as a member:

Code:
for (Iterator i = g1.getBikers().iterator() ; i.hasNext() ; ) {
      Biker b = (Biker)i.next();
      System.out.println(" name: " + b.getName()); // <-- Throws NPE
}



I believe this is the result of the numerical gap in the list-index column.

I realize this issue has been discussed in the forums, but I've yet to come up w/ a workable solution. unfortunately, this problem was discovered in a system after it's been in use for some time, so a major re-architecture is problematic. I'm hoping for a a way to solve this at the hibernate configuration level. Is the only solution to iterate through all the gangs that have this biker as a member and delete it from each List?

Thanks for any help.
Ken


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 14, 2008 7:10 pm 
Newbie

Joined: Thu Aug 14, 2008 3:47 pm
Posts: 4
OK... I think this is a case of me not fully grasping what hibernate can reasonably be expected to do. I guess what I need to do is not just delete the record, but remove it from any lists that have it as a member. e.g, instead of merely doing this:

Code:
session.delete(biker);


I need to do this:

Code:
for (Iterator i = biker.getGangs().iterator() ; i.hasNext() ; ) {
   Gang gang = (Gang)i.next();
   gang.getBikers().remove(biker);
}
session.delete(biker);


Have I seen the light?


Top
 Profile  
 
 Post subject:
PostPosted: Fri Aug 15, 2008 2:19 pm 
Newbie

Joined: Wed Aug 13, 2008 12:48 pm
Posts: 7
Location: Mérida, Yucatán, México
you'r right!!! ;)


Top
 Profile  
 
 Post subject: Problem implementing cleanup method in mapped object
PostPosted: Fri Aug 15, 2008 7:37 pm 
Newbie

Joined: Thu Aug 14, 2008 3:47 pm
Posts: 4
Got it! :) But I am now faced with a new wrinkle. It seems sensible to me to build the "cleanup" methods right into the mapped dataobjects. I.e., in my Biker object, I provide the method:

Code:
   public void cleanup() {
      for ( Iterator i = gangs.iterator() ; i.hasNext() ; ) {
         Gang g = (Gang)i.next();
         g.getBikers().remove(this);
      }
   }



I've made this method part of an interface "HouseKeeper", as my app may deal w/ mapped objects of different types, and I wish to be able to call it like so:

Code:
Object o = methodForLoadingObjectsOfVariousTypes(); // returns a Biker object in this case
HouseKeeper hk = (HouseKeeper)o;
hk.cleanup(); // first removes this biker from any gang's biker collections
sess.delete(o); // then delete this biker
tx.commit();


This produces the exception:

Code:
Exception in thread "main" org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [com.kqed.simplecms.test.list.Biker#33]
   at org.hibernate.impl.SessionImpl.forceFlush(SessionImpl.java:1014)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:165)
   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$5.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 com.kqed.simplecms.test.list.TestList.deleteById(TestList.java:126)
   at com.kqed.simplecms.test.list.TestList.main(TestList.java:24)


The funny thing is that this worked fine before I encapsulated the code into the biker object. That is, when I did the cleanup "inline", like so:

Code:
for (Iterator i = biker.getGangs().iterator() ; i.hasNext() ; ) {
   Gang gang = (Gang)i.next();
   gang.getBikers().remove(biker);
}
session.delete(biker);


It worked fine. Upon closer inspection with a debugger, I saw that in my calling code, my biker object reported itself to be:

Biker$$EnhancerByCGLIB$$2e8dd29a

So when I used this object when calling:

Code:
gang.getBikers().remove(biker);


follwed by:

Code:
session.delete(biker);


it works. However when I call:

Code:
biker.cleanup();


and step through that method internal to biker, I see that the this variable reports itself as a plain old Biker class (not the CGLIB generated class), so that when I call:

Code:
gang.getBikers().remove(this);


and later, after returning from the cleanup() method, call:

Code:
biker.cleanup();


... the results are bad, so that when I call tx.commit(), I get that exception.

I guess I'm expecting java to behave like java here, but the introduction of the CGLIB-generated objects is causing the PersistentSet.remove() method to not work as expected.

I would greatly appreciate any recommendations on how to handle this correctly!

Thanks!
Ken


Top
 Profile  
 
 Post subject:
PostPosted: Fri Aug 15, 2008 8:17 pm 
Newbie

Joined: Thu Aug 14, 2008 3:47 pm
Posts: 4
Once again I solved my own problem:

I needed to implement equals() and hashCode() in my dataobject.

(embarrassed)


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 5 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.