-->
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: 2 entities returned by a query while only one in DB
PostPosted: Mon Feb 21, 2011 4:25 am 
Newbie

Joined: Fri Feb 18, 2011 2:19 pm
Posts: 3
Hi,

I've experienced some weird behavior with Hibernate recently and I haven't foudn anything which explains it yet.

I have a simple piece of project with 2 classes : Speaker and Talk. A speaker can give multiple talks and a talk can only be given by one speaker.
Here is my domain :
Image

I decided to implement it with a bidirectional relationship, with a Set in my Spealer class.
Code:
public class Speaker extends BaseEntity {
             ...
   @OneToMany(mappedBy = "speaker", fetch = FetchType.EAGER)
   private Set<Talk> talks;
             ...
}


And my Talk which simply holds a reference to one Speaker.
Code:
public class Talk extends BaseEntity {
             ...
   @ManyToOne(cascade = CascadeType.ALL)
   @JoinColumn(name = "SPEAKER_FK", nullable=false)
   private Speaker speaker;
             ...
}


Here is my DAO implementation for the Speaker.
Code:
@Repository
@Transactional
public class SpeakerDaoImpl extends GenericDaoImpl<Speaker, Long> implements SpeakerDao {

   @Autowired
   private SessionFactory sessionFactory;

   public void setSessionFactory(SessionFactory s) {
      this.sessionFactory = s;
   }

   protected SessionFactory getSessionFactory() {
      if (sessionFactory == null) {
         throw new IllegalStateException(
               "sessionFactory has not been set on DAO before usage");
      }
      return sessionFactory;
   }

   protected Session getSession() {
      return getSessionFactory().getCurrentSession();
   }

   @SuppressWarnings("unchecked")
   public List<Speaker> findByLastName(String lastName) {
      Criteria criteria = getSession().createCriteria(getPersistentClass());
      criteria.add(Restrictions.like("lastName", lastName));
      return criteria.list();
   }

   @Transactional
   public Speaker makePersistent(Speaker speaker) {
      getSession().saveOrUpdate(speaker);
      return speaker;
   }
}


I then wrote a short unit test case to try out what I was doing.
Code:
package com.mycompany.myproject;

import java.util.List;

import org.joda.time.LocalDateTime;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.mycompany.myproject.model.Speaker;
import com.mycompany.myproject.model.Talk;

public class AdapterTest {

   protected ApplicationContext applicationContext;

   private static final Logger LOGGER = LoggerFactory
         .getLogger(AdapterTest.class);

   protected SpeakerDao speakerDao;

   protected TalkDao talkDao;

   @Before
   public void setUp() {
      applicationContext = new ClassPathXmlApplicationContext(new String[] {
            "classpath*:adapter-test-context.xml",
            "classpath*:datasource-test-context.xml" });
      speakerDao = (SpeakerDao) applicationContext.getBean("speakerDao");
      talkDao = (TalkDao) applicationContext.getBean("talkDao");
   }

   @After
   public void tearDown() {
      applicationContext = null;
      speakerDao = null;
      talkDao = null;
   }

   @Test
   public void testDataWritingAndReading() {

      // Creates one speaker
      Speaker newSpeaker = new Speaker();
      newSpeaker.setFirstName("John");
      newSpeaker.setLastName("Smith");
      newSpeaker.setEmail("john.smith@mycompany.com");
      newSpeaker.setOrganization("Mycompany");
      newSpeaker.setBirthDate(new LocalDateTime(1979, 1, 1, 0, 0));
      newSpeaker.setAge(22);
      newSpeaker.setGender("M");
      Speaker createdSpeaker = speakerDao.makePersistent(newSpeaker);
      
      // Creates a first talk, assign it to the speaker
      Talk talk1 = new Talk();
      talk1.setTitle("One title");
      talk1.setDescription("About this one title");
      talk1.setSpeaker(createdSpeaker);
      talkDao.makePersistent(talk1);

      //Create second talk with speaker3
      Talk talk2 = new Talk();
      talk2.setTitle("Another title");
      talk2.setDescription("About this other title");
      talk2.setSpeaker(createdSpeaker);
      talkDao.makePersistent(talk2);
      
      List<Speaker> speakersFound = speakerDao.findAll();
      LOGGER.debug("Total number of speakers : " + speakersFound.size());
      
      for(Speaker s : speakersFound)
         LOGGER.debug(s.toString());
   }
}


And this is giving a pretty strange output, instead of giving me 1 speaker, it prints out 2 speakers, both having the same id.
I tried to play around with the way I annotated the fields but nothing changes.
I know I could implement it as unidirectional mapping and removing the Set from my Speaker class but that would just be a workaround as I guess this behavior is not the expected one.

Do you see anything which explains these results ?

For those who might want to check out the complete code : http://dl.dropbox.com/u/19788800/adapter.zip

