-->
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.  [ 147 posts ]  Go to page Previous  1, 2, 3, 4, 5, 6, 7 ... 10  Next
Author Message
 Post subject:
PostPosted: Thu Feb 26, 2004 5:27 pm 
Beginner
Beginner

Joined: Sun Oct 26, 2003 11:21 pm
Posts: 27
RobJellinghaus wrote:
*What* underlying object? For CGLIB proxies there *is* no underlying object.


Incorrect. CGLIB proxies use your object underneath, see section 11.1 of the reference document, which also contains good information about proxies and == that I had forgotten when I answered your question.


RobJellinghaus wrote:
... That would be the same as saying that adding a proxy to a collection forces a full load of the proxy!


That's got to be te case if you're using all the object's fields in equals... how would one compare fields without loading them from the database.

Personally, I've had lots of luck using only the entitie's Hibernate Key property and it's Hibernate version property in equals and hashCode. I don't know why the Hibernate team doesn't recommend this approach but it means that equals and hashCode don't load extra fields from the database.

RobJellinghaus wrote:
I certainly hope this is true but pardon me if I don't take your word on it ;-)


It is actually more complicated that I first stated, see section 11.1 of the reference document.

Hans


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 26, 2004 7:02 pm 
Beginner
Beginner

Joined: Thu Nov 06, 2003 7:27 pm
Posts: 30
Location: Minneapolis, MN, USA
I've been meaning for a few days to sit down and do some thorough experimentation with my proposed implementation strategy. But I'm afraid I'm under a horrible deadline at work, and haven't had a chance yet! Apologies to Rob, who's waiting on the answer ... I will get to it, and when I do, I'll post the results!

In the meantime, I want to throw my strong support behind two things Rob said:

(1) The value object / domain object distinction is very important. It sounds like Hans is imagining a value object approach, while Rob and I are persisting domain objects. And domain objects really do have an identity which does not depend on their attributes -- they are objects proper, and their object identity is meaningful in the problem domain. In this sense, Christian, Hibernate is not being truly "transparent," because if it weren't for proxies, I wouldn't have to override eq()/hc() at all.

(2) It's potentially a Really Bad Thing if I have to inflate a proxy to call eq()/hc(). It basically makes proxies useless in many common situations.

More later when I get to it ....

Paul


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 26, 2004 7:20 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
melquiades wrote:
Apologies to Rob, who's waiting on the answer ... I will get to it, and when I do, I'll post the results!

Don't stress -- I'm implementing it myself right now :-) (I just patched hbm2java to support <meta attribute="suppress-equals-hashcode"> so you can turn off the generated methods....)

I'll keep you posted as well. And sorry I've been calling you "Mel" :-)
Cheers!
Rob

(p.s. ah the joys of open source -- anarchy can reign as needed :-D )


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 26, 2004 7:32 pm 
Beginner
Beginner

Joined: Thu Nov 06, 2003 7:27 pm
Posts: 30
Location: Minneapolis, MN, USA
Quote:
I'll keep you posted as well. And sorry I've been calling you "Mel" :-)

Yes, let's compare notes. And don't worry about the name -- though I must say, it threw me for a loop when I saw it! "Wait! Somebody else posted a different equals implementation? I missed that post!" (Melquiades is, of course, a character from One Hundred Years of Solitude, and a handle I've used for years.)

Cheers and tally-ho,

Paul


Top
 Profile  
 
 Post subject:
PostPosted: Thu Feb 26, 2004 9:01 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
gloeglm pointed out that plain old Java equality is actually good enough if 1) you don't keep objects around across sessions (which we don't) and 2) you aren't concerned about proxies. (Actually, he wasn't thinking about proxies, but we are in this thread.)

Somehow in this thread I got so wrapped up in things that I forgot all about using plain old object equality!

So I have added a flag to hbm2java to turn *off* its default equals() and hashCode() method generation. I plan to just use this flag and then not worry about this issue further.

Unfortunately for you, Paul, the Hibernate manual makes clear that you can't get what you want without some substantial Hibernate surgery:

Code:
Certain operations do not require proxy initialization

    * equals(), if the persistent class does not override equals()
    * hashCode(), if the persistent class does not override hashCode()
    * The identifier getter method


