Grails "a different object with the same identifier value was already associated with the session" error

9.2k views Asked by At

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)
        }
    }
2

There are 2 answers

2
schmolly159 On BEST ANSWER

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)
}
9
GreyBeardedGeek On

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.