Ho un problema con hashCode()
che delega a oggetti non inizializzati utilizzando la modalità di sospensione.Delega di funzione hash a delegati non inizializzati in cause di ibernazione che modificano hashCode
Il mio modello di dati appare come segue (il seguente codice è altamente potata a sottolineare il problema e quindi rotto, non replicare!):
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;
}
}
Si prega di notare che l'attuazione di hashCode()
segue accuratamente il consiglio come dato in the hibernate documentation.
Ora se carico un oggetto di tipo Compound
, carica con impazienza lo HasSet
con le parti. Questo chiama lo hashCode()
sulle parti, che a sua volta chiama lo hashCode()
sul composto. Tuttavia, il problema è che a questo punto non sono ancora disponibili tutti i valori considerati per la creazione del codice hash del composto. Pertanto, il codice hash delle parti cambia dopo l'inizializzazione è stata completata, frenando così il contratto del HashSet
e portando a tutti i tipi di errori difficili da rintracciare (come ad esempio avere lo stesso oggetto nelle parti impostato due volte).
Quindi la mia domanda è: qual è la soluzione più semplice per evitare questo problema (vorrei evitare di scrivere classi per il caricamento/inizializzazione personalizzato)? Devo fare qualcosa di sbagliato qui interamente?
Modifica: Mi manca qualcosa qui? Questo sembra essere un problema di base, perché non trovo nulla al riguardo da nessuna parte?
Invece di utilizzare l'identificatore di database per il confronto di uguaglianza, è necessario utilizzare una serie di proprietà per equals() che identificano i tuoi singoli oggetti. [...] Non c'è bisogno di usare l'identificatore persistente, il così chiamato "business key" è molto meglio. È una chiave naturale, ma questo tempo è non c'è niente di sbagliato nell'usarlo! (article from hibernate)
E
Si consiglia di implementare equals() e hashCode() utilizzando business uguaglianza chiave. Equità chiave business significa che il metodo equals() confronta solo le proprietà che formano la chiave aziendale. È una chiave che identificherebbe la nostra istanza nel mondo reale (una chiave candidata naturale ). (hibernate documentation)
Edit: Questa è la traccia dello stack quando il caricamento avviene (nel caso in cui questo aiuta). In quel momento, l'attributo someUniqueName
è nullo e quindi l'hashCode viene calcolato erroneamente.
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>
* (non è una risposta alla tua domanda) * ... * "Si noti che l'implementazione di hashCode segue completamente il consiglio dato nella documentazione di ibernazione" * [sic]. L'articolo che si collega a comporre i due codici hash moltiplicando per un numero primo prima di aggiungere, che è molto più comune di una semplice somma. Ti consiglio di seguire * veramente * attentamente quel consiglio e di moltiplicare il tuo * composto * con un numero primo prima di fare l'aggiunta. O quello o fai XOR i due hash. Ma aggiungerli tipicamente aumenta i rischi di collisioni. – TacticalCoder
Grazie per la raccomandazione. Il codice effettivo lo fa. Tuttavia questo non aiuta esattamente ad illustrare il problema, ed è per questo che l'ho rimosso. Come spiegato nella nota precedente: "* il seguente codice è fortemente potato per sottolineare il problema *". – roesslerj
Non è la soluzione più ideale, ma potrebbe risolvere il tuo problema. Per le varie raccolte di hash, la prima chiamata è per hashCode * come ottimizzazione *. Quindi equals() viene chiamato se i codici hash sono uguali. Potresti avere hashCode() restituire sempre 1, assumendo che il tuo metodo equals() non soffra dello stesso problema (ad esempio, un valore non inizializzato di compound.someUniqueValue). Come si presenta il metodo equals()? – Saish