Hibernate Books

All times are UTC - 5 hours [ DST ]



Post new topic Reply to topic  [ 7 posts ] 
Author Message
 Post subject: ConcurrentModificationException during cascade?
PostPosted: Mon Sep 22, 2003 8:04 am 
Newbie

Joined: Tue Sep 02, 2003 8:19 pm
Posts: 19
Hello,

I'm doing the following: I have an AbstractAnswer class, with subclasses Answer and AnswerSet. An AnswerSet can contain objects of type AbstractAnswer, so I can build a tree structure with this.

The mappings are:
<class
name="de.rpf.customer.idg.mafo2.data.AbstractAnswer"
table="answers"
discriminator-value="abstract"
>
<id name="id" unsaved-value="0">
<generator class="native"/>
</id>
<discriminator column="class"/>
<many-to-one name="parentQuestion"/>
<many-to-one name="parentSet" column="setId"/>
<subclass name="de.rpf.customer.idg.mafo2.data.Answer" discriminator-value="answer">
</subclass>

<subclass name="de.rpf.customer.idg.mafo2.data.AnswerSet" discriminator-value="set">
<list
name="answers"
lazy="false"
cascade="all-delete-orphan"
>
<key column="setId"/>
<index column="listIndex"/>
<one-to-many class="de.rpf.customer.idg.mafo2.data.AbstractAnswer"/>
</list>
</subclass>
</class>
The ParentSet field in AbstractAnswer is a reference of type AnswerSet and may be null for the root AnswerSet.
When I create a new Answer and add it to the root AnswerSet it all works fine. Now when I create a new AnswerSet, set its parentSet attribute and then add it to the root AnswerSet, it should be saved during cascade. Instead I get this when I commit the transaction:

java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:445)
at java.util.AbstractList$Itr.next(AbstractList.java:418)
at net.sf.hibernate.collection.PersistentCollection$IteratorProxy.next(PersistentCollection.java:262)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:298)
at net.sf.hibernate.engine.Cascades.cascade(Cascades.java:341)
at net.sf.hibernate.impl.SessionImpl.preFlushEntities(SessionImpl.java:2285)
at net.sf.hibernate.impl.SessionImpl.flushEverything(SessionImpl.java:2015)
at net.sf.hibernate.impl.SessionImpl.flush(SessionImpl.java:2004)
at net.sf.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:57)
...

I'm using Hibernate 2.0.3 on PostgreSQL 7.2.

I'm pretty sure I'm doing something wrong, but I can't find it. Any ideas?

Thanks
Carl-Eric


Top
 Profile  
 
 Post subject:
PostPosted: Mon Sep 22, 2003 11:23 am 
Newbie

Joined: Tue Sep 02, 2003 8:19 pm
Posts: 19
Addendum: The code used to do it looks like this:

AnswerSet rootSet = getRootSet();
new AnswerSet(rootSet.getParentQuestion(), rootSet);
t.commit();

The constructor looks like this:

AnswerSet(Question parentQ, AnswerSet parentS)
{
this.setParentQuestion(parentQ);
this.setParentSet(parentS);
}

The setParentSet method adds calls parentSet.addAnswer(this) to add itself to the parent AnswerSet. As far as I can see this *should* then be properly cascaded, so the new AnswerSet is saved.

The exception happens inside the t.commit().

Thanks
Carl-Eric


Top
 Profile  
 
 Post subject: SOLVED (at least temporarily)
PostPosted: Tue Sep 23, 2003 11:52 am 
Newbie

Joined: Tue Sep 02, 2003 8:19 pm
Posts: 19
Ok, I think I've worked around this now.

I changed the mapping of parentSet in AbstractAnswer to have its own colum and not rely on the same column as the list in AnswerSet (it is now <many-to-one name="parentSet" column="parentSet"/>). I am now maintaining the link back to the parent AnswerSet "manually".

One question remains, though: What happened here? Was what I originally did really wrong? Is what I do now the correct way to do it? Or is there a more elegant way to handle this?

