-->
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.  [ 2 posts ] 
Author Message
 Post subject: Map update and delete leads to exception
PostPosted: Thu Jun 10, 2004 8:26 pm 
Regular
Regular

Joined: Tue Oct 07, 2003 10:20 am
Posts: 77
I am currently having an issue with deleting an object after an update, where the parent object contains a Map and cascade is set to all-delete-orphan.

I apologise for the long post, but I can't explain it very well if it's short! Basically, I can't work out if what I'm doing is completely wrong, or if there is something funky going on with the cascade. A simple (as simple as it gets...) working example is available if required.

The environment setup is as follows:

Hibernate 2.1.4 (or 2.1.3)
MySQL 4.0.18, or Oracle 9.2.0.4
Latest available JDBC drivers for both DBs.

Simple Scenario:

A Project class contains a Set of possible Roles (unidirectional) for the entire Project, and a Map of Users(key) with UserDetails(value) for the Project. The UserDetails has a set of Roles (unidirectional) allocated to the User for the project.

I've narrowed the issue down to occur under the following circumstances (at the beginning of each step a new session is opened, at the end of each the session is flushed and closed):

1) Insert a User.
2) Create a project with two roles. Add the pre-persisted User, with new UserDetails mapping to "Role One". Insert Project.
3) Update the project - remove "User One" (and associated UserDetails). Update the Project (UserDetails correctly deleted).
4) Delete the project - ERROR - attempts to remove the UserDetails for "User One" again, fails with "Could not synchronize database state with session", since it has already been deleted.

Exception (MySQL for now, although I can obtain the Oracle one tomorrow morning):
Code:
Hibernate: delete from PROJECT_USER where PROJECT_USER_ID=?
00:41:34,540 DEBUG IntegerType:46 - binding '24' to parameter: 1
00:41:34,540 ERROR SessionImpl:2375 - Could not synchronize database state with session
net.sf.hibernate.HibernateException: Batch update row count wrong: 0
   at net.sf.hibernate.impl.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:65)
   at net.sf.hibernate.impl.BatcherImpl.executeBatch(BatcherImpl.java:122)
   at net.sf.hibernate.impl.BatcherImpl.prepareStatement(BatcherImpl.java:59)
   at net.sf.hibernate.impl.BatcherImpl.prepareStatement(BatcherImpl.java:56)
   at net.sf.hibernate.impl.BatcherImpl.prepareBatchStatement(BatcherImpl.java:109)
   at net.sf.hibernate.persister.EntityPersister.delete(EntityPersister.java:582)
   at net.sf.hibernate.impl.ScheduledDeletion.execute(ScheduledDeletion.java:29)
   at net.sf.hibernate.impl.SessionImpl.executeAll(SessionImpl.java:2414)
   at net.sf.hibernate.impl.SessionImpl.execute(SessionImpl.java:2372)
   at net.sf.hibernate.impl.SessionImpl.flush(SessionImpl.java:2236)
   at com.tangible.test.dao.BaseDAO.delete(BaseDAO.java:54)


The error doesn't occur if there is only one role associated with the project. I have checked the Hibernate LOG4J output on full debug - the correct SQL is being called and committed, and the entity listings are as follows:

