Monday, November 29, 2010

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

People working with Hibernate might be familiar with the below error.


org.springframework.orm.hibernate3.HibernateSystemException:
a different object with the same identifier value was already
associated with the session:
nested exception is org.hibernate.NonUniqueObjectException:
a different object with the same identifier value was already
associated with the session


This exception is generated when we try to load an instance object in session which has been already loaded by hibernate.
Easier said than done.
Hibernate considers two persistent entities identical if the two rows of the table can be said identical.

Above error could be generated in many ways and there may be various solutions on net like using session merge etc to get away with the problem.

I will discuss here the reason for me getting this problem and the way I resolved it.

Below is the code which generated the exception for me.

Following is the method which caused the exception

@Transactional(propagation=Propagation.REQUIRED)
public void registerStats(ImageCreativesStat imageCreativeStat){
List lst = getIcsByDimainAndCreative(imageCreativeStat);
if(lst.size() == 1){
imageCreativeStat.setId(lst.get(0).getId());
imageCreativeStat.setImp_delivered(lst.get(0).getImp_delivered() + imageCreativeStat.getImp_delivered());
imageCreativeStat.setClick_delivered(lst.get(0).getClick_delivered() + imageCreativeStat.getClick_delivered());
}
getHibernateTemplate().saveOrUpdate(imageCreativeStat);
}


The following code was calling the above method.


@Test
public final void testRegisterStats() {
ImageCreativesStat ics = new ImageCreativesStat();
ics.setCreative_id(123);
ics.setDomain_id(456);
Assert.assertTrue("PASSED : When no criteria specified ", icsDAO.getIcsByDimainAndCreative(ics).size() == 0);
ics.setImp_delivered(9);
ics.setClick_delivered(98);
icsDAO.registerStats(ics);
Assert.assertEquals("PASSED One record in the DB", 1, icsDAO.getIcsByDimainAndCreative(ics).size());

ics.setImp_delivered(10);
ics.setClick_delivered(11);
icsDAO.registerStats(ics);

Assert.assertEquals("PASSED One record in the DB", 1, icsDAO.getIcsByDimainAndCreative(ics).size());
ics = icsDAO.getIcsByDimainAndCreative(ics).get(0);
Assert.assertEquals(19, ics.getImp_delivered());
Assert.assertEquals(109, ics.getClick_delivered());
}


If you see in the test case above, the object "ImageCreativesStat" is created and passed to method "registerStats".
"ImageCreativesStat" object is not loaded through hibernate, so its a disconnected object for hibernate.

Therefore when we execute the below line
List lst = getIcsByDimainAndCreative(imageCreativeStat);


It loads a connected dataset. Hence when we execute the line
getHibernateTemplate().saveOrUpdate(imageCreativeStat);

it gives us an exception because the hibernate already holds an identical object in the session.

Changing the method "registerStats" to the following resolved the issue.


@Transactional(propagation=Propagation.REQUIRED)
public void registerStats(ImageCreativesStat imageCreativeStat){
ImageCreativesStat ics;
List lst = getIcsByDomainAndCreative(imageCreativeStat);
if(lst.size() == 1){
ics = lst.get(0);
ics.setImp_delivered(ics.getImp_delivered() + imageCreativeStat.getImp_delivered());
ics.setClick_delivered(ics.getClick_delivered() + imageCreativeStat.getClick_delivered());
getHibernateTemplate().update(ics);
}else{
getHibernateTemplate().save(imageCreativeStat);
}
}


I Hope this helps.
Please note, this is one of the causes and not the only cause for this exception.

7 comments:

  1. Thanks a lot for the post, I hit this exact error on an application in production and this fix took care of it.

    ReplyDelete
    Replies
    1. I am glad that it helped you and thanks for taking time to post the comment

      Delete