-->
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.  [ 4 posts ] 
Author Message
 Post subject: How in the...? Incorrect M2M collection item deletion.
PostPosted: Wed Aug 25, 2004 5:28 pm 
Regular
Regular

Joined: Tue Dec 09, 2003 2:39 pm
Posts: 106
Location: Toronto, Canada
Problem:
Haven't encountered this one before, and haven't read of anything like this in any of the forum topics.

We have an indexed M2M association between Subject and Attribute. Our association table is subject_attribute.

We have a junit test which populates the subject_attribute table with four associations for one particular subject.

When we remove an Attribute entity from the Subject's attributes collection it appears all is correct while debugging until we commit the transaction.

If we run our SQL profiler, we can see that Hibernate will incorrectly delete the wrong Attribute from the subject_attribute table and a pk violation is subsequently thrown because Hibernate attempts to adjust the indexes but updates a record with an already existing PK. Really! I can SQL output if necessary.

Any guidance would be helpful here.

Hibernate version: 2.1.6

Mapping documents:

Subject.hbm.xml:
Code:
        <list
            name="attributes"
            table="subject_attribute"
            lazy="false"
            inverse="false"
            cascade="none"
        >

              <key
                  column="subject_id"
              />

              <index
                  column="sort_order"
              />

              <many-to-many
                  class="com.casebank.spotlight.domain.Attribute"
                  column="attribute_id"
                  outer-join="true"
              />

        </list>


Code between sessionFactory.openSession() and session.close():

Code:
           
            Subject subject = (Subject) session.get(Subject.class, subjectId);
            Attribute attribute = (Attribute) session.get(Attribute.class, attributeId);
           
            if (subject.getAttributes().contains(attribute)) {
               subject.getAttributes().remove(attribute);
               session.update(subject);
               session.flush();
               success = true;
            }


Full stack trace of any exception that occurs:
Code:
ERROR [main] (JDBCException.java:38) - could not update collection rows: [com.casebank.spotlight.domain.Subject.attributes#70100000000000000000000000000017]
java.sql.SQLException: Violation of PRIMARY KEY constraint 'PK_group_attribute'. Cannot insert duplicate key in object 'subject_attribute'.
   at net.sourceforge.jtds.jdbc.SqlMessage.toSQLException(SqlMessage.java:85)
   at net.sourceforge.jtds.jdbc.SQLWarningChain.addOrReturn(SQLWarningChain.java:99)
   at net.sourceforge.jtds.jdbc.Tds.goToNextResult(Tds.java:837)
   at net.sourceforge.jtds.jdbc.TdsStatement.getMoreResults(TdsStatement.java:707)
   at net.sourceforge.jtds.jdbc.TdsStatement.executeCallImpl(TdsStatement.java:301)
   at net.sourceforge.jtds.jdbc.TdsStatement.internalExecuteCall(TdsStatement.java:281)
   at net.sourceforge.jtds.jdbc.PreparedStatement_base.execute(PreparedStatement_base.java:133)
   at net.sourceforge.jtds.jdbc.PreparedStatement_base.executeUpdate(PreparedStatement_base.java:286)
   at net.sf.hibernate.impl.NonBatchingBatcher.addToBatch(NonBatchingBatcher.java:22)
   at net.sf.hibernate.collection.BasicCollectionPersister.doUpdateRows(BasicCollectionPersister.java:123)
   at net.sf.hibernate.collection.AbstractCollectionPersister.updateRows(AbstractCollectionPersister.java:713)
   at net.sf.hibernate.impl.ScheduledCollectionUpdate.execute(ScheduledCollectionUpdate.java:48)
   at net.sf.hibernate.impl.SessionImpl.executeAll(SessionImpl.java:2414)
   at net.sf.hibernate.impl.SessionImpl.execute(SessionImpl.java:2370)
   at net.sf.hibernate.impl.SessionImpl.flush(SessionImpl.java:2236)
   at com.casebank.spotlight.dao.hibernate.HibernateSubjectDAO.deleteAttribute(HibernateSubjectDAO.java:746)
   at com.casebank.spotlight.dao.hibernate.HibernateSubjectDAOTest.testDeleteAttribute(HibernateSubjectDAOTest.java:471)
   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:324)
   at junit.framework.TestCase.runTest(TestCase.java:154)
   at com.casebank.spotlight.dao.hibernate.HibernateTestCase.runTest(HibernateTestCase.java:92)
   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 junit.extensions.TestDecorator.basicRun(TestDecorator.java:22)
   at junit.extensions.TestSetup$1.protect(TestSetup.java:19)
   at junit.framework.TestResult.runProtected(TestResult.java:124)
   at junit.extensions.TestSetup.run(TestSetup.java:23)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:421)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:305)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:186)


