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.  [ 6 posts ] 
Author Message
 Post subject: Object Identity
PostPosted: Wed Aug 20, 2008 2:29 pm 
Newbie

Joined: Mon Aug 11, 2008 2:59 pm
Posts: 11
Hibernate version:Hibernate Annotations 3.3.1.GA, Hibernate 3.2.6, Hibernate EntityManager 3.3.2.GA

Hi, all. From reading the hibernate manual, hibernate "guarantees" object identity for your persistent objects within a single session. That's what I'd expect (and want) after years of toplink experience. However, it's not what I'm seeing. This appears to be a bug to me, but since it's so fundamental to hibernate (I would think), I wanted to post it here to see if anyone can see something obvious I'm doing wrong before I enter it as a bug.

Here's the mapped class:
Code:
package com.foo.client.dataobject;

import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;

/**
* Data object that contains information about one of our clients.
*/
@Entity
@Table(name = "client")
public class Client {

  @Id
  @Column(name = "id")
  @Type(type = "com.foo.dbspecific.UUIDType")
  @GeneratedValue(generator = "inf-uuid")
  @GenericGenerator(name = "inf-uuid", strategy = "com.foo.dbspecific.UUIDGenerator")
  protected UUID mId;

  @Column(name = "name")
  private String mName;

  // Some other columns....

  /**
   * Default constructor for JPA.
   */
  Client() {
  }

  // Other methods...

}


(Note that hashCode() and equals() are not overwritten.) The test code:
Code:
...
public class ClientDaoTest extends AbstractTransactionalTestNGSpringContextTests {
  @PersistenceContext
  private EntityManager em;

  @Test(groups = { "functional" })
  public void identityTest() {
    Client c = (Client)em.createQuery("from Client c").getResultList().get(0);
    System.out.println("C ID IS " + c.getId() + ", hashCode " + c.hashCode());
    Client c2 = em.find(Client.class, c.getId());
    System.out.println("C2 ID IS " + c2.getId() + ", hashCode " + c2.hashCode() + ", ==? " + (c == c2));
    Client c3 =
        (Client)em.createQuery("from Client c where c.mId = :id").setParameter("id", c.getId())
            .getSingleResult();
    System.out.println("C3 ID IS " + c3.getId() + ", hashCode " + c3.hashCode() + ", ==? " + (c == c3));
  }
}


You (or at least I) would expect that the hashcodes for c, c2, and c3 would all be the same, and they would all "==" each other. However, this is the case only for c2 and c. Relevant output:

Code:
12:57:38.967 [1] DEBUG o.h.SQL: select client0_.id as id0_, client0_.database_user as database2_0_, client0_.insert_tm as insert3_0_, client0_.last_update_tm as last4_0_, client0_.name as name0_, client0_.status as status0_ from client client0_
C ID IS 0d2965a3-ce74-4b6f-926b-146420d20d12, hashCode 23578365
C2 ID IS 0d2965a3-ce74-4b6f-926b-146420d20d12, hashCode 23578365, ==? true
12:57:39.279 [1] DEBUG o.h.SQL: select client0_.id as id0_, client0_.database_user as database2_0_, client0_.insert_tm as insert3_0_, client0_.last_update_tm as last4_0_, client0_.name as name0_, client0_.status as status0_ from client client0_ where client0_.id=?
C3 ID IS 0d2965a3-ce74-4b6f-926b-146420d20d12, hashCode 28280066, ==? false


As this output indicates, no select is done to retrieve c2, and it is == to c. It also indicates a select is necessary to load c3 (not too surprising), but (surprisingly) c3 != c.

If someone thinks that the UUIDGenerator or UUIDType source code, the full log, persistence.xml, etc., are worth viewing, I'm happy to add that info as well -- just don't want to waste my time doing so if it's not relevant...

Thanks in advance,
Greg


Last edited by gregthoen on Fri Aug 22, 2008 5:42 pm, edited 1 time in total.

Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 21, 2008 3:05 am 
Pro
Pro

Joined: Tue Jun 12, 2007 4:13 am
Posts: 209
Location: Berlin, Germany
Hi Greg,

what makes you think that
Code:
(c == c3)

should return true?

After all, c3 was instantiated (!) by a separate query from the database. On the other side, you are right: there must be only one instance in Hibernate's persistent cache (I don't know how Hibernate does this). But this does not mean, that c3 is a simple alias to c.

What surprises me is that the hashcode for c3 is different!

_________________
Carlo
-----------------------------------------------------------
please don't forget to rate if this post helped you


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 21, 2008 10:21 am 
Newbie

Joined: Mon Aug 11, 2008 2:59 pm
Posts: 11
Hi, Carlo -- thanks for responding!

Quote:
what makes you think that (c == c3) should return true?


Because that's what the Hibernate documentation says, and after many years of toplink (now eclipselink) usage, it's what I'd expect from an ORM tool. Notice that the test code is all inside one single transaction, using one EntityManager (Hibernate session), so every object I'm getting from Hibernate should be in its first-level (session) cache. Some examples of Hibernate documentation that state that c should be == c3:

