5

Possible Duplicate:
Hibernate: different object with the same identifier value was already associated with the session

I have the following code in my controller in Grails that is failing with "a different object with the same identifier value was already associated with the session" error message. I have already visited few pages where it says that I must call "merge" before calling save which ends up with this error Provided id of the wrong type for class com.easytha.QuizTag. Expected: class java.lang.Long, got class org.hibernate.action.DelayedPostInsertIdentifier

Someone has suggested that grails searchable plugin might be causing this and I should remove searchable = true form my domain class which is not an option (refer to the previous post here grails searcheable plugin search in inner hasMany class)

One thing to point is that error is not thrown at the time of calling q.save() rather it's thrown while calling redirect redirect(action:"show",id:id)!!

Any suggestions?

def addTags(String tags,Long id){
        if(tags){
            String[] strTags = tags.split(",");
            Quiz q = Quiz.get(id)           
            for(String t in strTags){
                Tag tagToAdd = Tag.findByTag(t)

                if(!tagToAdd){
                    tagToAdd = new Tag(tag:t)
                    tagToAdd.save()
                }

                println "---> "+tagToAdd +" Quiz"+q?.id
                def qt = QuizTag.findByQuizAndTag(q,tagToAdd)
                if(!qt){
                    qt = new QuizTag(quiz:q,tag:tagToAdd);
                    q.addToTags(qt)
                }

            }           
            q.save()        
            redirect(action:"show",id:id)
        }
    }

-----------EDIT---------------

Final code that worked with searchable plugin
        def addTags(String tags,Long id){
        if(tags){
            String[] strTags = tags.split(",");
            Quiz q = Quiz.get(id)           
            for(String t in strTags){
                if (q.tags.any { QuizTag qt -> qt.tag.tag == t }) { continue; }
                    Tag tagToAdd = Tag.findOrSaveByTag(t);
                    QuizTag qt = new QuizTag(quiz:q,tag:tagToAdd)
                    q.addToTags(qt)
                }           
            q.save(flush:true)      
            redirect(action:"show",id:id)
        }
    }
Community
  • 1
  • 1
Sap
  • 5,197
  • 8
  • 59
  • 101
  • 1
    From the description of the save() method: "The object will not be persisted immediately unless the flush argument is used." That's why the error only occurs when the request finishes. – Tiago Farias Oct 09 '12 at 22:14
  • @TiagoFarias you are right. after calling q.save(flush:true) I get the error right at that line, other thing to note here is that even after the error my data is still being saved! Also this error only occurs if the tag already existed meaning "Tag.findByTag(t)" returns something – Sap Oct 09 '12 at 22:24
  • I can't post an answer, but here you go: Just check `q.hasChanged()` before saving. – EzPizza Jan 12 '22 at 13:29

2 Answers2

4

Since you do Quiz.get(id), you have a 'connected' instance, so you definitely do not need to 'merge' - that's only to re-connect a disconnected instance.

I think that the reason that you are getting the error is that the line:

def qt = QuizTag.findByQuizAndTag(q, tagToAdd)

pulls the QuizTag instance into the session, but Tag tagToAdd = Tag.findByTag(t) has already associated the instance with the session, and you are assigning to another variable. You now have two instances associated with the session, both representing the same row in the database, qt and tagToAdd (they have the same id). This produces an ambiguous situation, and is not allowed, hence the error.

It appears that you don't actually need qt to be instantiated (you only take action if it doesn't exist). So, I'd suggest doing a query to find out if it exists (perhaps a 'select count') instead of actually retrieving the object instance. That way, you'll only have one copy of the object.

Sébastien Le Callonnec
  • 26,254
  • 8
  • 67
  • 80
GreyBeardedGeek
  • 29,460
  • 2
  • 47
  • 67
  • As per your suggestion shouldn't the new code in "EDIT" should work? – Sap Oct 10 '12 at 02:34
  • I'm sorry, I don't understand your question. What is "the new code in EDIT" ? – GreyBeardedGeek Oct 10 '12 at 03:05
  • :) I mean the edited area in the original post. Where I changed variable assignment to Tag tagToAdd = Tag.findByTag(t)?:new Tag(tag:t) and def qt = QuizTag.findByQuizAndTag(q,tagToAdd)?:new QuizTag(quiz:q,tag:tagToAdd); Don't you think that could have worked! – Sap Oct 10 '12 at 12:43
  • No, I don't think so. You need to arrange things so that you don't instantiate the same object twice in the same session. – GreyBeardedGeek Oct 10 '12 at 23:57
  • Hey I updated my code once again in the ---EDIT--- in my original post and I am still getting the same error!! Any hints? – Sap Oct 11 '12 at 03:52
  • It looks to me like you have your logic backwards - you're creating objects if their count is greater than 0, and looking them up if their count is equal to 0? – GreyBeardedGeek Oct 11 '12 at 04:35
  • Thanks a lot!! This is what happens when one codes at 1:00AM :) Anyways I made the correct change and now I get "NULL not allowed for column "QUIZ_ID"; SQL statement: insert into quiz_tag (id, version, date_created, last_updated, tag_id, quiz_id, tags_idx) values (null, ?, ?, ?, ?, ?, ?) [23502-164]" !!!!! It seems like a such a simple thing and I have already spend two days on this one :( – Sap Oct 11 '12 at 05:05
  • Can you check my edit once more? at this point of time I have stopped using my analytical abilities... I am once again getting the same a different object with the same identifier value was already associated with the session: [com.easytha.QuizTag#8] – Sap Oct 11 '12 at 05:09
  • The current version looks ok to me - at this point, you're going to have to use the debugger to figure out where the extra instance is coming from. – GreyBeardedGeek Oct 12 '12 at 01:48
0

Grails by default lazy loads collections with Hibernate proxies. So maybe duplicate QuizTag proxies (or proxies and inflated objects) are being created, which is causing your issue.

You could try something like:

Quiz q = Quiz.get(id)         
for(String t in strTags){
    // Check if the tag is already joined to this quiz and
    // skip a dynamic finder load later
    if (q.tags.any { QuizTag qt -> qt.tag.tag == t }) { continue; }
    // Find or create-save the tag to join
    Tag tagToAdd = Tag.findOrSaveByTag(t);
    QuizTag qt = new QuizTag(quiz:q,tag:tagToAdd)
    qt.save()
    q.addToTags(qt)
}
schmolly159
  • 3,881
  • 17
  • 26
  • Well, my code above worked but now it is not indexing the tags and quiz!! so no search results:) – Sap Oct 13 '12 at 05:33
  • The behavior seems so random, it started working just a moment ago but now the same error when calling q.save(flush:true) – Sap Oct 13 '12 at 08:03