Come dovrebbero essere implementati uguali e hashcode quando si utilizza JPA e Hibernate


103

Come dovrebbero essere implementati gli uguali e il codice hash della classe del modello in Hibernate? Quali sono le insidie ​​comuni? L'implementazione predefinita è sufficiente per la maggior parte dei casi? Ha senso utilizzare le chiavi aziendali?

Mi sembra che sia piuttosto difficile farlo funzionare correttamente in ogni situazione, quando vengono presi in considerazione il recupero pigro, la generazione dell'ID, il proxy, ecc.


Vedere anche stackoverflow.com/a/39827962/548473 (implementazione primavera-data-JPA)
Grigory Kislin

Risposte:


74

Hibernate ha una bella e lunga descrizione di quando / come sovrascrivere equals()/ hashCode()nella documentazione

Il succo è che devi preoccuparti solo se la tua entità farà parte di un Seto se scollegherai / collegherai le sue istanze. Quest'ultimo non è così comune. Il primo è solitamente gestito al meglio tramite:

  1. Basando equals()/ hashCode()su una chiave di business - per esempio una combinazione unica di attributi che non sta per cambiare durante oggetto (o, almeno, della sessione) corso della vita.
  2. Se quanto sopra è impossibile, basare equals()/ hashCode()sulla chiave primaria SE è impostata e l'identità dell'oggetto / System.identityHashCode()altrimenti. La parte importante qui è che devi ricaricare il tuo Set dopo che una nuova entità è stata aggiunta ad esso e persiste; altrimenti potresti finire con un comportamento strano (che alla fine si traduce in errori e / o danneggiamento dei dati) perché la tua entità potrebbe essere allocata a un bucket che non corrisponde alla sua corrente hashCode().

1
Quando dici "reload" @ ChssPly76 intendi fare un refresh()? In che modo la tua entità, che obbedisce al Setcontratto, finisce nel bucket sbagliato (supponendo che tu abbia un'implementazione hashcode abbastanza buona).
non sequitor

4
Aggiorna la raccolta o ricarica l'intera entità (proprietario), sì. Per quanto riguarda il bucket sbagliato: a) aggiungi una nuova entità a set, il suo id non è ancora impostato, quindi stai usando identityHashCode che posiziona la tua entità nel bucket n. 1. b) la tua entità (all'interno di set) è persistente, ora ha un id e quindi stai usando hashCode () basato su quell'id. È diverso da quello sopra e avrebbe posizionato la tua entità nel secchio n. 2. Ora, supponendo che tu abbia un riferimento a questa entità altrove, prova a chiamare Set.contains(entity)e tornerai false. Lo stesso vale per get () / put () / etc ...
ChssPly76

Ha senso ma non ho mai usato identityHashCode da solo anche se lo vedo usato nella fonte Hibernate come nei loro ResultTransformers
non sequitor

1
Quando usi Hibernate, potresti anche incappare in questo problema , al quale non ho ancora trovato una soluzione.
Giovanni Botta

@ ChssPly76 A causa delle regole aziendali che determinano se due oggetti sono uguali, dovrò basare i miei metodi uguali / hashcode su proprietà che possono cambiare durante la vita di un oggetto. È davvero un grosso problema? In caso affermativo, come posso aggirarlo?
ubiquibacon

39

Non credo che la risposta accettata sia accurata.

Per rispondere alla domanda originale:

L'implementazione predefinita è sufficiente per la maggior parte dei casi?

La risposta è sì, nella maggior parte dei casi lo è.

Hai solo bisogno di sovrascrivere equals()e hashcode()se l'entità verrà utilizzata in un Set(che è molto comune) E l'entità verrà scollegata e successivamente ricollegata alle sessioni di ibernazione (che è un uso raro di ibernazione).

La risposta accettata indica che i metodi devono essere sovrascritti se una delle due condizioni è vera.


Questo è in linea con la mia osservazione, è tempo di scoprire perché .
Vlastimil Ovčáčík

"Hai solo bisogno di sovrascrivere equals () e hashcode () se l'entità verrà usata in un Set" è completamente sufficiente se alcuni campi identificano un oggetto, quindi non vuoi fare affidamento su Object.equals () per identificare oggetti.
davidxxx

17

La migliore equals/ hashCodeimplementazione è quando si utilizza una chiave aziendale univoca .

La chiave aziendale deve essere coerente in tutte le transizioni di stato dell'entità (transitoria, collegata, scollegata, rimossa), ecco perché non puoi fare affidamento sull'id per l'uguaglianza.

Un'altra opzione è passare all'utilizzo degli identificatori UUID , assegnati dalla logica dell'applicazione. In questo modo, puoi utilizzare l'UUID per equals/ hashCodeperché l'id viene assegnato prima che l'entità venga scaricata.