Name and version of the database you are using: MS SQL SERVER 2000


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 26, 2004 10:27 pm 
Regular
Regular

Joined: Tue Dec 09, 2003 2:39 pm
Posts: 106
Location: Toronto, Canada
Certainly this cannot be a Hibernate bug. Are there any glaring mistakes with our mapping, or our deletion idiom?

Any suggestions appreciated.

To state the problem in another way:

We have an indexed many-to-many collection that has values A,B,C,D. If we explicitly remove C from the collection upon a session.flush() we see that D is actually being deleted. A SQL Exception is later thrown when updating the table because it attempts to update C with B to in an attempt to adjust the indexes and of course a pk violation is thrown. Clear as mud?

Roll


Top
 Profile  
 
 Post subject:
PostPosted: Fri Aug 27, 2004 10:34 am 
Regular
Regular

Joined: Tue Dec 09, 2003 2:39 pm
Posts: 106
Location: Toronto, Canada
Here is our test case:

We have a Subject, whose ID is 70100000000000000000000000000017, that has a collection of 4 Attributes. We are attempting to delete Attribute 11100000000000000000000000000491 which is in indexed position 1 (of a zero based index)

Here is the code from the jUnit test:

Code:
   ...
   String subjectId = "70100000000000000000000000000017";
   String attributeId = "11100000000000000000000000000491";
         
   // delete
   assertTrue(((HibernateSubjectDAO) getMockDAO())
          .deleteAttribute(subjectId, attributeId));
   ...


Here is the relevant Subject DAO snippet:

Code:
           
   Subject subject = (Subject) session.get(Subject.class, subjectId);
   Attribute attribute = (Attribute) session.get(Attribute.class, attributeId);
           
   if (subject.getAttributes().contains(attribute)) {
           subject.getAttributes().remove(attribute);
               session.update(subject);
               session.flush();
               success = true;
   }


The snippet below is from the debug log:

Code:
DEBUG (BatcherImpl.java:237) - delete from subject_attribute where subject_id=? and sort_order=?
DEBUG (BatcherImpl.java:241) - preparing statement
DEBUG (NullableType.java:46) - binding '70100000000000000000000000000017' to parameter: 1
[color=red]DEBUG (NullableType.java:46) - binding '3' to parameter: 2[/color]
DEBUG (AbstractCollectionPersister.java:575) - done deleting collection rows: 1 deleted
DEBUG (AbstractCollectionPersister.java:710) - Updating rows of collection: com.casebank.spotlight.domain.Subject.attributes#70100000000000000000000000000017


The red highlighted text shows that 3 is being passed for the sort_order (index) parameter. However, in our jUnit test we are in fact attempting to remove sort_order (index) 1 from the collection.

Further up in the debug log, I see that the session is aware of all the Attributes in the Subject's collection:

