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.  [ 5 posts ] 
Author Message
 Post subject: Session.load / Session.get and NonUniqueObjectException
PostPosted: Fri Oct 19, 2007 4:30 am 
Newbie

Joined: Tue Oct 02, 2007 11:57 am
Posts: 5
Hi all,

I've been looking for solutions but cannot find any and it's making me crazy... After having debugged and debugged my app, I have come to the point that the problem can be resumed in this simple test case :

I have a simple class Activity which is mapped to a database table REP_ACTIVITIES (see mapping documents below). I try to load it twice by hibernate and compare the 2 generated instances :
Code:
      UID i = new UID(4);
      IActivity firstActivity = (IActivity)IdentifiableDAO.getInstance().load(Activity.class, i);
      IActivity secondActivity = (IActivity)IdentifiableDAO.getInstance().load(Activity.class, i);

      if(firstActivity!=secondActivity) {
         System.out.println("This is weird");
      }


Here is the implementation of my IdentifableDAO.load(Class,UID) method :
Code:
   public IdentifiedObject load(Class clazz, UID id) {
      log.debug("Loading class '" + clazz.getName() + "' for ID "+ id);
      Session session = hUtil.getSessionFactory().getCurrentSession();
      session.beginTransaction();
      //Trying to retrieve in-session object
      IdentifiedObject object = (IdentifiedObject)session.get(clazz, id.rawId());
      session.getTransaction().commit();
      return object;
   }


And here's my problem :
The 2 returned instances from the 2 consecutive calls to hibernate are different ! I was first calling Session.load(Class,long) to load my objects and first thought that converting it to a Session.get(Class,long) call will retrieve the instance of Activity in Session. But get has exactly the same behaviour since the object always exists in database...

After this kind of "double load", if i try some saveOrUpdate call on the second instance i will get a NonUniqueObjectException.

I am looking for a way to retrieve the same object instance when passing the same long id to a load operation (or get, or anything that can first check in the Session "cache"). I thought hibernate would do it! Maybe i have done something wrong but i cannot see what or find anything on forums.

It seems like hibernate knows that it has already loaded the same object (as it raises a NonUniqueObjectException on save) but still instantiate a new object for an id of an alreay loaded object.

I've got a workaround in my IdentifableDAO.load method which is implementing a cache of loaded hibernate objects, but it seems to me weird! I would need something in hibernate that would do this for me :
Code:
   public IdentifiedObject load(Class clazz, UID id) {
      log.debug("Loading class '" + clazz.getName() + "' for ID "+ id);
Code:
      // Manually implementing a Session object cache (which exists in hibernate !)
      IdentifiedObject object = hibernateObjects.get(new MultiKey(clazz,id));
      // If not in my hibernate cache then
      if(object==null) {
Code:
         Session session = hUtil.getSessionFactory().getCurrentSession();
         session.beginTransaction();
         //Trying to retrieve in-session object
         object = (IdentifiedObject)session.get(clazz, id.rawId());
         session.getTransaction().commit();
Code:
         // Registering loaded object in cache
         hibernateObjects.put(new MultiKey(clazz,id), object);
      }
Code:
      return object;
   }


I'm sure hibernate provides a way to do this, i cannot imagine that 2 load operation on a same id cannot return the same instance. Could you please help me ?

I know you can ;-)

Thx.

Hibernate version:
3.2
Mapping documents:
Mapping file :
Code:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
   <class name="com.nextep.datadesigner.vcs.impl.Activity" table="REP_ACTIVITIES" lazy="false">
      <id name="id" column="ACTIVITY_ID">
         <generator class="increment"/>
      </id>
      <property name="name" column="ACTIVITY_NAME"/>
      <property name="description" column="DESCRIPTION"/>
   </class>
</hibernate-mapping>

Hibernate configuration :

