|
From: <jue...@we...> - 2003-07-29 14:03:39
|
Everyone, A rather general problem that I've been wondering about for a while: = "equals"/"hashCode" implementation for entities with an externally = determined ID. I've repeatedly discovered issues with primary-key-based implementations = of the both. The root cause is adding an entity to a HashSet *before* = setting its ID, e.g. when using a MySQL autoincrement column as primary = key with Hibernate. Example: Filling a parent folder including some = children and then saving them all in one go. A "contains" or "remove" = call on the children collection will fail then, as the hash code has = changed while the object was in the collection. Of course, HashSet states that this is illegal, but how to avoid this? = It's especially tricky with m:n associations, e.g. between parents and = children. No matter how you load such an object tree, some entities will = always get added to a collection in a not fully initialized state, = potentially incurring hash code failure when the ID hasn't been set yet. = This not only arises with persistence tools like Hibernate but also with = remoting tools like Hessian. I see various solutions for the problem: - Reinitializing the HashSet after the change: This solves the = create-then-save issue, but seems like a hack and doesn't address the = mutual m:n issue. - Use the standard "equals"/"hashCode" implementations: This solves both = issues, but object equality is based on object identity then, which = isn't suitable e.g. for web applications that might store one entity in = the HTTP session and load another freshly, maybe wanting to compare = their parent. - "equals" based on the primary key, but "hashCode" returning the same = value for all instances of a class: This is valid, as "hashCode" has to = return the same value if "equals" is true but just *should* return = distinct values if not equal. As "hashCode" doesn't depend on the = primary key anymore, both issues are solved. The latter has the disadvantage that it doesn't care for the performance = of hash lookups. Looking up such an entity in a hash table is linear = like with an unsorted array. But typically this isn't a big deal, as = most collections will just contain a limited number of objects anyway, = e.g. assocation collections in an object tree. We've currently adopted the latter approach, but I'm not 100% convinced = that there isn't a better solution for the problem. What do you think? Juergen DI J=FCrgen H=F6ller Senior System Architect ______________________________________ werk3ATS - division systementwicklung part of werk3AT internetmedien oeg europaplatz 4 A - 4020 linz t. +43 (0) 732 71 65 29 502 f. +43 (0) 732 71 65 29 3 jue...@we... www.werk3at.com ______________________________________ werk3ATS - WIR ENTWICKELN ERFOLG |
|
From: Colin S. <col...@ex...> - 2003-07-29 14:44:35
|
jürgen höller [werk3AT] wrote: >Everyone, > >A rather general problem that I've been wondering about for a while: "equals"/"hashCode" implementation for entities with an externally determined ID. > >I've repeatedly discovered issues with primary-key-based implementations of the both. The root cause is adding an entity to a HashSet *before* setting its ID, e.g. when using a MySQL autoincrement column as primary key with Hibernate. Example: Filling a parent folder including some children and then saving them all in one go. A "contains" or "remove" call on the children collection will fail then, as the hash code has changed while the object was in the collection. > >Of course, HashSet states that this is illegal, but how to avoid this? It's especially tricky with m:n associations, e.g. between parents and children. No matter how you load such an object tree, some entities will always get added to a collection in a not fully initialized state, potentially incurring hash code failure when the ID hasn't been set yet. This not only arises with persistence tools like Hibernate but also with remoting tools like Hessian. > >I see various solutions for the problem: > >- Reinitializing the HashSet after the change: This solves the create-then-save issue, but seems like a hack and doesn't address the mutual m:n issue. > >- Use the standard "equals"/"hashCode" implementations: This solves both issues, but object equality is based on object identity then, which isn't suitable e.g. for web applications that might store one entity in the HTTP session and load another freshly, maybe wanting to compare their parent. > >- "equals" based on the primary key, but "hashCode" returning the same value for all instances of a class: This is valid, as "hashCode" has to return the same value if "equals" is true but just *should* return distinct values if not equal. As "hashCode" doesn't depend on the primary key anymore, both issues are solved. > >The latter has the disadvantage that it doesn't care for the performance of hash lookups. Looking up such an entity in a hash table is linear like with an unsorted array. But typically this isn't a big deal, as most collections will just contain a limited number of objects anyway, e.g. assocation collections in an object tree. > >We've currently adopted the latter approach, but I'm not 100% convinced that there isn't a better solution for the problem. What do you think? > > How about having a smart hashCode() function? That is, we do know certain behavioral invariants w/regards to the domain objects we persist (for example with Hibernate). When the object is created new and then saved to the persistent store: - object is created - id is null or zero - object is added to some collection (hashCode will be used) - object is persisted to the db, changing the id - id never changes from then on When the object is loaded from the db: - object is created - id is not null or zero - id never changes from then on - object is added to some collection (hashCode will be used) So, we make a hashCode with some smarts. First time hashcode is called, - if id is null/zero, then hashCode will forever return the same value, which is the same for all class instances. Inefficient, but doesn't cause Sets to barf. - but if is not-null/zero and hashCode has never been called while id was null/zero, then hashCode will retun a value based on the id. This means that loaded entities loaded form the db are treated efficiently. Still not that great in terms of efficiency for new entities, but efficient for old entities. Also, there is still an issue with any collection (ie not HashSet) that uses 'equals', since that is going to change when the id changes. >Juergen > > >DI Jürgen Höller >Senior System Architect >______________________________________ > >werk3ATS - division systementwicklung >part of werk3AT internetmedien oeg > >europaplatz 4 >A - 4020 linz > >t. +43 (0) 732 71 65 29 502 >f. +43 (0) 732 71 65 29 3 >jue...@we... >www.werk3at.com >______________________________________ >werk3ATS - WIR ENTWICKELN ERFOLG > > > >------------------------------------------------------- >This SF.Net email sponsored by: Free pre-built ASP.NET sites including >Data Reports, E-commerce, Portals, and Forums are available now. >Download today and enter to win an XBOX or Visual Studio .NET. >http://aspnet.click-url.com/go/psa00100003ave/direct;at.aspnet_072303_01/01 >_______________________________________________ >Springframework-developer mailing list >Spr...@li... >https://lists.sourceforge.net/lists/listinfo/springframework-developer > > |
|
From: Colin S. <col...@ex...> - 2003-07-29 16:09:41
|
jürgen höller [werk3AT] wrote: >Everyone, > >A rather general problem that I've been wondering about for a while: "equals"/"hashCode" implementation for entities with an externally determined ID. > >I've repeatedly discovered issues with primary-key-based implementations of the both. The root cause is adding an entity to a HashSet *before* setting its ID, e.g. when using a MySQL autoincrement column as primary key with Hibernate. Example: Filling a parent folder including some children and then saving them all in one go. A "contains" or "remove" call on the children collection will fail then, as the hash code has changed while the object was in the collection. > >Of course, HashSet states that this is illegal, but how to avoid this? It's especially tricky with m:n associations, e.g. between parents and children. No matter how you load such an object tree, some entities will always get added to a collection in a not fully initialized state, potentially incurring hash code failure when the ID hasn't been set yet. This not only arises with persistence tools like Hibernate but also with remoting tools like Hessian. > >I see various solutions for the problem: > >- Reinitializing the HashSet after the change: This solves the create-then-save issue, but seems like a hack and doesn't address the mutual m:n issue. > > Can you clarify what you mean when you say it doesn't address the mutual m:n issue? Main issue that I see here is really hibernate. If Hibernate and the collections it manages are happy, then I don't care about some transient collection elsewhere that has the original instance of the object with the empty id field (since I don't consider that the same object any longer anyways). But I think with some (a lot of) code Hibernate could track persistent collections (it already does insert a bytecode modified version of each collection anyways, so has some collection handling), track whether an object can potentially be a member of a collection (which it can do based on the mapping data it has), and then on assigning an ID go through all the candidate collections and try to pull out the old instance if it exists and reinsert it properly. I will say, after this conversation, GUIDs are starting to look pretty good :-) Too bad about legacy tables though... |