Thanks
Carl-Eric


Top
 Profile  
 
 Post subject:
PostPosted: Tue Sep 23, 2003 2:29 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
I dunno, sounds like you did something "funny" in your Java code, like accessed the collection during the cascade. But I can't see any code, so I've no idea.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Sep 23, 2003 2:53 pm 
Newbie

Joined: Tue Sep 02, 2003 8:19 pm
Posts: 19
gavin wrote:
I dunno, sounds like you did something "funny" in your Java code, like accessed the collection during the cascade. But I can't see any code, so I've no idea.


Well *that* can easily be changed :-)

Though I am quite certain I didn't do any funny things with it. I created the new set, committed the Transaction, and only then continued with my application. And I am 100% certain that there was no other thread doing anything with it either.

The code:
Code:
        try
        {
            t = DAOTransaction.beginTransaction(); // This is just a wrapped Hibernate Transaction, commit() and rollback() go straight through to the Hibernate Transaction object

            Long answerSetId = (Long) dform.get("answerSetId");
             // this loads the parent AnswerSet:
            AnswerSet aset = t.getAnswerDAO().getAnswerSet(answerSetId);
            User u = (User) req.getSession().getAttribute("user");
            String type = (String) dform.get("type");

             // check to see whether the current user is allowed to do this
            if (aset.getParentQuestion().getPoll().checkEditPrivileges(u))
            {
                if ("single".equals(type))
                {
                     // create a new single Answer, this has always worked
                    new Answer(aset.getParentQuestion(), aset);
                }
                else if ("set".equals(type))
                {
                     // create an AnswerSet
                    new AnswerSet(aset.getParentQuestion(), aset);
                }
                 // everything works fine up to here!
                 // if I created an AnswerSet, this call bombed:
                t.commit();
            }
            else
            { // No rights, no change: rollback
                t.rollback();
            }

            t = null;
        }
        catch (DAOException e) // a wrapped hibernate exception
        {
            if (t != null)
            {
                t.rollback();
            }

            log.warn(e);
            throw e;
        }
        // and return to the rest of the webapp. everything else that could happen happens in a new hibernate session
        return mapping.findForward("success");


The constructor of AbstractAnswer simply sets the reference fields to parentSet and parentQuestion, and adds the Answer to parentSet, if necessary. That is all done by the time it gets to t.commit(), though.

With the AnswerSet list and the parentSet field sharing a column, the t.commit() call throws the exception mentioned in the original post. Now that they don't share that column any more it works.

It works now, so I am just interested in this out of curiosity, and to understand Hibernate better, because it does seem to me that it is just a workaround. If you think it's not worth investigating more, that's fine, though.

Thanks for your help, even though we users are a pain sometimes, Gavin ;-)

Carl-Eric


Top
 Profile  
 
 Post subject:
PostPosted: Tue Sep 23, 2003 3:07 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 12:50 pm
Posts: 5130
Location: Melbourne, Australia
And do you implement Lifecycle or Interceptor, or do anything in a getter or setter?


Top
 Profile  
 
 Post subject:
PostPosted: Tue Sep 23, 2003 3:41 pm 
Newbie

Joined: Tue Sep 02, 2003 8:19 pm
Posts: 19
Lifecycle and Interceptor: No.

Getter and setter... let me check... argh. I didn't see that. Mea culpa. Yes, I was modifying the parent AnswerSet in the setParentSet setter - I was simply adding the AbstractAnswer itself to the parent AS again (parent.addAnswer(this)). That it worked with my "workaround" was only due to the fact that for the workaround I also changed addAnswer to check whether the List contained this Answer already. Looking at it now, it seems that the original implementation was buggy in this regard anyway, since it doesn't make sense to always add the answer.

I didn't notice this because I didn't think Hibernate would call any of the setters during commit or cascade, so I didn't check the setters. I tried it again now with using the same column, and it works fine.

Sorry for bothering you, and thank you for your help.
Carl-Eric


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 7 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.