-->
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.  [ 13 posts ] 
Author Message
 Post subject: Association mapping results in duplicate objects.
PostPosted: Wed Jul 25, 2007 5:20 am 
Newbie

Joined: Wed Jul 11, 2007 6:06 am
Posts: 15
Hibernate version: 3.2.1

Name and version of the database you are using: HSQLDB-1.8.0.7

Mapping documents:
I have three classes: Event, EventInstance and NewsItem. An Event can have multiple EventInstances, however each EventInstance belongs to one Event. Each EventInstance also belongs to one NewsItem, and one NewsItem can have multiple EventInstances. Besides that each EventInstance has two String variables. Here are the mapping files:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
   "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
   "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="nl.semlab.viewerpro.client.core.event">

   <class name="DefaultEvent" table="event">

      <id name="id" type="long" unsaved-value="-1">
         <generator class="native" />
      </id>

      <property name="name" type="string" length="100" not-null="true" />
      <property name="description" type="string" />
      
      <!-- TODO: figure out why we have to use lazy="false" here -->
      <set name="eventActions" table="eventAction" lazy="false" cascade="all">
         <key column="eventId" />
         <one-to-many class="nl.semlab.viewerpro.client.core.eventaction.DefaultEventAction" />
      </set>
      
      <!-- TODO: figure out why we have to use lazy="false" here -->
      <set name="eventInstances" table="eventInstance" lazy="false" inverse="true" cascade="all-delete-orphan">
         <key column="eventId" />
         <one-to-many class="nl.semlab.viewerpro.client.core.eventinstance.DefaultEventInstance" />
      </set>

   </class>

</hibernate-mapping>

Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
   "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
   "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="nl.semlab.viewerpro.client.core.eventinstance">

   <class name="DefaultEventInstance" table="eventInstance">

      <id name="id" type="long" unsaved-value="-1">
         <generator class="native" />
      </id>

      <many-to-one name="event" column="eventId" class="nl.semlab.viewerpro.client.core.event.DefaultEvent" lazy="false" />
      <many-to-one name="newsItem" column="newsItemId" class="nl.semlab.viewerpro.client.core.news.DefaultNewsItem" lazy="false" />

      <property name="plainSelectedText" type="string" />
      <property name="annotatedSelectedText" type="string" />

   </class>

</hibernate-mapping>

Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
   "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
   "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="nl.semlab.viewerpro.client.core.news">

   <class name="DefaultNewsItem" table="newsitem">

      <id name="id" type="long" unsaved-value="-1">
         <generator class="native" />
      </id>

      <property name="GUID" type="string" length="100" />
      <property name="URI" type="string" />
      <property name="published" />
      <property name="title" not-null="true" />
      <property name="body" column="fulltekst" type="text"
         not-null="false" />
      <property name="annotatedTitle" not-null="true" />
      <property name="annotatedBody" not-null="false" />

      <many-to-one name="source" class="nl.semlab.viewerpro.client.core.source.DefaultSource" column="sourceid" unique="false" lazy="false" not-null="true" />

      <!-- TODO: figure out why we have to use lazy="false" here -->
      <set name="eventInstances" table="eventInstance" lazy="false" inverse="true" cascade="all">
         <key column="newsItemId" />
         <one-to-many class="nl.semlab.viewerpro.client.core.eventinstance.DefaultEventInstance" />
      </set>

   </class>

</hibernate-mapping>


The problem is that this results in multiple Event objects in memory with the same ID. The SQL DB contains the proper rows, however when Hibernate loads everything to memory there are multiple Event objects being created with the same ID. I have a manager object which has a list of all Event objects loaded by Hibernate. It filled like this:
Code:
public List<Event> getEvents() throws DaoException
{
   List<Event> result = null;

   Session session = getSessionFactory().getCurrentSession();
   try
   {
      session.getTransaction().begin();
      Query query = session.createQuery("from DefaultEvent e");
      query.setTimeout(5);
      result = query.list();

   }
   catch (HibernateException he)
   {
      session.getTransaction().rollback();
      throw new DaoException("Unable to load events.", he);
   }
   finally
   {
      session.getTransaction().commit();
   }

   return result;
}


