-->
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 ... 10  Next
Author Message
 Post subject:
PostPosted: Tue Feb 24, 2004 10:27 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
I think it's a beautifully horrible (horribly beautiful?) hack. My one concern is how will this interact with second-level caching, which crosses session lifetimes. But now I recall someone saying that the second-level cache is a *data* cache, NOT an *object* cache, so it should be fine.

Very very crafty :-) Maybe this is the right thing to do in hbm2java?!?!?! Gavin and Christian, I think you want to get this into the book!!!!!

Cheers!
Rob


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 25, 2004 12:04 am 
Beginner
Beginner

Joined: Sun Oct 26, 2003 11:21 pm
Posts: 27
Hmm... I'm not sure that I'm following the logic here, so just let me know if this doesn't make sense.

You must decide what equals means to your object. There's no standard, perfect meaning for equals.

Equals can mean that one field (the ID) is equal or it means that several fields are equal or it means that one field is equal unless it's null, in which case another should be used.

This may sound obvious but it seems to me like you're thinking that the last of these three solutions is the best solution. It certainly may be the one that fits your needs.

But if you were working with me and you presented this idea, I would flag it immediately as being too complex. Of course, I might turn out to be wrong in the end. It's just an instinct.

Here's an idea, I hope it helps.

If equals (or hashCode) is to include the ID, then it should always include ID, even if it's null. Behaving otherwise, IMHO, is asking for trouble. If your definition of equals changes depending on the circumstances, you're probably going to forget the rules at some point and expect equals to mean the wrong thing.

Now, if you've got a particular case in which you need to add a bunch of no-ID objects to a Set, why not just write an extension to HashSet? Your HashSet could wrap your objects in a class that implements the special version of hashCode. This way, the special behavior is confined to the place where you need it and you don't have to remember that hashCode behaves differently in different circumstances.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 25, 2004 1:12 am 
Beginner
Beginner

Joined: Thu Nov 06, 2003 7:27 pm
Posts: 30
Location: Minneapolis, MN, USA
Quote:
Hmm... I'm not sure that I'm following the logic here, so just let me know if this doesn't make sense.

You must decide what equals means to your object. There's no standard, perfect meaning for equals.

Equals can mean that one field (the ID) is equal or it means that several fields are equal or it means that one field is equal unless it's null, in which case another should be used.


I do think you're missing the logic. I want equals() to track logical object identity through the lifecycle of an object. This is the immutable primary key if it's set, object identity if it's not. Reread the first post for the logical consequences....

Quote:
If equals (or hashCode) is to include the ID, then it should always include ID, even if it's null. Behaving otherwise, IMHO, is asking for trouble.


Well, my "beautifully horrible hack" does essentially include the null ID, at least in a logical sense.....

Quote:
Now, if you've got a particular case in which you need to add a bunch of no-ID objects to a Set, why not just write an extension to HashSet?