Code:
DEBUG (Printer.java:75) - listing entities:
DEBUG (Printer.java:82) - com.casebank.spotlight.domain.Subject{attributes=[Attribute#11100000000000000000000000000485, Attribute#11100000000000000000000000000490, Attribute#11100000000000000000000000000500], descriptions=uninitialized, names=[22-00-00 AUTOFLIGHT], children=[Subject#70100000000000000000000000000055, Subject#70100000000000000000000000000077], id=70100000000000000000000000000017}
DEBUG (Printer.java:82) - com.casebank.spotlight.domain.Attribute{parentsIds=[70100000000000000000000000000002, 70100000000000000000000000000017], descriptions=uninitialized, attributeValues=uninitialized, questions=uninitialized, attributeType=AttributeType#10300000000000000000000000000006, names=[SymLogic], matchArgs=uninitialized, observationCost=ObservationCost#22100000000000000000000000000001, children=uninitialized, references=uninitialized, id=11100000000000000000000000000490, observationTime=ObservationTime#22200000000000000000000000000003}
[color=red]DEBUG (Printer.java:82) - com.casebank.spotlight.domain.Attribute{parentsIds=[70100000000000000000000000000017], descriptions=uninitialized, attributeValues=uninitialized, questions=uninitialized, attributeType=AttributeType#10300000000000000000000000000005, names=[SymLogic], matchArgs=uninitialized, observationCost=ObservationCost#22100000000000000000000000000001, children=uninitialized, references=uninitialized, id=11100000000000000000000000000491, observationTime=ObservationTime#22200000000000000000000000000003}[/color]
DEBUG (Printer.java:82) - com.casebank.spotlight.domain.Attribute{parentsIds=[70100000000000000000000000000004, 70100000000000000000000000000017, 70100000000000000000000000000022], descriptions=uninitialized, attributeValues=uninitialized, questions=uninitialized, attributeType=AttributeType#10300000000000000000000000000004, names=[SymLogic], matchArgs=uninitialized, observationCost=ObservationCost#22100000000000000000000000000001, children=uninitialized, references=uninitialized, id=11100000000000000000000000000485, observationTime=ObservationTime#22200000000000000000000000000003}
DEBUG (Printer.java:82) - com.casebank.spotlight.domain.Attribute{parentsIds=[70100000000000000000000000000017], descriptions=uninitialized, attributeValues=uninitialized, questions=uninitialized, attributeType=AttributeType#10300000000000000000000000000005, names=[Pressurization problem], matchArgs=uninitialized, observationCost=ObservationCost#22100000000000000000000000000001, children=uninitialized, references=uninitialized, id=11100000000000000000000000000500, observationTime=ObservationTime#22200000000000000000000000000003}


The red highlighted Attribute is in index position 1 and is the entity we are attempting to delete.

Where we run into the actual exception is when Hibernate tries to update the collection after having deleted the previous Attribute:

Code:
DEBUG (BatcherImpl.java:237) - update subject_attribute set attribute_id=? where subject_id=? and sort_order=?
DEBUG (BatcherImpl.java:241) - preparing statement
DEBUG (NullableType.java:46) - binding '70100000000000000000000000000017' to parameter: 2
DEBUG (NullableType.java:46) - binding '11100000000000000000000000000490' to parameter: 1
DEBUG (NullableType.java:46) - binding '1' to parameter: 3
DEBUG (BatcherImpl.java:203) - done closing: 0 open PreparedStatements, 0 open ResultSets
DEBUG (BatcherImpl.java:261) - closing statement
DEBUG (JDBCExceptionReporter.java:36) - SQL Exception
java.sql.SQLException: Violation of PRIMARY KEY constraint 'PK_group_attribute'. Cannot insert duplicate key in object 'subject_attribute'.


This is obviously also incorrect.

Please deposit 2cents here...


Top
 Profile  
 
 Post subject: And the envelope please...
PostPosted: Fri Aug 27, 2004 3:08 pm 
Regular
Regular

Joined: Tue Dec 09, 2003 2:39 pm
Posts: 106
Location: Toronto, Canada
So the answer is take your DBA out back and open a can of whoopass!!!

LOL...

Seriously, the issue was during the migration of our legacy database she incorrectly made the subject_id and attribute_id a composite primary key. However, many-to-many afficionados will tell you that the correct key should be subject_id (our key) and sort_order (our index).

Which raises a question: would it be possible upon startup when the session factory is being built with the appropriate XML mappings that it also validates that the correct keys are set for many-to-many collections in the schema and all other scenarios where required? Just a thought.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 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.