However, when I get an Event object from the EventInstance object and get the Event object with the same ID from the eventmanager Java tells me that they are different using the following code:
Code:
Event instanceEvent = eventInstance.getEvent();
Event managerEvent = null;
Event[] events = m_model.getEventManager().getEvents();
for (Event event : events)
{
   if (event.getId() == instanceEvent.getId())
   {
      logger.info("FOUND");
      managerEvent = event;
      break;
   }
}

if (instanceEvent == managerEvent)
{
   logger.info("SAME!");
}
else
{
   logger.info("DIFFERENT!");
   logger.info("instance event id: " + instanceEvent.getId());
   logger.info("manager event id: " + managerEvent.getId());
}


So two Event objects with the same ID are different. This leads to problems when, for example, removing an EventInstance for the EventInstance set of an Event object.

I really do hope somebody around here can clear this up for me, because at this point I am completely stuck with the association mapping between these objects.

--
Best regards,
Jethro Borsje


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jul 25, 2007 7:02 am 
Regular
Regular

Joined: Fri May 12, 2006 4:05 am
Posts: 106
Hibernate guarantees uniqueness of objects with a given id within a Session. Since your code uses a new Session for each db-access, this won't help in your case.
I'd recommend you use longer Sessions anyway, since your solution won't profit from hibernates 1st-Level-cache.


Top
 Profile  
 
 Post subject: RE: Association mapping results in duplicate objects
PostPosted: Wed Jul 25, 2007 7:14 am 
Newbie

Joined: Sat Oct 28, 2006 6:16 am
Posts: 17
The thing to realize with hibernate is that there is only a one to one mapping between domain objects and java objects when all java objects are loaded from the same session. If you want to work with java objects loaded from different sessions then you need to think of java objects as representations of domain objects.

To grasp this consider the relationship between java objects and dates. In java, dates are represented by instances of the Date class. There is not a one to one correspondence between dates and instances of Date. For example to represent the date January 1, 1970, 00:00:00 GMT in java you can use the following code:

Code:
Date date = new Date(0);


This creates a representation of the date January 1, 1970, 00:00:00 GMT. Now if you execute the following code:

Code:
Date date1 = new Date(0);
Date date2 = new Date(0);
System.out.println(date1 == date2);


it will print out false. Why? - because, even though date1 and date2 are identical dates, each points to a different representation of the same date.

Hopefully the example with Dates is obvious. So how does this relate to your problem?

In the code you submitted:

Code:
Event instanceEvent = eventInstance.getEvent();
Event managerEvent = null;
Event[] events = m_model.getEventManager().getEvents();
for (Event event : events)
{
   if (event.getId() == instanceEvent.getId())
   {
      logger.info("FOUND");
      managerEvent = event;
      break;
   }
}


instanceEvent and managerEvent each points to different representations of the same domain object. This happens because you loaded these objects in different hibernate sessions.

If you want to work with objects loaded in different hibernate sessions then you must implement equals and hashCode correctly. See http://www.hibernate.org/hib_docs/v3/reference/en/html_single/#persistent-classes-equalshashcode for details. This should hopefully solve the problems you are getting with removing objects from a Set.

I also recommend reading http://www.hibernate.org/109.html[/code]


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jul 25, 2007 7:15 am 
Newbie

Joined: Wed Jul 11, 2007 6:06 am
Posts: 15
piet.t wrote:
Hibernate guarantees uniqueness of objects with a given id within a Session. Since your code uses a new Session for each db-access, this won't help in your case.
I'd recommend you use longer Sessions anyway, since your solution won't profit from hibernates 1st-Level-cache.

I am not really sure how I use a new session every time. By using the "getCurrentSession()" method I do not necessarily create a new session, do I?


Top
 Profile  
 
 Post subject: Re: RE: Association mapping results in duplicate objects
PostPosted: Wed Jul 25, 2007 7:41 am 
Newbie

Joined: Wed Jul 11, 2007 6:06 am
Posts: 15
simon_t wrote:
The thing to realize with hibernate is that there is only a one to one mapping between domain objects and java objects when all java objects are loaded from the same session. If you want to work with java objects loaded from different sessions then you need to think of java objects as representations of domain objects.