Eek! And my own extension to Map, to List, to half of the utility methods on Collections (and don't forget the Apache Commons Collections), to JUnit ... where does it stop?


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 25, 2004 1:18 am 
Beginner
Beginner

Joined: Thu Nov 06, 2003 7:27 pm
Posts: 30
Location: Minneapolis, MN, USA
Quote:
I think it's a beautifully horrible (horribly beautiful?) hack. Very very crafty.


When insanity is the only option, I strive for elegant insanity.

Quote:
My one concern is how will this interact with second-level caching, which crosses session lifetimes. But now I recall someone saying that the second-level cache is a *data* cache, NOT an *object* cache, so it should be fine.


Yes, that's how I understood it as well. I think we're in the clear. We'll try it out tomorrow and see if it manages to get off the ground without bursting into flames and maiming poor Orville.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 25, 2004 2:00 am 
Beginner
Beginner

Joined: Sun Oct 26, 2003 11:21 pm
Posts: 27
You've got rules for equals that depend on the lifecycle. That's your call but I wouldn't go there myself.

No one but you can judge the quality of those rules because only you know the internals of your application that relate to the "lifecycle".

My lifecycle might say that one shouldn't expect to compare objects until they've been persisted and assigned an ID. You obviously have a different idea. I've never seen a solution that works for everyone.

So, your "hack", which is really an implementation of custom rules and not a hack at all, implements the following rule: "if the ID is null on the first call to equals, never use the ID again for the life of the object, even if the ID is set later. This includes serialized (by RMI) versions of the same object."

I'm not too keen on this rule. Having said that, I'm sure that you could fix it.

But do you want to spend the time? That kind of rule seems rather prone to error and I'd keep away from it. There are many cases for this kind of rule and my first instinct would be to document them all. My second is that it's not worth documenting all possible cases unless you're dealing with a very important part of the applicaiton.

For instance, your coded rule leads to another rule: an unpersisted object will never equals a version of itself that's loaded via an HSQL query. You're OK with that... but have you thought out all the similar rules? And if you've thought them out, have you documented them in case you forget? Do you want to?

If it's not worth it to document the cases, I'd reconsider implementing something that's got all those cases.

Going through that thought process, I recommended implementing a custom HashSet because I assumed that you're going to use the "special case" of non-ID equals far less frequently than the case of ID-based equals. Maybe I'm wrong about that.

My experience is that most of the time, you've got one main use case and several cases that happen much less frequently. I try to avoid solving the most generic case unless I really have to, or am particularly inspired and willing to write lots of tests. I usually solve the most common case in the main code and find seperate strategies for solving the less common cases.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 25, 2004 2:15 am 
Beginner
Beginner

Joined: Thu Nov 06, 2003 7:27 pm
Posts: 30
Location: Minneapolis, MN, USA
Quote:
You've got rules for equals that depend on the lifecycle. That's your call but I wouldn't go there myself. No one but you can judge the quality of those rules because only you know the internals of your application that relate to the "lifecycle".


To some externt, I disagree. Hibernate has a certain semantics of instance identity, which directly affects how things are loaded and saved. I want equals() to reflect those rules.

Quote:
This includes serialized (by RMI) versions of the same object."


We don't serialize our business objects; that's a notion that's just wildly incompatible with proxies and smart collections. If somebody thinks that's a good idea, though, they could certainly make the equality object transient.

Quote:
But do you want to spend the time? That kind of rule seems rather prone to error and I'd keep away from it.


The thing, we're already getting the errors. I'm not doing this for entertainment! But you make it sound as though I'm not thinking through the implications; believe me, I am certainly trying.

Quote:
For instance, your coded rule leads to another rule: an unpersisted object will never equals a version of itself that's loaded via an HSQL query.


I think this is not the case. Doesn't Hibernate guarantee instance uniqueness within a session?

Quote:
Going through that thought process, I recommended implementing a custom HashSet because I assumed that you're going to use the "special case" of non-ID equals far less frequently than the case of ID-based equals.


I think the difference in our perspectives is that I don't see this as a "special case" -- the use of object equality is widespread in our business domain layer. I want what I consider natural object semantics, and I don't want them to break just because I'm using an O/R mapper.

Here's a way of phrasing what I want in a way that makes it sound quite natural: I want equals() to behave exactly like == would if it passed through proxies.


Top
 Profile  
 
 Post subject: Stupid bug
PostPosted: Wed Feb 25, 2004 2:30 am 
Beginner
Beginner

Joined: Thu Nov 06, 2003 7:27 pm
Posts: 30
Location: Minneapolis, MN, USA
My cat example has a serious problem; that if-check that throws the exception is bogus, and just shouldn't be there at all. I'll post a revised version if/when we actually get it tested & working.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 25, 2004 6:11 am 
Hibernate Team
Hibernate Team

Joined: Mon Aug 25, 2003 9:11 pm
Posts: 4592
Location: Switzerland
Quote:
To some externt, I disagree. Hibernate has a certain semantics of instance identity, which directly affects how things are loaded and saved. I want equals() to reflect those rules.


And that is the problem. We are talking about two different scopes:

The scope when two objects have the same database identity and are in the same Set, outside of a Session, non-persistent (detached). We recommend to implement equals()/hashCode() for this to work, using a candidate key attribute.

The scope of two objects that have the same database identity and are in the same Table. We need a primary key for that and we use the best candidate key as a primary key attribute or better, add a surrogate key. The mapping of this database identifier to an identifier property is actually optional!

Don't mix those two, the first is only relevant for a few milliseconds, the other for a hundred years.

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


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 25, 2004 12:03 pm 
Beginner
Beginner

Joined: Thu Nov 06, 2003 7:27 pm
Posts: 30
Location: Minneapolis, MN, USA
Quote:
We are talking about two different scopes: ... The scope when two objects have the same database identity and are in the same Set, outside of a Session, non-persistent (detached). ... The scope of two objects that have the same database identity and are in the same Table. ... Don't mix those two.


The fundamental problem here is that objects and rows have very different notions of identity, and one has to serve the other. Both you and hgilde are taking the relational view: there should be some property which defines the identity of the thing we're talking about. And that's a valid view! In this view, the object serves the database's notion of identity, using "keys" for equality.

Another equally valid view is one in which the database serves the object's notion of identity through a synthetic key with no real-world meaning. This synthetic key becomes a long-lived extension of object identity after the original instance disappears. In this case, object identity is object identity, and the use of sequences and keys is just an implementation mechanism that gives the programmer this notion of "object instance" they're expecting. So yes, there are different scopes, but the semantics of identity are preserved between them; only the implementation mechanism of identity changes. There's no "mixing" of anything unless you're prying off the lid of the O/R mapper by looking at the ID (in which case you're worrying about the implementation mechanism and not the semantics).

Hibernate already supports this second view of the world, as any self-respecting O/R mapper should, by supporting transparent ID assignment and instance uniquing within a session. And if it weren't for proxies, I could just use == and be done with it. That should be a sign that I'm asking for something self-consistent and reasonable -- which I really think I am.

It's tempting to engage in "monster barring": when the answer is surprising, our instinct is to say that the question was unreasonable, or at least exceptional. I do not think my question is exceptional. I have a very OO view of identity; it's self-consistent and widely understood; I just don't want proxies to break it.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 25, 2004 12:11 pm 
Beginner
Beginner

Joined: Sun Oct 26, 2003 11:21 pm
Posts: 27
melquiades wrote:
To some externt, I disagree. Hibernate has a certain semantics of instance identity, which directly affects how things are loaded and saved. I want equals() to reflect those rules.


Hibernate says that two objects are equal if and only if their keys are equal. You're application obviously has rules that say that two objects are equal if all their other fields are equal. You're already differing from Hibernate.

melquiades wrote:
...they could certainly make the equality object transient.


Absolutly but now you'v got to remember to do that. Added complexity.

melquiades wrote:
The thing, we're already getting the errors.


Ah ha. I didn't know. I am doing this for fun. :) But I sympathize.

melquiades wrote:
But you make it sound as though I'm not thinking through the implications; believe me, I am certainly trying.


I apologize profusely, I didn't mean that at all. I meant that I would rather not think all that out, I'd look for a simpler solution instead.

melquiades wrote:
... I want what I consider natural object semantics, and I don't want them to break just because I'm using an O/R mapper.


Why not just compare on all attributes, including the ID? If two objects have not yet been persisted, their ID's will both be null and thus equal. This will leave the rest of the fields to make up the equals test.

melquiades wrote:
I want equals() to behave exactly like == would if it passed through proxies.


I'm sorry but I don't follow you. == does pass through proxies because two Hibernate proxies are only equal if they have the same key value, in which == will work fine. Are you mixing objects from different sessions or objects that have been loaded by Hibernate with those that haven't?


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 25, 2004 12:28 pm 
Beginner
Beginner

Joined: Sun Oct 26, 2003 11:21 pm
Posts: 27
melquiades wrote:
I do not think my question is exceptional. I have a very OO view of identity; it's self-consistent and widely understood; I just don't want proxies to break it.


If you want to store your objects in a relational database, you've got to think in terms relational data. This may or may not mix perfectly with your current OO thinking. But if you don't want to think in terms of a relational model, you're going to have trouble using an O/R mapper. What you might want is a pure object database, which Hibernate is not.

Hibernate tries to provide a framework to help you integrate the relational model into the OO model (or maybe the other way around) and it does a good job. I use hibernate all over the place but I've implemented my stuff so that my OO view meshes with the relational view.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 25, 2004 1:35 pm 
Beginner
Beginner

Joined: Thu Nov 06, 2003 7:27 pm
Posts: 30
Location: Minneapolis, MN, USA
hgilde wrote:
Your application obviously has rules that say that two objects are equal if all their other fields are equal.


Actually, no, that's the opposite of what I want! I want two objects to be equal if they are "the same" object. "The same" is well-defined in the absence of persistence; it's == and also the default implementation of equals(). Hibernate manages to keep it well-defined -- in the same way, no less! -- as long as I (1) don't mix objects from different sessions and (2) don't use proxies.

As it happens, (1) is not a problem for us, but (2) is.

Quote:
Why not just compare on all attributes, including the ID?


Logically: because, in this OO notion of equality, I could do this:
Code:
Thinger t1 = new Thinger();
Thinger t2 = new Thinger();
set.add(t1);
set.add(t2);
assert set.size() == 2;
t1.setName("bill"); // this is OK, because name is not part of identity

For performance reasons: because this would force inflation of proxies for equals(), which I don't want.

Quote:
I want equals() to behave exactly like == would if it passed through proxies.


Quote:
I'm sorry but I don't follow you. == does pass through proxies because two Hibernate proxies are only equal if they have the same key value, in which == will work fine.


You're thinking of equals(). An object and its proxy are not the same instance, and do not compare == to one another.

Quote:
Ah ha. I didn't know. I am doing this for fun. :)


Well, I will admit that, although necessity is the motivator, I do find the dicussion fun! (Necessity is the mother of entertainment?)


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 25, 2004 2:04 pm 
Beginner
Beginner

Joined: Sun Oct 26, 2003 11:21 pm
Posts: 27
Ok, I get it now. Maybe you could use the Lifecycle interface to know if you're under the control of a Session. If you are, then compare ID's, if not then compare with ==?

Code:
public boolean equals(Object o) {

//insert standard equals code here

    if (sessionControl)
        return this.getID().equals(that.getID());
    else
        return this == that;
}


This might not jive with the hashCode/equals contract.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 25, 2004 2:11 pm 
Beginner
Beginner

Joined: Sun Oct 26, 2003 11:21 pm
Posts: 27
Note that your definition of equals is not the only one out there. I use == rather rarely, only for performance reasons and in very controlled circumstances. In fact, once I'm on JDK 1.5 and my primitives are all boxed in objects, I might never use == again.

I always override equals and I define equals as "is of a compatible class and has the same contents" or "can be used in place of" rather than "occupies the same memory location".


Top
 Profile  
 
 Post subject: It's much worse than we thought
PostPosted: Wed Feb 25, 2004 7:06 pm 
Expert
Expert

Joined: Thu Jan 08, 2004 6:17 pm
Posts: 278
Hmm, I just went back to the very beginning of this thread and saw this:

melquiades wrote:
Our strategy has thus been to use ID when present, but fall back to instance equality if the ID is not set. Here's the problem with that approach:

Code:
Thinger t = new Thinger(); // t has no ID yet
Set s = new HashSet();
s.add(t);
s.contains(t); // returns true
session.save(t); // t now has an ID
s.contains(t); // returns false, because t's hash code changed

I hasten to point out that the problem here is not hashCode() -- it is behaving correctly: after being saved, t compares equal to objects it wouldn't have before (namely, anything with the same ID), so its hashCode must change.

Something looked familiar to me about this code. From the reference manual:

Code:
Parent p = .....;
Child c = new Child();
p.getChildren().add(c);
session.save(c);
session.flush();

If p.getChildren() is a HashSet, then this is identical to the code you gave. And you are absolutely right, it is broken! I ran it with this code:

Code:
        HashSet set = new HashSet();
        Campaign campaign = new Campaign("test1", client, new ArrayList(), new HashSet(), new HashSet(), new HashSet());
        set.add(campaign);
        log.info("set contains campaign: "+set.contains(campaign));
        log.info("campaign.hashcode = "+campaign.hashCode());

        session.save(client);
        //session.save(campaign);

        log.info("campaign.hashcode = "+campaign.hashCode());
        log.info("Does set contain campaign???: "+set.contains(campaign));


And sure enough, the output is:

Code:
INFO: set contains campaign: true
INFO: campaign.hashcode = 629
INFO: campaign.hashcode = 630
INFO: Does set contain campaign???: false


So what this means is that Hibernate's own reference manual could be extended to this:

Code:
Parent p = .....;
Child c = new Child();
p.getChildren().add(c);
session.save(c);
if (!p.getChildren().contains(c)) System.err.println("After saving c, c is no longer a child of p!!!");


and it will in fact print "After saving c, c is no longer a child of p!!!"

Very surprising, and a bit scary! Yes, this is only an issue for new objects... but you could easily construct a whole object graph (in the view layer, say), and then pass it in to saveAndUpdate... and suddenly all the HashSets in the code are broken!

In fact, this is guaranteed to happen whenever a graph of objects is passed to saveAndUpdate!!! If the graph contains HashSets (as could easily happen if a client constructs an object graph and sends it over to the server), then the graph, when deserialized (or just when constructed), will get hashcodes baked into its HashSets. And then when saveOrUpdate is called on the graph, it will break all the HashSet invariants!

I am amazed. Seems like this means that anytime you make an object graph persistent, you may have to immediately discard it and re-fetch it from scratch, as any collections (specifically HashSets, but maybe others too) which contain any objects in the graph may now be corrupt!

Am I overstating the case here? I actually don't think I am :-P Any contribution from Hibernate folks?

Mel, it would seem it has just become a lot more urgent that your technique (for coupling object identity to session scope) actually works.....

Cheers,
Rob


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 ... 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.