GORM doesn't inject hashCode and equals

classic Classic list List threaded Threaded
19 messages Options
Reply | Threaded
Open this post in threaded view
|

GORM doesn't inject hashCode and equals

Lari Hotari -

I was debugging a Grails application and noticed a very strange bug that was caused by missing hashCode and equals methods in domain classes.
Grails doesn't inject these by default. There is a common misunderstanding that GORM takes care of generating hashCode and equals.

GORM only injects toString by default. This is implemented in DefaultGrailsDomainClassInjector:
http://github.com/grails/grails/blob/3dfc143677e13c5574f4724159f53ae92cb35f5a/grails/src/commons/org/codehaus/groovy/grails/compiler/injection/DefaultGrailsDomainClassInjector.java

I think this JIRA should be reopened:
http://jira.codehaus.org/browse/GRAILS-2372
I'd place a vote on this one.

Check the comments on this Jira:
http://jira.codehaus.org/browse/GROOVY-27
"Allow autogeneration of hashCode and equals for 'database' style objects"
quoting this JIRA:
"Guillaume Laforge added a comment - 02/Nov/07 04:37 AM
The GORM layer in Grails offers a similar mechanism.
It's not in Groovy per-se, but one can easily reuse GORM standalone in Groovy."

GORM doesn't generate hashCode and equals... Even Guillaume thinks it does. :)
I hope it could.

I think the original plan was to also inject hashCode and equals:
http://grails.markmail.org/message/2eiybnpjgxmxcxfc
http://grails.markmail.org/message/fvpdhsoyrobwhgvb


Regards,

Lari
Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

Lari Hotari -

Hibernate 2nd level caching seems to have problems with domain classes
that don't implement hashCode & equals.
I found these references in Hibernate forum:
http://forum.hibernate.org/viewtopic.php?t=990520&start=0&postdays=0&postorder=asc&highlight=
http://forum.hibernate.org/viewtopic.php?p=2392840

My caching problems went away after adding equals and hashCode by using
commons-lang builders:

import org.apache.commons.lang.builder.*

    @Override
    boolean equals(final Object that) {
            EqualsBuilder.reflectionEquals(this, that, ["id"])
    }

    @Override
    int hashCode() {
            HashCodeBuilder.reflectionHashCode(this, ["id"])
    }

In my case the objects are read-only so I only added the unique id to
hashCode & equals (this might not be valid for your solution).

Regards,

Lari

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Re: GORM doesn't inject hashCode and equals

Robert Fletcher
Basing equals and hashCode on id in domain objects isn't a very good
idea as the hashCode will change when the object is saved. Usual good
practice is to base it on a (preferably immutable) unique property
(such as username for a user class). I don't know how Grails would go
about identifying the right candidate property or properties
automatically.

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: Re: GORM doesn't inject hashCode and equals

Lari Hotari -
Robert Fletcher kirjoitti:
> Basing equals and hashCode on id in domain objects isn't a very good
> idea as the hashCode will change when the object is saved. Usual good
> practice is to base it on a (preferably immutable) unique property
> (such as username for a user class). I don't know how Grails would go
> about identifying the right candidate property or properties
> automatically.
>  

I understand that. I mentioned this in my posting:
>
>
> In my case the objects are read-only so I only added the unique id to
> hashCode & equals (this might not be valid for your solution).
>
>

Regards,

Lari

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

Robert Fischer
In reply to this post by Lari Hotari -
How would you assert that hashCode/equals is defined?  Using "id" falls down if you have both saved
and unsaved objects: two objects with the unsaved id might not be semantically equal (e.g. might
have different properties other than id) but would return equals; an object that is saved would
suddenly not be equal to a copy which was unsaved.

So, what's the recommended alternative?

Before you answer -- here's some food for thought.
http://enfranchisedmind.com/blog/posts/a-java-gotcha/
http://enfranchisedmind.com/blog/posts/java-posse-equals-inheritance-and-the-liskov-substitution-principle/

~~ Robert.

Lari Hotari wrote:

>
> I was debugging a Grails application and noticed a very strange bug that
> was caused by missing hashCode and equals methods in domain classes.
> Grails doesn't inject these by default. There is a common
> misunderstanding that GORM takes care of generating hashCode and equals.
>
> GORM only injects toString by default. This is implemented in
> DefaultGrailsDomainClassInjector:
> http://github.com/grails/grails/blob/3dfc143677e13c5574f4724159f53ae92cb35f5a/grails/src/commons/org/codehaus/groovy/grails/compiler/injection/DefaultGrailsDomainClassInjector.java
>
> I think this JIRA should be reopened:
> http://jira.codehaus.org/browse/GRAILS-2372
> I'd place a vote on this one.
>
> Check the comments on this Jira:
> http://jira.codehaus.org/browse/GROOVY-27
> "Allow autogeneration of hashCode and equals for 'database' style objects"
> quoting this JIRA:
> "Guillaume Laforge
> <http://jira.codehaus.org/secure/ViewProfile.jspa?name=guillaume> added
> a comment - 02/Nov/07 04:37 AM
> The GORM layer in Grails offers a similar mechanism.
> It's not in Groovy per-se, but one can easily reuse GORM standalone in
> Groovy."
>
> GORM doesn't generate hashCode and equals... Even Guillaume thinks it
> does. :)
> I hope it could.
>
> I think the original plan was to also inject hashCode and equals:
> http://grails.markmail.org/message/2eiybnpjgxmxcxfc
> http://grails.markmail.org/message/fvpdhsoyrobwhgvb
>
>
> Regards,
>
> Lari

--
~~ Robert Fischer.
Grails Training        http://GroovyMag.com/training
Smokejumper Consulting http://SmokejumperIT.com
Enfranchised Mind Blog http://EnfranchisedMind.com/blog

Check out my book, "Grails Persistence with GORM and GSQL"!
http://www.smokejumperit.com/redirect.html

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

Lari Hotari -
Robert Fischer kirjoitti:

> How would you assert that hashCode/equals is defined?  Using "id"
> falls down if you have both saved and unsaved objects: two objects
> with the unsaved id might not be semantically equal (e.g. might have
> different properties other than id) but would return equals; an object
> that is saved would suddenly not be equal to a copy which was unsaved.
>
> So, what's the recommended alternative?
>
> Before you answer -- here's some food for thought.
> http://enfranchisedmind.com/blog/posts/a-java-gotcha/
> http://enfranchisedmind.com/blog/posts/java-posse-equals-inheritance-and-the-liskov-substitution-principle/ 
>
>
This problem is very familiar to the Hibernate folks:
http://www.hibernate.org/109.html

The database is read-only in my specific case where I used the database
primary key (id) as part of the hashCode/equals.

Before adding hashCode/equals the Hibernate's caching produced a lot of
cache misses. I believe other Grails users that might want to optimize
performance by using Hibernate caching might be interested in this
information. I didn't have time to dig in the problem so that I could
find out the specific reason. I did find these postings on Hibernate
forum which are related to this problem.

Regards,

Lari

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

Daniel Honig
So is there anything I'm doing that is terribly wrong by using the generate 'equals/hashCode' function for my objects that is boiled into IntelliJ and throwing caution to the wind??

@Robert good articles BTW

On Thu, Apr 9, 2009 at 5:37 PM, Lari Hotari <[hidden email]> wrote:
Robert Fischer kirjoitti:

How would you assert that hashCode/equals is defined?  Using "id" falls down if you have both saved and unsaved objects: two objects with the unsaved id might not be semantically equal (e.g. might have different properties other than id) but would return equals; an object that is saved would suddenly not be equal to a copy which was unsaved.

So, what's the recommended alternative?

Before you answer -- here's some food for thought.
http://enfranchisedmind.com/blog/posts/a-java-gotcha/
http://enfranchisedmind.com/blog/posts/java-posse-equals-inheritance-and-the-liskov-substitution-principle/

This problem is very familiar to the Hibernate folks:
http://www.hibernate.org/109.html