Code:
Date date = new Date(0);


This creates a representation of the date January 1, 1970, 00:00:00 GMT. Now if you execute the following code:

Code:
Date date1 = new Date(0);
Date date2 = new Date(0);
System.out.println(date1 == date2);


it will print out false. Why? - because, even though date1 and date2 are identical dates,
each points to a different representation of the same date.

Hopefully the example with Dates is obvious. So how does this relate to your problem?

I understand the example, it exactly demonstrates what I do not want. I want to have the same Java object for the same database row.

simon_t wrote:
In the code you submitted:

Code:
Event instanceEvent = eventInstance.getEvent();
Event managerEvent = null;
Event[] events = m_model.getEventManager().getEvents();
for (Event event : events)
{
   if (event.getId() == instanceEvent.getId())
   {
      logger.info("FOUND");
      managerEvent = event;
      break;
   }
}


instanceEvent and managerEvent each points to different representations of the same domain object. This happens because you loaded these objects in different hibernate sessions.

This is what I do not really understand. How / where did I create a new sessions?

simon_t wrote:
If you want to work with objects loaded in different hibernate sessions then you must implement equals and hashCode correctly. See http://www.hibernate.org/hib_docs/v3/reference/en/html_single/#persistent-classes-equalshashcode for details. This should hopefully solve the problems you are getting with removing objects from a Set.

I also recommend reading http://www.hibernate.org/109.html[/code]

I have implemented the equals() and hashcode() methods and as far as I can see they work properly. The following code:
Code:
Event instanceEvent = eventInstance.getEvent();
Event managerEvent = null;
Event[] events = m_model.getEventManager().getEvents();
for (Event event : events)
{
   if (event.getId() == instanceEvent.getId())
   {
      logger.info("FOUND");
      managerEvent = event;
      break;
   }
}

logger.info("hashcode instance event : " + instanceEvent.hashCode());
logger.info("hashcode manager event  : " + managerEvent.hashCode());

if (instanceEvent == managerEvent)
{
   logger.info("SAME!");
}
else if (instanceEvent.equals(managerEvent))
{
   logger.info("EQUAL");
}
else
{
   logger.info("DIFFERENT!");
   logger.info("instance event id: " + instanceEvent.getId());
   logger.info("manager event id: " + managerEvent.getId());
}

Results in this:
Quote:
INFO [2007-07-25 13:39:22,382] RemoveEventInstanceCommand.execute(83) | FOUND
INFO [2007-07-25 13:39:22,383] RemoveEventInstanceCommand.execute(89) | hashcode instance event : -1053211646
INFO [2007-07-25 13:39:22,383] RemoveEventInstanceCommand.execute(90) | hashcode manager event : -1053211646
INFO [2007-07-25 13:39:22,383] RemoveEventInstanceCommand.execute(98) | EQUAL


So the two different EventInstance objects have the same hashcode and are equal!

--
Best regards,
Jethro Borsje


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jul 25, 2007 10:32 am 
Newbie

Joined: Wed Jul 11, 2007 6:06 am
Posts: 15
Hi everybody,

I played around with it a bit more and figured out the problem is that somehow Hibernate does not properly uses my hashcode() and equals() methods. If I use those methods Java tells me my objects are the same, yet Hibernate creates them separately instead of returning the already created object.

--
Best regards,
Jethro Borsje


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jul 25, 2007 10:33 am 
Newbie

Joined: Wed Jul 11, 2007 6:06 am
Posts: 15
Hi everybody,

I played around with it a bit more and figured out the problem is that somehow Hibernate does not properly uses my hashcode() and equals() methods. If I use those methods Java tells me my objects are the same, yet Hibernate creates them separately instead of returning the already created object.

--
Best regards,
Jethro Borsje


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 26, 2007 2:41 am 
Newbie

Joined: Wed Jul 11, 2007 6:06 am
Posts: 15
I am sorry for double posting, is there a way to remove a post?


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jul 26, 2007 4:50 am 
Newbie

Joined: Wed Jul 11, 2007 6:06 am
Posts: 15
After more checking I found the problem of duplicate objects is happening everywhere in my application, not just in the aforementioned objects. This happens despite having implemented correct equals() and hashCode() methods everywhere.