Many thanks in advance for your help !


Top
 Profile  
 
 Post subject: Re: 2 entities returned by a query while only one in DB
PostPosted: Mon Feb 21, 2011 9:18 am 
Expert
Expert

Joined: Tue Jun 16, 2009 3:36 am
Posts: 990
Your implementation of Speaker method

public int hashCode() {

is implemented wrong.
The hashcode must be something immutable for the livetime of the object.
In your implementation the calculated hashcode is changing with the content of the object itself.


Top
 Profile  
 
 Post subject: Re: 2 entities returned by a query while only one in DB
PostPosted: Mon Feb 21, 2011 10:32 am 
Newbie

Joined: Fri Feb 18, 2011 2:19 pm
Posts: 3
Thanks for your feedback but I'm not sure to understand your point.

From my understanding, the value of the hashcode had to change based on some sort of natural key as explained in the documentation : http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/persistent-classes.html#persistent-classes-equalshashcode

Here is how it looks now for the Speaker :
Code:
public class Speaker extends BaseEntity {
   
   ...

   @Override
   public int hashCode() {
      final int prime = 31;
      int result = 0;
      result = prime * result + ((age == null) ? 0 : age.hashCode());
      result = prime * result
            + ((birthDate == null) ? 0 : birthDate.hashCode());
      result = prime * result + ((email == null) ? 0 : email.hashCode());
      result = prime * result
            + ((firstName == null) ? 0 : firstName.hashCode());
      result = prime * result + ((gender == null) ? 0 : gender.hashCode());
      result = prime * result
            + ((lastName == null) ? 0 : lastName.hashCode());
      result = prime * result
            + ((organization == null) ? 0 : organization.hashCode());
      return result;
   }

   @Override
   public boolean equals(Object obj) {
      if (this == obj) {
         return true;
      }
      if (!(obj instanceof Speaker)) {
         return false;
      }
      Speaker other = (Speaker) obj;
      if (age == null) {
         if (other.age != null) {
            return false;
         }
      } else if (!age.equals(other.age)) {
         return false;
      }
      if (birthDate == null) {
         if (other.birthDate != null) {
            return false;
         }
      } else if (!birthDate.equals(other.birthDate)) {
         return false;
      }
      if (email == null) {
         if (other.email != null) {
            return false;
         }
      } else if (!email.equals(other.email)) {
         return false;
      }
      if (firstName == null) {
         if (other.firstName != null) {
            return false;
         }
      } else if (!firstName.equals(other.firstName)) {
         return false;
      }
      if (gender == null) {
         if (other.gender != null) {
            return false;
         }
      } else if (!gender.equals(other.gender)) {
         return false;
      }
      if (lastName == null) {
         if (other.lastName != null) {
            return false;
         }
      } else if (!lastName.equals(other.lastName)) {
         return false;
      }
      if (organization == null) {
         if (other.organization != null) {
            return false;
         }
      } else if (!organization.equals(other.organization)) {
         return false;
      }
      return true;
   }

   ...

}


Am I misunderstanding ?


Top
 Profile  
 
 Post subject: Re: 2 entities returned by a query while only one in DB
PostPosted: Mon Feb 21, 2011 11:44 am 
Expert
Expert

Joined: Tue Jun 16, 2009 3:36 am
Posts: 990
Quote:
It is recommended that you implement equals() and hashCode() using Business key equality. Business key equality means that the equals() method compares only the properties that form the business key. It is a key that would identify our instance in the real world. (a natural candidate key).


I just meant that simple taking all attributes as natural key is not a good approach.
For example age is not a good candidate for a natural key, as it is definitively not a immutable property
(usually we humans get older from year to year, so the age will change ;-) )

Anyway in your test-case it seems that you are not changing the speakers attributes ,
so probably the problem of the 2 entities stays in some other part.

I suggest you to log the jdbc activity in order to see if it is effectively the query result returning 2 times the same entity,
or if it is hibernate internally which duplicates the speaker instance for some reason whatever.
To log all jdbc activity I suggest to use p6spy.

best regards
G.D.


Top
 Profile  
 
 Post subject: Re: 2 entities returned by a query while only one in DB
PostPosted: Tue Feb 22, 2011 7:53 am 
Newbie

Joined: Fri Feb 18, 2011 2:19 pm
Posts: 3
Indeed, that helped understand how it gets 2 elements in the results, the statement executed is the following :

Code:
select
        *
    from
        CONF_SPEAKER this_ left outer join CONF_TALK talks2_
            on this_.ID = talks2_.SPEAKER_FK;


I guess that it's well the expected behavior of Hibernate and that I had wrong expectations.

So the solution in my case study would be :
    Using a unidirectional relationship and removing the Set
    Using a lazy fetch type

Anyway, thanks for your help.


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.