It looks to me like Hibernate is kind of trying to have it both ways here. It will ensure that two proxies to the same persistent object are == to each other (and hence hash to the same ID), but it will NOT ensure that an object is equal to its own proxy. (And come to think of it, how did you get into that situation anyway, unless you saved objects from session to session? -- which we already know is fundamentally evil, even in your land of beautiful horror?)

So I think that to make your hybrid-identity proposal work fully, you would need to:
[list=]Implement equals() and hashCode() as you have above
Add some kind of flag to the mapping to tell Hibernate to NOT try to load a proxy when calling equals() or hashCode() on it, but instead to just use the persistent identity[/list]

And you would still have trouble if you:
[list=]made a new object
added it to a set
saved it
closed the session
opened a new session
lazily loaded the object (getting a proxy)
checked to see whether the proxy is in the set[/list]
You want the final check to say "yes, this object is in the set." But even with your horrible beauty, that won't happen.

Although, let's continue down this primrose path of perfect obsession. I was explaining all this to a coworker and he said it made him nervous -- he wants a way to tell when someone does the wrong thing. So I was thinking, your objects that save their pre-persistent identity as their equalityObject could put themselves in some kind of weak cache when they do so. They are now the "Pre-Persistents" and they are forever suspect. My original thought ended there -- when you make a new session, do System.gc() and make sure the Pre-Persistent cache is empty afterwards (otherwise you are hanging onto objects when you shouldn't and you may corrupt your collections if you reload Pre-Persistents in the new session).

Now, *if* you could somehow intercept Hibernate's proxy creation, *then* you could check to see whether the persistent ID you are about to make a proxy for exists in this weak cache. If it *does* exist in that weak cache, it means that some collection somewhere is holding onto the original pre-persistent object. In that case, you should potentially just use that very object, rather than making a proxy at all!!!

What this basically amounts to then is a trans-session weak cache of ALL objects that obey this equality contract, and a tight integration of that trans-session with Hibernate.

I doubt whether Hibernate has the right hooks to enable such an integration now, but I'm sure it's possible.

However, having thought this through to the point where everyone except you and I are ready to shriek uncontrollably, and given that I don't actually need proxies let alone trans-session proxy-to-pre-persistent identity, I am going to stop thinking about this issue :-) I do hope you post how it goes for you though!

Cheers!
Rob


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 27, 2004 2:11 am 
Beginner
Beginner

Joined: Thu Nov 06, 2003 7:27 pm
Posts: 30
Location: Minneapolis, MN, USA
Quote:
It looks to me like Hibernate is kind of trying to have it both ways here. It will ensure that two proxies to the same persistent object are == to each other (and hence hash to the same ID), but it will NOT ensure that an object is equal to its own proxy.


Well summarized.

Quote:
(And come to think of it, how did you get into that situation anyway, unless you saved objects from session to session? -- which we already know is fundamentally evil, even in your land of beautiful horror?)


Fortunately, my situation is not this dire (though your weak cache scheme, I think, beats my equals() in the horribly beautiful / beautifully horrible contest!). It's a typical SSB app: there's one session per session bean call, and all persistent objects are discarded when we leave the call.