Page 22 of the Hibernate Core 3.2.2 PDF Manual (or online at http://www.hibernate.org/hib_docs/v3/re ... ure-states, section 2.2): "For a particular persistence context, Hibernate guarantees that persistent identity is equivalent to Java identity (in-memory location of the object)."

Page 43 of the Hibernate Core 3.2.2 PDF Manual (or online at the beginning of http://www.hibernate.org/hib_docs/v3/re ... lshashcode): "Hibernate guarantees equivalence of persistent identity (database row) and Java identity only inside a particular session scope."

Page 126 of the Hibernate Core 3.2.2 PDF Manual (or online at http://www.hibernate.org/hib_docs/v3/re ... s-identity): "Then for objects attached to a particular Session (i.e. in the scope of a Session) the two notions are equivalent, and JVM identity for database identity is guaranteed by Hibernate."

(Note that persistence context <==> hibernate session <==> EntityManager instance <==> session scope, and that JVM identity <==> Java identity <==> in-memory location of the object <==> "==" on the objects returns true . "<==>" here means "is equivalent to"....)

Quote:
What surprises me is that the hashcode for c3 is different!


Not sure why you'd be surprised that hashCode is different but that "==" is false? As I mentioned in the post, I have not overridden hashCode() in the objects (as the Hibernate docs state, you only need to if you are putting instances of the object in a detached Set), so hashCode() for these objects is java.lang.Object.hashCode(), which is essentially the memory address of the object (which is what determines if "==" is true).

So, as far as I can tell, in 3 different places in the Hibernate manual, Hibernate "guarantees" that JVM identity == database identity within a particular session, and yet that does not seem to be the case?

Any insight, Carlo or anyone else? :)

Thanks,
Greg


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 21, 2008 1:34 pm 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
Of course Hibernate guarantees object identity inside one persistence context. But what you are testing is not Hibernate, it's AbstractTransactionalTestNGSpringContextTests. Nobody here has any idea what that thing does. It might proxy the EntityManager instance you are testing. So please remove that or post on the forum of the authors of that magic class.

_________________
JAVA PERSISTENCE WITH HIBERNATE
http://jpwh.org
Get the book, training, and consulting for your Hibernate team.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 21, 2008 6:02 pm 
Newbie

Joined: Mon Aug 11, 2008 2:59 pm
Posts: 11
Thanks for responding, Christian.

The AbstractTransactionalTestNGSpringContextTests doesn't do much, but with Spring in the mix, it does proxy the EntityManager (using ThreadLocal stuff so that it can be thread-safe). So, I wrote a very simple main() that has no Spring at all in it, and the result is the same.

The persistent class is the same as in my original post. Here's the modified test with nothing but hibernate:

Code:
package com.foo.client.dao;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import com.foo.client.dataobject.Client;

public class NonSpringJPATest {

  public static void main(String[] args) {
    // Setup
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("HibTestPersistenceUnit");
    System.out.println("EMF class: " + emf.getClass().getName());
    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();
    tx.begin();

    Client c = (Client)em.createQuery("from Client c").setMaxResults(1).getResultList().get(0);
    System.out.println("C ID IS " + c.getId() + ", hashCode " + c.hashCode());
    Client c2 = em.find(Client.class, c.getId());
    System.out.println("C2 ID IS " + c2.getId() + ", hashCode " + c2.hashCode() + ", ==? " + (c == c2));
    Client c3 =
        (Client)em.createQuery("from Client c where c.mId = :id").setParameter("id", c.getId())
            .getSingleResult();
    System.out.println("C3 ID IS " + c3.getId() + ", hashCode " + c3.hashCode() + ", ==? " + (c == c3));

    tx.commit();
    em.close();
    emf.close();
  }
}


Here's the relevant output from running it:

Code:
EMF class: org.hibernate.ejb.EntityManagerFactoryImpl
16:50:31.275 [1] DEBUG o.h.SQL: select client0_.id as id0_, client0_.database_user as database2_0_, client0_.insert_tm as insert3_0_, client0_.last_update_tm as last4_0_, client0_.name as name0_, client0_.status as status0_ from client client0_ limit ?
C ID IS 61a0395a-f0cb-4f18-9fe3-fc6958870577, hashCode 7920716
C2 ID IS 61a0395a-f0cb-4f18-9fe3-fc6958870577, hashCode 7920716, ==? true
16:50:31.462 [1] DEBUG o.h.SQL: select client0_.id as id0_, client0_.database_user as database2_0_, client0_.insert_tm as insert3_0_, client0_.last_update_tm as last4_0_, client0_.name as name0_, client0_.status as status0_ from client client0_ where client0_.id=?
C3 ID IS 61a0395a-f0cb-4f18-9fe3-fc6958870577, hashCode 27136417, ==? false


The only other thing that I'd think would be of interest is the persistence.xml:

Code:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
                      http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
  version="1.0">

  <persistence-unit name="HibTestPersistenceUnit" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>com.foo.client.dataobject.Client</class>
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
      <property name="hibernate.connection.driver_class" value="org.postgresql.Driver"/>
      <property name="hibernate.connection.username" value="inf"/>
      <property name="hibernate.connection.password" value="letmein"/>
      <property name="hibernate.connection.url" value="jdbc:postgresql://localhost/inf"/>   
    </properties>
  </persistence-unit>
</persistence>


Any thoughts? It appears to be a bug to me, but I'm hesitant to file a bug, when it seems like something so fundamental, it's hard to believe it'd be broken in a public release.

Thanks,
Greg


Last edited by gregthoen on Fri Aug 22, 2008 5:47 pm, edited 1 time in total.

Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 21, 2008 6:23 pm 
Newbie

Joined: Mon Aug 11, 2008 2:59 pm
Posts: 11
*sigh* Never mind - figured it out finally by changing our id from a UUID to an int, and noticed that then the test worked fine. Looking at the UserType for UUID's that we wrote here (UUIDType), it appears that the guy who wrote the UserType had this in it:

Code:

@Override
  public boolean equals(Object x, Object y) {
    return x == y;
  }


rather than:

Code:
  @Override
  public boolean equals(Object x, Object y) {
    return x.equals(y);
  }


I think he just copied/pasted a UserType implementation he found somewhere on the web (that must have been for a primitive type), and didn't know to change the .equals(). Sorry for wasting your time...

Greg


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