Before update:
Code:
01:01:26,540 DEBUG Printer:75 - listing entities:
01:01:26,540 DEBUG Printer:82 - com.tangible.test.bo.Project{userDetails=[], roles=[Role#27, Role#28], name=NEW_PROJECT_NAME, id=14}
01:01:26,540 DEBUG Printer:82 - com.tangible.test.bo.User{name=USER_NAME|1, id=40}
01:01:26,540 DEBUG Printer:82 - com.tangible.test.bo.Role{name=ROLE_2, id=27}
01:01:26,540 DEBUG Printer:82 - com.tangible.test.bo.UserDetails{adminUser=false, roles=[Role#27, Role#28], lockedForProject=true, id=25}
01:01:26,540 DEBUG Printer:82 - com.tangible.test.bo.Role{name=ROLE_1, id=28}


After update:
Code:
01:01:26,603 DEBUG Printer:75 - listing entities:
01:01:26,618 DEBUG Printer:82 - com.tangible.test.bo.Project{userDetails=[], roles=[Role#27, Role#28], name=NEW_PROJECT_NAME, id=14}
01:01:26,618 DEBUG Printer:82 - com.tangible.test.bo.User{name=USER_NAME|1, id=40}
01:01:26,618 DEBUG Printer:82 - com.tangible.test.bo.Role{name=ROLE_2, id=27}
01:01:26,618 DEBUG Printer:82 - com.tangible.test.bo.Role{name=ROLE_1, id=28}


When it cascades through the Project object for final deletion (UserDetails object reappears):
Code:
01:01:26,650 DEBUG Printer:75 - listing entities:
01:01:26,650 DEBUG Printer:82 - com.tangible.test.bo.Project{userDetails=[], roles=[Role#27, Role#28], name=NEW_PROJECT_NAME, id=14}
01:01:26,650 DEBUG Printer:82 - com.tangible.test.bo.Role{name=ROLE_2, id=27}
01:01:26,650 DEBUG Printer:82 - com.tangible.test.bo.UserDetails{adminUser=false, roles=[Role#27, Role#28], lockedForProject=true, id=25}
01:01:26,650 DEBUG Printer:82 - com.tangible.test.bo.Role{name=ROLE_1, id=28}


Mapping Files:

Project Mapping File
Code:
<hibernate-mapping>
    <class name="com.tangible.test.bo.Project" table="PROJECT" dynamic-update="false" dynamic-insert="false">

        <id name="id" column="PROJECT_ID" type="int" unsaved-value="0">
            <generator class="native"/>
        </id>

        <property name="name" type="string" update="true" insert="true" access="property" column="PROJECT_NAME" length="25"/>

        <map name="userDetails" lazy="false" sort="unsorted" inverse="false" cascade="all-delete-orphan">
            <key column="PROJECT_ID"/>
            <index-many-to-many class="com.tangible.test.bo.User" column="USER_ID"/>
            <one-to-many class="com.tangible.test.bo.UserDetails"/>
        </map>

        <set name="roles" lazy="false" inverse="false" cascade="all-delete-orphan" sort="unsorted" >
            <key column="PROJECT_ID" />
            <one-to-many class="com.tangible.test.bo.Role" />
        </set>
    </class>
</hibernate-mapping>


UserDetails Mapping File
Code:
<hibernate-mapping>
    <class name="com.tangible.test.bo.UserDetails" table="PROJECT_USER" dynamic-update="false" dynamic-insert="false">

        <id name="id" column="PROJECT_USER_ID" type="int" unsaved-value="0">
            <generator class="native"/>
        </id>

        <property name="adminUser" type="boolean" update="true" insert="true" access="property" column="ADMIN_USER"/>

        <property name="lockedForProject" type="boolean" update="true" insert="true" access="property" column="LOCKED_FOR_PROJECT"/>

        <set name="roles" table="PROJECT_USER_ROLES" lazy="false" inverse="false" cascade="none" sort="unsorted">
            <key column="PROJECT_USER_ID"/>
            <many-to-many class="com.tangible.test.bo.Role" column="ROLE_ID" outer-join="auto"/>
        </set>
    </class>
</hibernate-mapping>


User Mapping File
Code:
<hibernate-mapping>
    <class name="com.tangible.test.bo.User" table="TANGIBLE_USER" dynamic-update="false" dynamic-insert="false">

        <id name="id" column="USER_ID" type="int" unsaved-value="0">
            <generator class="native"/>
        </id>

        <property name="name" type="string" update="true" insert="true" access="property" column="USERNAME" length="25"/>
    </class>
</hibernate-mapping>


Role Mapping File
Code:
<hibernate-mapping>
    <class name="com.tangible.test.bo.Role" table="TANGIBLE_ROLE" dynamic-update="false" dynamic-insert="false">

        <id name="id" column="ROLE_ID" type="int" unsaved-value="0">
            <generator class="native"/>
        </id>

        <property name="name" type="string" update="true" insert="true" access="property" column="ROLE_NAME" length="25"/>
    </class>
</hibernate-mapping>


Java Code for Updating:
Code:
Session session = SessionHandler.getCurrentSession();
Transaction trans = session.beginTransaction();
session.saveOrUpdateCopy(updateObject);
session.flush();
trans.commit();


Java Code for Deleting:
Code:
Session session = SessionHandler.getCurrentSession();
Transaction trans = session.beginTransaction();
session.delete(deleteObject);
session.flush();
trans.commit();


Any help or advice is much appreciated.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jun 11, 2004 5:44 am 
Regular
Regular

Joined: Tue Oct 07, 2003 10:20 am
Posts: 77
Well in creating my simple test case, it seems the code which breaks on MySQL now "works" on Oracle, although it is still attempting to delete entities which have already been deleted.

The SQL that's used is as follows (highlights in red show the statements that are repeated, and shouldn't be):

Hibernate: update PROJECT set PROJECT_NAME=? where PROJECT_ID=?
10:26:50,117 DEBUG StringType:46 - binding 'NEW_PROJECT_NAME' to parameter: 1
10:26:50,117 DEBUG IntegerType:46 - binding '11' to parameter: 2
Hibernate: delete from PROJECT_USER_ROLES where PROJECT_USER_ID=?
10:26:50,127 DEBUG IntegerType:46 - binding '12' to parameter: 1

Hibernate: update PROJECT_USER set PROJECT_ID=null, USER_ID=null where PROJECT_ID=?
10:26:50,127 DEBUG IntegerType:46 - binding '11' to parameter: 1
Hibernate: update TANGIBLE_ROLE set PROJECT_ID=null where PROJECT_ID=? and ROLE_ID=?
10:26:50,127 DEBUG IntegerType:46 - binding '11' to parameter: 1
10:26:50,127 DEBUG IntegerType:46 - binding '14' to parameter: 2
Hibernate: delete from PROJECT_USER where PROJECT_USER_ID=?
10:26:50,137 DEBUG IntegerType:46 - binding '12' to parameter: 1
Hibernate: delete from TANGIBLE_ROLE where ROLE_ID=?
10:26:50,137 DEBUG IntegerType:46 - binding '14' to parameter: 1

** UPDATED PROJECT **

** DELETING PROJECT **
Hibernate: update PROJECT_USER set PROJECT_ID=null, USER_ID=null where PROJECT_ID=?
10:26:50,157 DEBUG IntegerType:46 - binding '11' to parameter: 1
Hibernate: update TANGIBLE_ROLE set PROJECT_ID=null where PROJECT_ID=?
10:26:50,157 DEBUG IntegerType:46 - binding '11' to parameter: 1
Hibernate: delete from PROJECT_USER_ROLES where PROJECT_USER_ID=?
10:26:50,157 DEBUG IntegerType:46 - binding '12' to parameter: 1
Hibernate: delete from PROJECT_USER where PROJECT_USER_ID=?
10:26:50,167 DEBUG IntegerType:46 - binding '12' to parameter: 1

Hibernate: delete from TANGIBLE_ROLE where ROLE_ID=?
10:26:50,167 DEBUG IntegerType:46 - binding '13' to parameter: 1
10:26:50,167 DEBUG IntegerType:46 - binding '14' to parameter: 1
Hibernate: delete from PROJECT where PROJECT_ID=?
10:26:50,167 DEBUG IntegerType:46 - binding '11' to parameter: 1
** DELETED PROJECT **


I've stepped through with a debugger now and it seems that when you do the delete, with cascade set as all-delete-orphan, the orphaned objects it creates when it does the update are still present. Obviously, when it comes to do the deletion it therefore tries to redelete these orphans - is this expected functionality? I might just be expecting too much out of the cascade. I am going to add my test case to jira, as then you can at least see it in action (if you want to).

I still have a case of this breaking on Oracle, but it's not something I can easily strip out - the mappings are similar, there are just a lot more properties. The exception thrown is:

Code:
10:35:55,220 ERROR SessionImpl:2375 - Could not synchronize database state with session
net.sf.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) for com.qxlva.qure.datamodel.bo.RoleImpl instance with identifier: 75
   at net.sf.hibernate.persister.AbstractEntityPersister.check(AbstractEntityPersister.java:501)
   at net.sf.hibernate.persister.NormalizedEntityPersister.delete(NormalizedEntityPersister.java:610)
   at net.sf.hibernate.impl.ScheduledDeletion.execute(ScheduledDeletion.java:29)
   at net.sf.hibernate.impl.SessionImpl.executeAll(SessionImpl.java:2414)
   at net.sf.hibernate.impl.SessionImpl.execute(SessionImpl.java:2372)
   at net.sf.hibernate.impl.SessionImpl.flush(SessionImpl.java:2236)
   at com.qxlva.qure.datamodel.dao.ProjectDAO.delete(ProjectDAO.java:65)
   at com.qxlva.qure.datamodel.dao.ProjectDAOTest.delete(ProjectDAOTest.java:145)
   at com.qxlva.qure.datamodel.dao.DAOTest.tearDown(DAOTest.java:208)
   at junit.framework.TestCase.runBare(TestCase.java:130)
   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.textui.TestRunner.doRun(TestRunner.java:116)
   at com.intellij.rt.execution.junit2.IdeaJUnitAgent.doRun(IdeaJUnitAgent.java:57)
   at junit.textui.TestRunner.start(TestRunner.java:172)
   at com.intellij.rt.execution.junit.TextTestRunner2.startRunnerWithArgs(TextTestRunner2.java:23)
   at com.intellij.rt.execution.junit2.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:97)
   at com.intellij.rt.execution.junit2.JUnitStarter.main(JUnitStarter.java:31)
   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 com.intellij.rt.execution.application.AppMain.main(AppMain.java:78)


For the time being, I'm using the workaround of reloading the object before doing the deletion, which works, but I'd rather not do that.


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