Can this have something to do with the fact that I am not using a cache? This what my configuration looks like:
Code:
Configuration config = new Configuration()
   .setProperty("hibernate.connection.driver_class", driver)
   .setProperty("hibernate.dialect", dialect)
   .setProperty("hibernate.connection.password", password)
   .setProperty("hibernate.connection.url", connectionString)
   .setProperty("hibernate.connection.username", userName)
   .setProperty("hibernate.connection.provider_class", "org.hibernate.connection.C3P0ConnectionProvider")
   .setProperty("hibernate.c3p0.min_size", "1")
   .setProperty("hibernate.c3p0.max_size", "1")
   .setProperty("hibernate.c3p0.timeout", "1800")
   .setProperty("hibernate.c3p0.max_statements", "50")
   .setProperty("hibernate.current_session_context_class", "thread")
   .setProperty("hibernate.show_sql", "false")
   
   // next two properties are specific for the hsqldb... TODO fix
   .setProperty("hibernate.hbm2ddl.auto", "update")
   .setProperty("cache.provider_class","org.hibernate.cache.NoCacheProvider")
   .addClass(DefaultNewsItem.class)
   .addClass(DefaultEquity.class)
   .addClass(DefaultImpact.class)
   .addClass(DefaultSource.class)
   .addClass(DefaultRule.class)
   .addClass(DefaultAppliedRule.class)
   .addClass(DefaultEvent.class)
   .addClass(DefaultAction.class)
   .addClass(DefaultEventAction.class)
   .addClass(DefaultEventInstance.class);


Or is there some other setting that I need to set to let hibernate use the equals() and hashCode() methods properly?

--
Best regards,
Jethro Borsje


Top
 Profile  
 
 Post subject:
PostPosted: Sun Jul 29, 2007 4:56 am 
Newbie

Joined: Sat Oct 28, 2006 6:16 am
Posts: 17
jethro wrote:
piet.t wrote:
Hibernate guarantees uniqueness of objects with a given id within a Session. Since your code uses a new Session for each db-access, this won't help in your case.
I'd recommend you use longer Sessions anyway, since your solution won't profit from hibernates 1st-Level-cache.

I am not really sure how I use a new session every time. By using the "getCurrentSession()" method I do not necessarily create a new session, do I?