Puoi anche utilizzare l'identificatore di entità per equalse hashCode, ma ciò richiede di restituire sempre lo stesso hashCodevalore in modo da assicurarti che il valore hashCode dell'entità sia coerente in tutte le transizioni di stato dell'entità. Dai un'occhiata a questo post per ulteriori informazioni su questo argomento .


+1 per l'approccio uuid. Mettilo in un BaseEntitye non pensare mai più a quel problema. Ci vuole un po 'di spazio sul lato db ma quel prezzo è meglio pagare per il comfort :)
Martin Frey

12

Quando un'entità viene caricata tramite caricamento lento, non è un'istanza del tipo di base, ma è un sottotipo generato dinamicamente generato da javassist, quindi un controllo sullo stesso tipo di classe fallirà, quindi non usare:

if (getClass() != that.getClass()) return false;

invece usa:

if (!(otherObject instanceof Unit)) return false;

che è anche una buona pratica, come spiegato in Implementing equals in Java Practices .

per lo stesso motivo, l'accesso diretto ai campi potrebbe non funzionare e restituire null, invece del valore sottostante, quindi non utilizzare il confronto sulle proprietà, ma utilizzare i getter, poiché potrebbero attivare il caricamento dei valori sottostanti.


1
Funziona se stai confrontando oggetti di classi concrete, che non hanno funzionato nella mia situazione. Stavo confrontando oggetti di super classi, nel qual caso questo codice ha funzionato per me: obj1.getClass (). IsInstance (obj2)
Tad

6

Sì, è difficile. Nel mio progetto uguale e hashCode si basano entrambi sull'id dell'oggetto. Il problema di questa soluzione è che nessuno dei due funziona se l'oggetto non è stato ancora reso persistente, poiché l'id è generato dal database. Nel mio caso è tollerabile poiché in quasi tutti i casi gli oggetti vengono persistiti immediatamente. Oltre a questo, funziona alla grande ed è facile da implementare.


Quello che penso che abbiamo fatto è usare l'identità dell'oggetto nel caso in cui l'id non sia stato generato
Kathy Van Stone,

2
il problema qui è che se mantieni l'oggetto, il tuo codice hash cambia. Ciò può avere grandi risultati dannosi se l'oggetto fa già parte di una struttura dati basata su hash. Quindi, se finisci per usare l'identità dell'oggetto, faresti meglio a continuare a usare obj id fino a quando l'oggetto non è completamente liberato (o rimuovere l'oggetto da qualsiasi struttura basata su hash, persistere, quindi aggiungerlo di nuovo). Personalmente, penso che sarebbe meglio non usare id e basare l'hash su proprietà immutabili dell'oggetto.
Kevin Day,

1

Nella documentazione di Hibernate 5.2 si dice che potresti non voler implementare hashCode ed è uguale a tutti - a seconda della tua situazione.

https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#mapping-model-pojo-equalshashcode

In genere, due oggetti caricati dalla stessa sessione saranno uguali se sono uguali nel database (senza implementare hashCode ed è uguale a).

Diventa complicato se stai usando due o più sessioni. In questo caso, l'uguaglianza di due oggetti dipende dall'implementazione del metodo uguale.

Inoltre, ti troverai nei guai se il tuo metodo uguale sta confrontando ID che vengono generati solo durante la persistenza di un oggetto per la prima volta. Potrebbero non esserci ancora quando viene chiamato uguale.


0

C'è un articolo molto carino qui: https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

Citando una riga importante dell'articolo:

Si consiglia di implementare equals () e hashCode () utilizzando l'uguaglianza della chiave Business. Uguaglianza della chiave aziendale 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):

In parole povere

public class Cat {

...
public boolean equals(Object other) {
    //Basic test / class cast
    return this.catId==other.catId;
}

public int hashCode() {
    int result;

    return 3*this.catId; //any primenumber 
}

}

0

Se ti è capitato di ignorare equals, assicurati di rispettare i suoi contratti: -

  • SIMMETRIA
  • RIFLETTENTE
  • TRANSITIVO
  • COERENTE
  • NON NULL

E sovrascrivi hashCode, poiché il contratto si basa equalssull'implementazione.

Joshua Bloch (designer del framework Collection) ha fortemente esortato a seguire queste regole.

  • elemento 9: sovrascrivi sempre hashCode quando esegui l'override di uguale

Ci sono gravi effetti indesiderati quando non segui questi contratti. Ad esempio, List#contains(Object o)potrebbe restituire un booleanvalore errato in quanto il contratto generale non è stato rispettato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.