-->
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.  [ 8 posts ] 
Author Message
 Post subject: use-in-equals for a subclass
PostPosted: Thu Jun 28, 2007 12:25 am 
Newbie

Joined: Thu Jun 28, 2007 12:12 am
Posts: 5
My meta use-in-equals is being ignored in a subclass. Is this expected? I'm using hibernate tools 3.2.0.beta9a.

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 schema="test" package="org.hibernate.forum">

  <class name="Bug" table="bug" discriminator-value="Bug">
 
    <id name="id" type="int" column="id">
      <generator class="native"/>
    </id>
   
    <discriminator column="bug_type" type="string" length="32" />
   
    <property name="name"  type="string" length="64" not-null="true">
      <meta attribute="use-in-equals">true</meta>
    </property>
   
  </class>
 
  <subclass name="SubBug" discriminator-value="SubBug" extends="Bug" >
    <property name="subname" type="string" length="64" not-null="true" >
      <meta attribute="use-in-equals">true</meta>
    </property>
  </subclass>

</hibernate-mapping>


The resulting Bug class has an equals method and hashcode() , the resulting SubBug has neither.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Jun 28, 2007 5:41 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
if i remember correctly the you should get a warning about it being ignored because if not it would result in asymmetric equals which is bad.

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject: Use discriminator column & Compare to Ecliplse
PostPosted: Sat Jun 30, 2007 5:36 pm 
Newbie

Joined: Thu Jun 28, 2007 12:12 am
Posts: 5
It never amazes me how hard equals() is! After some study, I see the problem. Fortunately, there is a solution...

If you think in terms of the table-per-class inheritance model, I want the discriminator column in the equivalence relation which comparing a fixed projection of the table onto a set of columns. Equality after projection is always an equivalence relation, so this must work. In my Bug, SubBug example this set of fields is {discriminator, name, subname}.

The way hibernate implements equals uses instanceof instead of getClass() comparison.. This is precisely the difference between including or not including the discriminator column in the table projection. Interestingly, eclipse generates equals() methods that always use the getClass() comparison. Neither is right or wrong, they simply define different equalivalence relations. Depending on what I'm modelling I will need both at different times.

The example Bug.equals method from hibernate:
Code:
public boolean equals(Object other) {
         if ( (this == other ) ) return true;
       if ( (other == null ) ) return false;
       if ( !(other instanceof Bug) ) return false;
       Bug castOther = ( Bug ) other;
         
       return ( (this.getName()==castOther.getName()) || ( this.getName()!=null && castOther.getName()!=null && this.getName().equals(castOther.getName()) ) );


Compare to what eclipse does via Source->Generate hashcode() and Equals() for Bug.equals :
Code:
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final Bug other = (Bug) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
and then SubBug.equals
Code:
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        final SubBug other = (SubBug) obj;
        if (subname == null) {
            if (other.subname != null)
                return false;
        } else if (!subname.equals(other.subname))
            return false;
        return true;
    }


Note the gotcha with the eclipse way is that even if I cast I can never have a Bug equal a SubBug.
Code:
        Bug bug1 = new Bug("null pointer");
        SubBug subBug2 = new SubBug("null pointer", "foo");
        Bug bug2 = (Bug)subBug2;
        assert (bug2.equals(bug1) == false);

The last assertion is because bug2.equals calls SubBug.equals (not Bug.equals!) and the getClass() returns SubBug, while obj.getClass() returns Bug, so it drops out. This seems really strange unless you think about it in terms of the database table and projecting onto {discriminator, name, subname}. Casting doesn't change the getClass() value, nor the discriminator column.

If you don't include the discriminator column in the projection, but do use a column that only exists in a subtype, you get into trouble when you cast up, because the parent class can't do the projection you really want as it doesn't have access to all the fields. The child can, so you get different results. Hence the violation of symmetry.

The implementation mechanism to allow both seems obvious: allow use-in-equals to apply to the descriminator too. Then use getClass comparison like eclipse if it's set. Otherwise use instanceof as you already do.[/code]


Top
 Profile  
 
 Post subject:
PostPosted: Mon Jul 02, 2007 4:27 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
I don't think calling .getClass() will work properly with proxies.

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject: Proxies?
PostPosted: Mon Jul 02, 2007 9:27 pm 
Newbie

Joined: Thu Jun 28, 2007 12:12 am
Posts: 5
Good point. So use HibernateProxyHelper.getClassWithoutInitalizingProxy() instead.


Top
 Profile  
 
 Post subject: better yet
PostPosted: Mon Jul 02, 2007 10:31 pm 
Newbie

Joined: Thu Jun 28, 2007 12:12 am
Posts: 5
Better yet, call an equality helper utility that can map .getClass() to the proper discriminator value. A simple implementation would just resolve proxies, but a complex one might give me more control over which branches of the inheritance hierarchy I consider equalivalent.

For example, suppose I had a Person class with a Man and Woman subclass and each of these had lots of subtypes. Maybe I want equals to require the same gender by ascending until I hit Man or Woman (or Person, I suppose).

If I had a proxy for a Father object (a sublcass of Man), then I would call something like: equalityHelper.getEquivalenceClass(fatherProxy) and get Man.class. This could be implemented like this
Code:
public Class getEquivalenceClass(Object o) {
    return getEquivalenceClass(o.getClass());
}

public Class getEquivalenceClass(Class c)
    Class eq = equivalenceMap.get(c);
    return (eq == null) ? getEquivalenceClass(c.getSuperclass()) : eq;
}


A class could be put in the equivalenceMap for it's root class if it is the root mapping class, or if it's so annotated with an appropriate use-in-equals meta tag on its discriminator.

Using an EqualityHelper could simplify the generation of equality methods and make it powerful and extensible. The hbm.xml file could be used to state which equality helper implementation to use if you wanted to override the default.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Jul 03, 2007 1:51 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
having hibernate specific code in pojo's would be bad...

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jul 04, 2007 12:02 am 
Newbie

Joined: Thu Jun 28, 2007 12:12 am
Posts: 5
It's also bad to introduce hibernate specific proxies underneath the pojos when that interferes in subtle ways with common and useful ways of constructing equals methods. If the choice is truly between the not using the equals method you need and hibernate free pojos is there no need for choice?

What would you suggest as the proxy-safe way to require a gender match in a class model using Man and Woman subclasses of the Person class?

To remove the hibernate dependencies from the generated domain model, you could generate the artifacts I was speaking of in a user specified package. One solutione would use a generated EqualityHelper interface and EqualityHelperFactory class, both referenced from the pojo equals method, and then either generic standard EqualityHelper implementations with the factory using generated code or configuration to get the right pojo specific behavior or, alternatively, a domain model specific implementation for each mapped class. This option adds a little complexity, so maybe it's a three way choice:

a) simple: less fine grained control over equals methods (status quo)
b) coupled: fine grained control with pojos that depend on hibernate
c) complex: fine grained control without pojo-hibernate coupling

Tough decision...


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