Code between sessionFactory.openSession() and session.close():
Not much code :
Code:
      session.beginTransaction();
      //Trying to retrieve in-session object
      IdentifiedObject object = (IdentifiedObject)session.get(clazz, id.rawId());
      session.getTransaction().commit();

Full stack trace of any exception that occurs:
If for some reason i try to persist in a same Session an object grappe which references this 2 instances (my real app problem), i will have a NonUniqueObjectException :
Code:
Exception in thread "main" org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.nextep.datadesigner.vcs.impl.Activity#4]
   at org.hibernate.engine.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:590)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:223)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:89)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
   at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:507)
   at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:499)
   at org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:218)
   at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:268)
   at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:216)
   at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
   at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
   at org.hibernate.engine.Cascade.cascade(Cascade.java:97)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.cascadeOnUpdate(DefaultSaveOrUpdateEventListener.java:357)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:329)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:223)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:89)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
   at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:507)
   at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:499)
   at org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:218)
   at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:268)
   at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:216)
   at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
   at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
   at org.hibernate.engine.Cascade.cascade(Cascade.java:97)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.cascadeOnUpdate(DefaultSaveOrUpdateEventListener.java:357)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:329)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:223)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:89)
   at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
   at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:507)
   at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:499)
   at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:495)
   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:597)
   at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:301)
   at $Proxy0.saveOrUpdate(Unknown Source)
   at com.nextep.datadesigner.dao.IdentifiableDAO.save(IdentifiableDAO.java:87)

Name and version of the database you are using:
Oracle 10.2
The generated SQL (show_sql=true):
Hibernate: select activity0_.ACTIVITY_ID as ACTIVITY1_2_0_, activity0_.ACTIVITY_NAME as ACTIVITY2_2_0_, activity0_.DESCRIPTION as DESCRIPT3_2_0_ from REP_ACTIVITIES activity0_ where activity0_.ACTIVITY_ID=?
Hibernate: select activity0_.ACTIVITY_ID as ACTIVITY1_2_0_, activity0_.ACTIVITY_NAME as ACTIVITY2_2_0_, activity0_.DESCRIPTION as DESCRIPT3_2_0_ from REP_ACTIVITIES activity0_ where activity0_.ACTIVITY_ID=?

Debug level Hibernate log excerpt:


Problems with Session and transaction handling?

Read this: http://hibernate.org/42.html


Top
 Profile  
 
 Post subject:
PostPosted: Sat Oct 20, 2007 2:12 am 
Senior
Senior

Joined: Sat Aug 19, 2006 6:31 pm
Posts: 139
I think the commit and after each load clears hibernate session's transaction level cache. So the next load, will create a new object. If you want the object to remain in cache across commits (but still in the same session) you might want to look at the second level cache.

http://www.hibernate.org/hib_docs/v3/re ... ance-cache

_________________
Don't forget to rate the reply if it helps..:)

Budyanto


Top
 Profile  
 
 Post subject:
PostPosted: Mon Oct 22, 2007 3:45 am 
Newbie

Joined: Tue Oct 02, 2007 11:57 am
Posts: 5
Thanks for your reply.

Actually, i had already tried to use a second level cache and obtained the same results. However, I've made some more tests after reading your answer, and logged the results for you to see what happened.

1) I've activated the EhCacheProvider in the hibernate.cfg.xml file
Code:
<property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>


2) I've set a ehcache.xml up which contains the following :
Code:
<ehcache>
    <diskStore path="./resource"/>
    <defaultCache
        maxElementsInMemory="10000"
        eternal="true"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        diskPersistent="false"
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU"
        />
</ehcache>


3) I changed the mapping file of my activity class to add caching properties :
Code:
      <cache usage="read-write"/>


4) I defined the log4j parameters so that i can see debug traces.

5) I run the same test case. The debug trace of ehcache brings some useful information, but it seems inconsistent to me. Here are the traces :