http://www.hibernate.org/hib_docs/v3/reference/en/html_single/#tutorial-firstapp-workingpersistence says:
Quote:
A Session begins when it is first needed, when the first call to getCurrentSession() is made. It is then bound by Hibernate to the current thread. When the transaction ends, either through commit or rollback, Hibernate automatically unbinds the Session from the thread and closes it for you. If you call getCurrentSession() again, you get a new Session and can start a new unit of work. This thread-bound programming model is the most popular way of using Hibernate, as it allows flexible layering of your code (transaction demarcation code can be separated from data access code, we'll do this later in this tutorial).


Top
 Profile  
 
 Post subject:
PostPosted: Sun Jul 29, 2007 5:01 am 
Newbie

Joined: Sat Oct 28, 2006 6:16 am
Posts: 17
Hi Jethro

You said:

Quote:
After more checking I found the problem of duplicate objects is happening everywhere in my application, not just in the aforementioned objects. This happens despite having implemented correct equals() and hashCode() methods everywhere.


Implementing equals() and hashCode() will not rid your application of duplicate objects. All implementing equals() and hashCode() will do is enable you to work with objects loaded from different hibernate sessions.

You said on Wed Jul 25, 2007 at 12:41 pm:

Quote:
This is what I do not really understand. How / where did I create a new sessions?


It is difficult to make my point clear without knowing how you create the hibernate sessions. Instead please consider the following code:

Code:
Session firstSession = createSession();
firstSession.beginTransaction();

Event eventFromFirstSession = (Event) firstSession.load(Event.class, 1);

firstSession.getTransaction().commit();

Session secondSession = createSession();
secondSession.beginTransaction();

Event eventFromSecondSession = (Event) secondSession.load(Event.class, 1);

secondSession.getTransaction()..commit();

if (eventFromFirstSession == eventFromSecondSession) {
  System.out.println("duplicate java objects created");
}


The above code will print out "duplicate java objects created". Of course the example needs some other supporting code but I hope it serves to help explain an important point. domain objects loaded in different sessions in this way will result in duplicate java objects.

You also said on Wed Jul 25, 2007 at 12:41 pm:

Quote:
I understand the example, it exactly demonstrates what I do not want. I want to have the same Java object for the same database row.


I think there are only two ways of avoiding duplicate objects.

1. Make sure your application never uses objects loaded from different sessions together

2. To re-attach the objects you want to use to the new session before loading other objects from the session by using Session.update() or Session.lock().

I word of warning. I have personally found avoiding duplicate objects very difficult to do.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jul 30, 2007 3:54 am 
Newbie

Joined: Wed Jul 11, 2007 6:06 am
Posts: 15
simon_t wrote:
Quote:
After more checking I found the problem of duplicate objects is happening everywhere in my application, not just in the aforementioned objects. This happens despite having implemented correct equals() and hashCode() methods everywhere.

Implementing equals() and hashCode() will not rid your application of duplicate objects. All implementing equals() and hashCode() will do is enable you to work with objects loaded from different hibernate sessions.

Allright, that's clear. Thank you for clearing it up.


simon_t wrote:
Quote:
This is what I do not really understand. How / where did I create a new sessions?

It is difficult to make my point clear without knowing how you create the hibernate sessions.

Here is my code which loads objects:
Code:
public List<NewsItem> getNewsItems() throws DaoException
{
   List<NewsItem> result = null;

   Session session = getSessionFactory().getCurrentSession();
   try
   {
      session.getTransaction().begin();
      Query query = session.createQuery("from DefaultNewsItem as newsitem order by newsitem.published");
      query.setTimeout(5);
      result = query.list();
   }
   catch (HibernateException he)
   {
      session.getTransaction().rollback();
      throw new DaoException("Unable to load newsitems.", he);
   }
   finally
   {
      session.getTransaction().commit();
   }

   return result;
}

Both the events and the newsitems are loaded this way.

simon_t wrote:
Instead please consider the following code:
Code:
Session firstSession = createSession();
firstSession.beginTransaction();

Event eventFromFirstSession = (Event) firstSession.load(Event.class, 1);

firstSession.getTransaction().commit();

Session secondSession = createSession();
secondSession.beginTransaction();

Event eventFromSecondSession = (Event) secondSession.load(Event.class, 1);

secondSession.getTransaction()..commit();

if (eventFromFirstSession == eventFromSecondSession) {
  System.out.println("duplicate java objects created");
}


The above code will print out "duplicate java objects created". Of course the example needs some other supporting code but I hope it serves to help explain an important point. domain objects loaded in different sessions in this way will result in duplicate java objects.

Allright, now I am a bid confused, because if "eventFromFirstSession == eventFromSecondSession" is true, doesn't this mean that the two objects are exactly the same (the same in memory), so this means that they are not duplicate...

simon_t wrote:
You also said on Wed Jul 25, 2007 at 12:41 pm:

Quote:
I understand the example, it exactly demonstrates what I do not want. I want to have the same Java object for the same database row.


I think there are only two ways of avoiding duplicate objects.

1. Make sure your application never uses objects loaded from different sessions together

2. To re-attach the objects you want to use to the new session before loading other objects from the session by using Session.update() or Session.lock().

Ad 1: in my previous code example: how did I close the session? E.g., how can I load everything in one session, doesn't getCurrentSession() return the same sessions each time?

Ad 2: If I chose this approach I should either use session.update() or session.lock() to add my already loaded objects to the new session?

Thank you very much for all you help, I think we are close to finding a solution.

--
Best regards,
Jethro


Top
 Profile  
 
 Post subject:
PostPosted: Wed Aug 01, 2007 4:22 am 
Newbie

Joined: Wed Jul 11, 2007 6:06 am
Posts: 15
Putting all the load code between
Code:
Session session = configuration.getSessionFactory().getCurrentSession();
Transaction transaction = session.beginTransaction();

and
Code:
transaction.commit();

Seems to do the trick.

Thanks for the help!


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