The database is read-only in my specific case where I used the database primary key (id) as part of the hashCode/equals.

Before adding hashCode/equals the Hibernate's caching produced a lot of cache misses. I believe other Grails users that might want to optimize performance by using Hibernate caching might be interested in this information. I didn't have time to dig in the problem so that I could find out the specific reason. I did find these postings on Hibernate forum which are related to this problem.

Regards,

Lari


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

  http://xircles.codehaus.org/manage_email



Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

Robert Fischer
In reply to this post by Lari Hotari -
A read-only database is not the normal case for Grails.  Given that, what's your recommended
alternative to GORM using the default object hashCode/equals?

~~ Robert.

Lari Hotari wrote:

> Robert Fischer kirjoitti:
>> How would you assert that hashCode/equals is defined?  Using "id"
>> falls down if you have both saved and unsaved objects: two objects
>> with the unsaved id might not be semantically equal (e.g. might have
>> different properties other than id) but would return equals; an object
>> that is saved would suddenly not be equal to a copy which was unsaved.
>>
>> So, what's the recommended alternative?
>>
>> Before you answer -- here's some food for thought.
>> http://enfranchisedmind.com/blog/posts/a-java-gotcha/
>> http://enfranchisedmind.com/blog/posts/java-posse-equals-inheritance-and-the-liskov-substitution-principle/ 
>>
>>
> This problem is very familiar to the Hibernate folks:
> http://www.hibernate.org/109.html
>
> The database is read-only in my specific case where I used the database
> primary key (id) as part of the hashCode/equals.
>
> Before adding hashCode/equals the Hibernate's caching produced a lot of
> cache misses. I believe other Grails users that might want to optimize
> performance by using Hibernate caching might be interested in this
> information. I didn't have time to dig in the problem so that I could
> find out the specific reason. I did find these postings on Hibernate
> forum which are related to this problem.
>
> Regards,
>
> Lari
>
> ---------------------------------------------------------------------
> To unsubscribe from this list, please visit:
>
>    http://xircles.codehaus.org/manage_email
>
>
>

--
~~ Robert Fischer.
Grails Training        http://GroovyMag.com/training
Smokejumper Consulting http://SmokejumperIT.com
Enfranchised Mind Blog http://EnfranchisedMind.com/blog

Check out my book, "Grails Persistence with GORM and GSQL"!
http://www.smokejumperit.com/redirect.html

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

