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