Here's the situation I'm worried about (and actually, I'm not even making this one up -- we have two objects that work exactly like this, and many other situations with the equivalent problem):

Code:
class A {
    private List<B> children;
    public List<B> getChildren() { return children; }
}

class B {   // suppose I proxy this class
    private A parent;
    public int getPosition() {
        return parent.getChildren().indexOf(this);
    }
}

Suppose I don't override eq()/hc():

Code:
...
    // creating the relationship, everything works fine:
    a.getChildren().add(b);
    b.setParent(a);
    b.getPosition();  // returns 0 (correct)
...
    // now suppose I come back later and read
    // the same b in a different session:
    b = findBById(b_id); // b is a proxy
    a = findAById(a_id); // a contains proxy for b
    b.getPosition();  // returns -1 (wrong) b/c real b != proxy b


What I really want is a hollow object, not a proxy. This is why the JDO people turned to bytecode modification (which I'm perfectly fine with, though I know it bother many people) -- they really thought this stuff through and came up with a scheme that works. If only there were an open source JDO implementation as good as Hibernate!


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 27, 2004 12:18 pm 
Beginner
Beginner

Joined: Sun Oct 26, 2003 11:21 pm
Posts: 27
I agree that it might be better to modify an object rather than proxying it.

I also agree that your code will break unless you define a notion of logical equivalence rather than physical equivalence. Why not make equals and hashCode dependant on the Hibernate ID or the Hibernate ID plus object version? Thus, an object will always equals and hashCode to another instance of itself as long as Hibernate believes that they are equal. And the ID and version are always loaded, so invoking equals won't cause extra overhead.

The down side is that you can't add the object to a Set/Map key until it's been saved. But if you're only adding the objects to a Hibernate managed collection, you'll need to save the object at some point anyway, so why not do it first.

And I'm sorry for calling you Mel too. It's just habit from the old IRC days when your name quickly became a shorter version of your handle


Top
 Profile  
 
 Post subject:
PostPosted: Fri Feb 27, 2004 8:01 pm 
Beginner
Beginner

Joined: Thu Nov 06, 2003 7:27 pm
Posts: 30
Location: Minneapolis, MN, USA
Quote:
Why not make equals and hashCode dependant on the Hibernate ID or the Hibernate ID plus object version? ... The down side is that you can't add the object to a Set/Map key until it's been saved.


You answered your own question.

Quote:
But if you're only adding the objects to a Hibernate managed collection, you'll need to save the object at some point anyway, so why not do it first.


Hmmm.
-- Not all orphans are persistable.
-- It breaks -- or at least undermines -- most forms of bulk insert, which can be a big performance hit.
-- Sometimes it's useful to validate a fully constructed object, and only then deny a save. Granted, we'll probably do a rollback if there's an exception...probably.

Quote:
And I'm sorry for calling you Mel too. It's just habit from the old IRC days when your name quickly became a shorter version of your handle


No apologies necessary -- it just caught me by surprise! It was usually "melq" on IRC.


Top
 Profile  
 
 Post subject: Another equals/hashCode strategy
PostPosted: Mon Mar 01, 2004 11:17 am 
Newbie

Joined: Sun Nov 30, 2003 7:24 am
Posts: 11
Here's my take on equals/hashCode:

I use Hibernate ID for equality check and hash code. I also always have a thread-local session available, so transient objects take care of save()-ing themselves when hashCode() is called. I operate on the premises that the object will either be saved later, or the transaction rolled back. There are some tricks involved to have proxies work as expected, and also to avoid them hitting the DB when ID is requested. Basically, my root data object class looks like this (irrelevant bits omitted):

Code:
import org.springframework.orm.hibernate.SessionFactoryUtils;

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Session;
import net.sf.hibernate.proxy.HibernateProxy;
import net.sf.hibernate.proxy.HibernateProxyHelper;

public class DataObject
{
    private Long id;
    private Integer version;
   
    public Long getId()
    {
        return id;
    }

    public void setId(Long id)
    {
        this.id = id;
    }

    public Integer getVersion()
    {
        return version;
    }
   
    public void setVersion(Integer version)
    {
        this.version = version;
    }
   
    public boolean equals(Object o)
    {
        if(o == this) return true;
        if(id == null) return false;
        if(o == null || HibernateProxyHelper.getClass(this) != HibernateProxyHelper.getClass(o))
        {
            return false;
        }
        return id.equals(getId((DataObject)o));
    }
   
    public int hashCode()
    {
        if(id == null) save();
        return id.hashCode();
    }
   
    private void save()
    {
        Session s = UserSession.getSession().getHibernateSession();
        try
        {
            s.save(this);
            s.flush();
        }
        catch(HibernateException e)
        {
            throw SessionFactoryUtils.convertHibernateAccessException(e);
        }
    }
   
    public static Long getId(DataObject dao)
    {
        if(dao instanceof HibernateProxy)
        {
            try
            {
                return (Long)HibernateProxyHelper.getIdentifier(dao, null);
            }
            catch(HibernateException e)
            {
                throw SessionFactoryUtils.convertHibernateAccessException(e);
            }
        }
        else
        {
            return dao.getId();
        }
    }
}


- equals() works on objects from different sessions and proxies as expected: persistent instances/proxies with equal ID compare as equal; transient instances compare equal only to themselves.
- equals() won't cause a proxy to hit the DB if a proxy is passed as a parameter to it
- hashCode() will cause a save/flush when called on a transient instance
- equals() will *not* cause a save/flush when called on a transient instance - it's not required
- a getId(DataObject) static method used by equals() is provided with public visibility for the app to access the ID without proxies hitting DB.

For my purposes, works just dandy. However, there really is no silver bullet - different approaches are required for different usage scenarios.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 01, 2004 12:20 pm 
Beginner
Beginner

Joined: Thu Nov 06, 2003 7:27 pm
Posts: 30
Location: Minneapolis, MN, USA
Quote:
I use Hibernate ID for equality check and hash code. I also always have a thread-local session available, so transient objects take care of save()-ing themselves when hashCode() is called.


Clever -- that works. It's essentially equivalent to assigning the ID on creation, but a little less eager, so presumably it's faster.

I think we may end up taking a route like this. Its only disadvantage is that it requires our domain objects to call back to the data layer, which is sort of a no-no in our world. But hopefully we can abstract that enough that it's not a problem.

Is there a way to have Hibernate assign an ID without calling save()? (I'm guessing not....)


Top
 Profile  
 
 Post subject: Further thoughts
PostPosted: Tue Mar 02, 2004 5:56 pm 
Beginner
Beginner

Joined: Thu Nov 06, 2003 7:27 pm
Posts: 30
Location: Minneapolis, MN, USA
Hmmmm ... if I understand correctly, a proxy will inflate for equals()/hashCode() if and only if the proxied object overrides them. (Right?) To make object comparison not force proxy inflation, you must not override equals().

But an object and its proxy never compare ==, so an object must override eq()/hc() to compare equal to its proxy. Therefore it is impossible to make a working equals() implementation that does not force proxy inflation.

That seems like a fairly serious shortcoming of the proxy framework. Am I understanding things correctly? Or am I just wrapped around the axle?

It would be nice to be able to say to hibernate, "proxy this object, but just use ID for equals." Maybe a proxy should take an "equals-strategy" attribute or something, with values "id|custom". Something like that.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Mar 03, 2004 1:58 am 
Newbie

Joined: Sun Nov 30, 2003 7:24 am
Posts: 11
My code above won't force proxy inflating when called as

Code:
nonproxy.equals(proxy)


I believe that's a best-effort approach toward not inflating proxies when not needed. You're right that

Code:
proxy.equals(whatever)


will still inflate the proxy, though. Yes, it'd be nice if it were configurable in cases where it is known that we don't need anything except ID for comparison. Hm. Maybe we should raise an issue in JIRA?


Top
 Profile  
 
 Post subject:
PostPosted: Wed Mar 03, 2004 2:35 am 
Newbie

Joined: Sun Nov 30, 2003 7:24 am
Posts: 11
As a matter of fact, this was already requested in JIRA:

http://opensource.atlassian.com/projects/hibernate/secure/ViewIssue.jspa?id=10701


Top
 Profile  
 
 Post subject: And the outcome is....
PostPosted: Mon Apr 19, 2004 7:57 am 
Newbie

Joined: Wed Jan 07, 2004 11:16 am
Posts: 2
Location: Netherlands
This discussion on the very fundamental issue of object equality has lasted for a week or two.

Mostly Hibernate users participated, with an occasional post from the Hibernate team, but what is the outcome?

I understand that this is not an easy ride, as multiple fundamental aspects have to be addressed:
- persistent / detached objects
- caching
- proxies
- performance

Why did the discussion come to an end early March, with no real solution?
The mentioned request in Jira (post in this discussion of March 3rd) was rejected.
Hibernate team: a penny for your thoughts...

Kind regards,

Rob.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Apr 19, 2004 10:09 am 
Hibernate Team
Hibernate Team

Joined: Tue Sep 09, 2003 2:10 pm
Posts: 3246
Location: Passau, Germany
All the thoughts and issuse about equals/hashCode have been put together here: http://www.hibernate.org/109.html


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 147 posts ]  Go to page Previous  1, 2, 3, 4, 5, 6, 7 ... 10  Next

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.