Robert Fischer
In reply to this post by Daniel Honig
IIRC (it's been a while since I was on IntelliJ), they do the same thing that Commons-Lang does with
their EqualsBuilder: it checks all the properties of the objects for equality.  If you don't include
"id" in that list, and you only define hashCode/equals on leaf classes (that is, classes which are
never inherited from), you'll be fine.  :)

~~ Robert.

Daniel Honig wrote:

> So is there anything I'm doing that is terribly wrong by using the
> generate 'equals/hashCode' function for my objects that is boiled into
> IntelliJ and throwing caution to the wind??
>
> @Robert good articles BTW
>
> On Thu, Apr 9, 2009 at 5:37 PM, Lari Hotari <[hidden email]
> <mailto:[hidden email]>> wrote:
>
>     Robert Fischer kirjoitti:
>
>         How would you assert that hashCode/equals is defined?  Using
>         "id" falls down if you have both saved and unsaved objects: two
>         objects with the unsaved id might not be semantically equal
>         (e.g. might have different properties other than id) but would
>         return equals; an object that is saved would suddenly not be
>         equal to a copy which was unsaved.
>
>         So, what's the recommended alternative?
>
>         Before you answer -- here's some food for thought.
>         http://enfranchisedmind.com/blog/posts/a-java-gotcha/
>         http://enfranchisedmind.com/blog/posts/java-posse-equals-inheritance-and-the-liskov-substitution-principle/
>
>
>     This problem is very familiar to the Hibernate folks:
>     http://www.hibernate.org/109.html
>
>     The database is read-only in my specific case where I used the
>     database primary key (id) as part of the hashCode/equals.
>
>     Before adding hashCode/equals the Hibernate's caching produced a lot
>     of cache misses. I believe other Grails users that might want to
>     optimize performance by using Hibernate caching might be interested
>     in this information. I didn't have time to dig in the problem so
>     that I could find out the specific reason. I did find these postings
>     on Hibernate forum which are related to this problem.
>
>     Regards,
>
>     Lari
>
>
>     ---------------------------------------------------------------------
>     To unsubscribe from this list, please visit:
>
>       http://xircles.codehaus.org/manage_email
>
>
>

--
~~ Robert Fischer.
Grails Training        http://GroovyMag.com/training
Smokejumper Consulting http://SmokejumperIT.com
Enfranchised Mind Blog http://EnfranchisedMind.com/blog

Check out my book, "Grails Persistence with GORM and GSQL"!
http://www.smokejumperit.com/redirect.html

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

Robert Fletcher
I'm not sure checking *all* the properties is really the way to go. It
will probably work adequately for most cases but is it really
necessary for first class domain classes (i.e. not 'join' classes)
that will tend to have some kind of natural business key. Join class
equality can be based on the two associations for a many-to-many case
or the owning association and index. There's quite a good primer in
this on the Hibernate site: http://www.hibernate.org/109.html

I agree that writing equals and hashCode methods is a PITA and one of
the few things I have to do when developing with Grails that feels
'boilerplatey'. But, I think there's just enough thought required to
make automating or applying a convention over configuration approach
difficult.

Cheers,
Rob

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

Robert Fletcher
Sorry, just noticed that link was already posted in the thread.

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

Dierk König
In reply to this post by Robert Fletcher
I think what we need here is an AST Macro, e.g.

@Equator (props="a, b, c")

that fills in equals/hashcode at bytecode level by using the
given prop names for determining equality and hashcode.

The name of the macro is of course a joke.

cheers
Dierk


Am 10.04.2009 um 10:07 schrieb Robert Fletcher:

> I'm not sure checking *all* the properties is really the way to go. It
> will probably work adequately for most cases but is it really
> necessary for first class domain classes (i.e. not 'join' classes)
> that will tend to have some kind of natural business key. Join class
> equality can be based on the two associations for a many-to-many case
> or the owning association and index. There's quite a good primer in
> this on the Hibernate site: http://www.hibernate.org/109.html
>
> I agree that writing equals and hashCode methods is a PITA and one of
> the few things I have to do when developing with Grails that feels
> 'boilerplatey'. But, I think there's just enough thought required to
> make automating or applying a convention over configuration approach
> difficult.
>
> Cheers,
> Rob
>
> ---------------------------------------------------------------------
> To unsubscribe from this list, please visit:
>
>    http://xircles.codehaus.org/manage_email
>
>


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

a.shneyderman
> @Equator (props="a, b, c")

This will be PITA to maintain. "a, b, c" are not re-nameable anymore.

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

Robert Fletcher
That problem already exists for transients, hasMany, allowedMethods,
etc. in Grails. Not that I disagree with your point.

On Fri, Apr 10, 2009 at 10:52 AM, Alex Shneyderman
<[hidden email]> wrote:
>
> This will be PITA to maintain. "a, b, c" are not re-nameable anymore.
>

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

Robert Fischer
In reply to this post by a.shneyderman
If you rename a property -- particularly one that's a key identity property for your class -- you're
pretty much in pain one way or another.

An AST transform seems appropriate here.  It could leverage commons-lang
EqualsBuilder/HashCodeBuilder.  If someone wants to submit one to me, I'll put it into GORM Labs
right away.

~~ Robert.

Alex Shneyderman wrote:

>> @Equator (props="a, b, c")
>
> This will be PITA to maintain. "a, b, c" are not re-nameable anymore.
>
> ---------------------------------------------------------------------
> To unsubscribe from this list, please visit:
>
>     http://xircles.codehaus.org/manage_email
>
>
>

--
~~ Robert Fischer.
Grails Training        http://GroovyMag.com/training
Smokejumper Consulting http://SmokejumperIT.com
Enfranchised Mind Blog http://EnfranchisedMind.com/blog

Check out my book, "Grails Persistence with GORM and GSQL"!
http://www.smokejumperit.com/redirect.html

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

Lance Lavandowska-2
Dierk König wrote:
 > I think what we need here is an AST Macro, e.g.

Before I read the AST suggestion I was thinking perhaps another static
block (like mappings, constraints, hasMany):

static hashCode = [a, b, c]

In that link Robert mentioned it does say

'You can though mark certain properties with <meta
attribute="use-in-equals">true</meta> to tell hbm2java to generate a
proper equals/hashcode.'

So it isn't a new idea to generate equals/hashcode from a notation.

Lance

Robert Fischer wrote:

> If you rename a property -- particularly one that's a key identity
> property for your class -- you're pretty much in pain one way or another.
>
> An AST transform seems appropriate here.  It could leverage commons-lang
> EqualsBuilder/HashCodeBuilder.  If someone wants to submit one to me,
> I'll put it into GORM Labs right away.
>
> ~~ Robert.
>
> Alex Shneyderman wrote:
>>> @Equator (props="a, b, c")
>>
>> This will be PITA to maintain. "a, b, c" are not re-nameable anymore.

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

Robert Fischer
The AST transform is more generally useful, so it's probably the better route.

Lance Lavandowska wrote:

> Dierk König wrote:
>  > I think what we need here is an AST Macro, e.g.
>
> Before I read the AST suggestion I was thinking perhaps another static
> block (like mappings, constraints, hasMany):
>
> static hashCode = [a, b, c]
>
> In that link Robert mentioned it does say
>
> 'You can though mark certain properties with <meta
> attribute="use-in-equals">true</meta> to tell hbm2java to generate a
> proper equals/hashcode.'
>
> So it isn't a new idea to generate equals/hashcode from a notation.
>
> Lance
>
> Robert Fischer wrote:
>> If you rename a property -- particularly one that's a key identity
>> property for your class -- you're pretty much in pain one way or another.
>>
>> An AST transform seems appropriate here.  It could leverage
>> commons-lang EqualsBuilder/HashCodeBuilder.  If someone wants to
>> submit one to me, I'll put it into GORM Labs right away.
>>
>> ~~ Robert.
>>
>> Alex Shneyderman wrote:
>>>> @Equator (props="a, b, c")
>>>
>>> This will be PITA to maintain. "a, b, c" are not re-nameable anymore.
>
> ---------------------------------------------------------------------
> To unsubscribe from this list, please visit:
>
>    http://xircles.codehaus.org/manage_email
>
>
>

--
~~ Robert Fischer.
Grails Training        http://GroovyMag.com/training
Smokejumper Consulting http://SmokejumperIT.com
Enfranchised Mind Blog http://EnfranchisedMind.com/blog

Check out my book, "Grails Persistence with GORM and GSQL"!
http://www.smokejumperit.com/redirect.html

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

ld@ldaley.com

On 14/04/2009, at 2:09 AM, Robert Fischer wrote:

> The AST transform is more generally useful, so it's probably the  
> better route.

But it's a bit out of alignment with general GORM configuration.

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply | Threaded
Open this post in threaded view
|

Re: GORM doesn't inject hashCode and equals

Robert Fischer
Don't think of it as GORM configuration, then.  :)

Luke Daley wrote:
>
> On 14/04/2009, at 2:09 AM, Robert Fischer wrote:
>
>> The AST transform is more generally useful, so it's probably the
>> better route.
>
> But it's a bit out of alignment with general GORM configuration.
>

~~ Robert Fischer.
Grails Training        http://GroovyMag.com/training
Smokejumper Consulting http://SmokejumperIT.com
Enfranchised Mind Blog http://EnfranchisedMind.com/blog

Check out my book, "Grails Persistence with GORM and GSQL"!
http://www.smokejumperit.com/redirect.html

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email