After the first load call, i have the following traces :
Code:
***EHCache*** DEBUG 09:31:12,708 MemoryStore:138 - com.nextep.datadesigner.vcs.impl.ActivityCache: com.nextep.datadesigner.vcs.impl.ActivityMemoryStore miss for com.nextep.datadesigner.vcs.impl.Activity#4
***EHCache*** DEBUG 09:31:12,710 Cache:661 - com.nextep.datadesigner.vcs.impl.Activity cache - Miss
Hibernate: select activity0_.ACTIVITY_ID as ACTIVITY1_2_0_, activity0_.ACTIVITY_NAME as ACTIVITY2_2_0_, activity0_.DESCRIPTION as DESCRIPT3_2_0_ from REP_ACTIVITIES activity0_ where activity0_.ACTIVITY_ID=?
NEXTEP>>  INFO 09:31:12,730 Activity:59 - Activity ID=4 with name <Updated new_table >
***EHCache*** DEBUG 09:31:12,747 MemoryStore:138 - com.nextep.datadesigner.vcs.impl.ActivityCache: com.nextep.datadesigner.vcs.impl.ActivityMemoryStore miss for com.nextep.datadesigner.vcs.impl.Activity#4
***EHCache*** DEBUG 09:31:12,749 Cache:661 - com.nextep.datadesigner.vcs.impl.Activity cache - Miss

No surprise, we have a miss in the cache since this is our first load... Then, we have a surprising behaviour on the second load call. Again, here are the traces :
Code:
***EHCache*** DEBUG 09:33:20,754 MemoryStore:135 - com.nextep.datadesigner.vcs.impl.ActivityCache: com.nextep.datadesigner.vcs.impl.ActivityMemoryStore hit for com.nextep.datadesigner.vcs.impl.Activity#4

Yes we have a hit !!! Cool, the cache is working and seems to retrieve object among several different session transactions. This is what i need.
But: the bad news is, a new Activity object is still created! I cannot understand why. This means the object instance returned by the second load call is different from the one returned by the first load call.

Since the cache knows it exists, why a new object with same attributes is created by hibernate ? With my more complex concrete problem, it has the side effect to generate a NonUniqueObjectException just because hibernate creates a new object for the same id on successive loads.

I cannot believe this is a normal behaviour, and i'm sure there is a way to retrieve the previously loaded instance (the second level cache knows it since it has a hit!) from hibernate. But how?

I'm missing something...


Top
 Profile  
 
 Post subject:
PostPosted: Mon Oct 22, 2007 6:16 am 
Newbie

Joined: Tue Oct 02, 2007 11:57 am
Posts: 5
After some more investigations (and more understanding of the second level cache) :
Second level cache does not cache the object instances, but only the object attributes. Therefore, if an object has been found in the second level cache (a hit), then Hibernate re-instantiate it with values from the cache.

This explains why i'm having a "hit" AND a newly created Activity instance.

It seems like this cache is useful to reduce database transactions (in my test case i have indeed 1 less SQL-select statement, the one which retrieve the Activity), but does not help to retrieve an "already loaded" object instance.

I think the 2nd level cache will not help me for what i am trying to do.

Is there a way to make 2 successive load calls on the same id / same object but different sessions returning the same object instance ? This might be via the SessionFactory or something else which has the lifecycle of my whole application...

Or maybe there is a workaround pattern to do this and which avoids NonUniqueObjectExceptions ?

Thanks for help.


Top
 Profile  
 
 Post subject: solution
PostPosted: Tue Feb 05, 2008 7:04 pm 
Beginner
Beginner

Joined: Fri Oct 06, 2006 2:49 am
Posts: 25
Hi,

I do not know if it is the answer is still useful for you but probably for others.

I think your problem comes from the fact that a new session is opened for each call you make to your DAO method. If I am not mistaking you have configured your session to be available the duration of your transactions and your transaction is restricted to the DAO method. If it is not the case, I do not understand how this porblem occured... :(

For this reason each time you call the method a new instance is created because it is using a different Session.

Cheers,
Tiggy


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