Tengo un problema con hashCode()
que se delega en objetos no inicializados utilizando hibernate.Delegar función hash a delegados no inicializados en causas de hibernación cambiar hashCode
Mi modelo de datos se ve de la siguiente manera (el siguiente código es muy podada hacer hincapié en el problema y por lo tanto rompe, no se replican!):
class Compound {
@FetchType.EAGER
Set<Part> parts = new HashSet<Part>();
String someUniqueName;
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getSomeUniqueName() == null) ? 0 : getSomeUniqueName().hashCode());
return result;
}
}
class Part {
Compound compound;
String someUniqueName;
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getCompound() == null) ? 0 : getCompound().hashCode());
result = prime * result + ((getSomeUniqueName() == null) ? 0 : getSomeUniqueName().hashCode());
return result;
}
}
Tenga en cuenta que la aplicación de hashCode()
sigue cuidadosamente los consejos como se da in the hibernate documentation.
Ahora, si cargo un objeto del tipo Compound
, carga ansiosamente el HasSet
con las piezas. Esto llama al hashCode()
en las partes, que a su vez llama al hashCode()
en el compuesto. Sin embargo, el problema es que en este punto, no todos los valores que se consideran para crear el hashCode del compuesto están disponibles. Por lo tanto, el hashCode de las piezas cambia después de que se completa la inicialización, frenando así el contrato del HashSet
y dando lugar a todo tipo de errores difíciles de localizar (como, por ejemplo, tener el mismo objeto en las piezas configuradas dos veces).
Así que mi pregunta es: ¿Cuál es la solución más simple para evitar este problema (me gustaría evitar escribir clases para la carga/inicialización personalizada)? ¿Hago algo mal aquí por completo?
Editar: ¿Falta algo aquí? Esto parece ser un problema básico, ¿por qué no encuentro nada al respecto en ningún lado?
En lugar de utilizar el identificador de base de datos para la comparación de igualdad, se debe utilizar un conjunto de propiedades de los iguales() que identifican sus objetos individuales. [...] No es necesario utilizar el identificador persistente, por lo que llamado "clave comercial" es mucho mejor. Es una clave natural, pero este tiempo no hay nada de malo en su uso! (article from hibernate)
Y
Se recomienda que implemente equals() y hashCode() utilizando negocios igualdad de clave. La igualdad de clave comercial significa que el método igual() compara solo las propiedades que forman la clave comercial. Es una clave que identificaría nuestra instancia en el mundo real (una clave candidata natural ). (hibernate documentation)
Editar: Este es el seguimiento de la pila cuando la carga pasa (en caso de que esto ayuda). En ese momento, el atributo someUniqueName
es nulo y, por lo tanto, hashCode se calcula erróneamente.
Compound.getSomeUniqueName() line: 263
Compound.hashCode() line: 286
Part.hashCode() line: 123
HashMap<K,V>.put(K, V) line: 372
HashSet<E>.add(E) line: 200
HashSet<E>(AbstractCollection<E>).addAll(Collection<? extends E>) line: 305
PersistentSet.endRead() line: 352
CollectionLoadContext.endLoadingCollection(LoadingCollectionEntry, CollectionPersister) line: 261
CollectionLoadContext.endLoadingCollections(CollectionPersister, List) line: 246
CollectionLoadContext.endLoadingCollections(CollectionPersister) line: 219
EntityLoader(Loader).endCollectionLoad(Object, SessionImplementor, CollectionPersister) line: 1005
EntityLoader(Loader).initializeEntitiesAndCollections(List, Object, SessionImplementor, boolean) line: 993
EntityLoader(Loader).doQuery(SessionImplementor, QueryParameters, boolean) line: 857
EntityLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor, QueryParameters, boolean) line: 274
EntityLoader(Loader).loadEntity(SessionImplementor, Object, Type, Object, String, Serializable, EntityPersister, LockOptions) line: 2037
EntityLoader(AbstractEntityLoader).load(SessionImplementor, Object, Object, Serializable, LockOptions) line: 86
EntityLoader(AbstractEntityLoader).load(Serializable, Object, SessionImplementor, LockOptions) line: 76
SingleTableEntityPersister(AbstractEntityPersister).load(Serializable, Object, LockOptions, SessionImplementor) line: 3293
DefaultLoadEventListener.loadFromDatasource(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 496
DefaultLoadEventListener.doLoad(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 477
DefaultLoadEventListener.load(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 227
DefaultLoadEventListener.proxyOrLoad(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 269
DefaultLoadEventListener.onLoad(LoadEvent, LoadEventListener$LoadType) line: 152
SessionImpl.fireLoad(LoadEvent, LoadEventListener$LoadType) line: 1090
SessionImpl.internalLoad(String, Serializable, boolean, boolean) line: 1038
ManyToOneType(EntityType).resolveIdentifier(Serializable, SessionImplementor) line: 630
ManyToOneType(EntityType).resolve(Object, SessionImplementor, Object) line: 438
TwoPhaseLoad.initializeEntity(Object, boolean, SessionImplementor, PreLoadEvent, PostLoadEvent) line: 139
QueryLoader(Loader).initializeEntitiesAndCollections(List, Object, SessionImplementor, boolean) line: 982
QueryLoader(Loader).doQuery(SessionImplementor, QueryParameters, boolean) line: 857
QueryLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor, QueryParameters, boolean) line: 274
QueryLoader(Loader).doList(SessionImplementor, QueryParameters) line: 2542
QueryLoader(Loader).listIgnoreQueryCache(SessionImplementor, QueryParameters) line: 2276
QueryLoader(Loader).list(SessionImplementor, QueryParameters, Set, Type[]) line: 2271
QueryLoader.list(SessionImplementor, QueryParameters) line: 459
QueryTranslatorImpl.list(SessionImplementor, QueryParameters) line: 365
HQLQueryPlan.performList(QueryParameters, SessionImplementor) line: 196
SessionImpl.list(String, QueryParameters) line: 1268
QueryImpl.list() line: 102
<my code where the query is executed>
* (no es una respuesta a su pregunta) * ... * "Tenga en cuenta que la implementación de hashCode sigue atentamente los consejos que figuran en la documentación de hibernación" * [sic]. El mismo artículo que enlazas compone los dos códigos hash multiplicando por un número primo antes de agregar, que es mucho más común que simplemente sumar. Te recomiendo que realmente sigas ese consejo y multipliques tu * compuesto * por un número primo antes de hacer la suma. O eso o hacer XOR los dos hashes. Pero simplemente agregarlos típicamente aumenta los riesgos de colisiones. – TacticalCoder
Gracias por la recomendación. El código real lo hace. Sin embargo, esto no ayuda a ilustrar el problema, y es por eso que lo eliminé. Como se explica en la nota anterior: "* el siguiente código es muy útil para enfatizar el problema *". – roesslerj
No es la solución más ideal, pero podría resolver su problema. Para las diversas colecciones de hash, la primera llamada es a hashCode * como optimización *. Entonces se llama a igual() si los códigos hash son iguales. Podría hacer que hashCode() siempre devuelva 1, suponiendo que su método equals() no sufre el mismo problema (p. Ej., Un valor no inicializado de compound.someUniqueValue). ¿Cómo se ve el método equals()? – Saish