Comment implémenter equals et hashcode lors de l'utilisation de JPA et Hibernate

103

Comment les equals et hashcode de la classe de modèle doivent-ils être implémentés dans Hibernate? Quels sont les écueils courants? L'implémentation par défaut est-elle suffisante pour la plupart des cas? Y a-t-il un sens à utiliser des clés professionnelles?

Il me semble qu'il est assez difficile de faire fonctionner correctement dans toutes les situations, lorsque la récupération paresseuse, la génération d'identifiants, le proxy, etc. sont pris en compte.

Egaga
la source
Voir aussi stackoverflow.com/a/39827962/548473 (implémentation spring-data-jpa)
Grigory Kislin

Réponses:

74

Hibernate a une belle et longue description de quand / comment remplacer equals()/ hashCode()dans la documentation

L'essentiel est que vous ne devez vous en soucier que si votre entité fera partie d'un Setou si vous allez détacher / attacher ses instances. Ce dernier n'est pas si courant. Le premier est généralement mieux géré via:

  1. Baser equals()/ hashCode()sur une clé métier - par exemple une combinaison unique d'attributs qui ne changera pas pendant la durée de vie de l'objet (ou, au moins, de la session).
  2. Si ce qui précède est impossible, base equals()/ hashCode()sur la clé primaire SI elle est définie et l'identité de l'objet / System.identityHashCode()sinon. La partie importante ici est que vous devez recharger votre ensemble une fois qu'une nouvelle entité a été ajoutée et persistante; Sinon, vous risquez de vous retrouver avec un comportement étrange (entraînant finalement des erreurs et / ou une corruption de données) car votre entité peut être allouée à un compartiment ne correspondant pas à son courant hashCode().
ChssPly76
la source
1
Quand vous dites "recharger" @ ChssPly76, vous voulez dire faire un refresh()? Comment votre entité, qui obéit au Setcontrat, se retrouve-t-elle dans le mauvais compartiment (en supposant que vous ayez une implémentation de hashcode suffisamment bonne).
non sequitor
4
Actualisez la collection ou rechargez l'entité (propriétaire) entière, oui. En ce qui concerne le mauvais compartiment: a) vous ajoutez une nouvelle entité à définir, son identifiant n'est pas encore défini, vous utilisez donc identityHashCode qui place votre entité dans le compartiment n ° 1. b) votre entité (dans l'ensemble) est persistante, elle a maintenant un identifiant et vous utilisez donc hashCode () basé sur cet identifiant. C'est différent de ci-dessus et aurait placé votre entité dans le bucket # 2. Maintenant, en supposant que vous détenez une référence à cette entité ailleurs, essayez d'appeler Set.contains(entity)et vous reviendrez false. Il en va de même pour get () / put () / etc ...
ChssPly76
Cela a du sens, mais je n'ai jamais utilisé identityHashCode moi-même bien que je le vois utilisé dans la source Hibernate comme dans leurs ResultTransformers
non séquitor
1
Lorsque vous utilisez Hibernate, vous pouvez également rencontrer ce problème , auquel je n'ai toujours pas trouvé de solution.
Giovanni Botta
@ ChssPly76 En raison des règles métier qui déterminent si deux objets sont égaux, je devrai baser mes méthodes equals / hashcode sur des propriétés qui peuvent changer pendant la durée de vie d'un objet. Est-ce vraiment un gros problème? Si oui, comment puis-je le contourner?
ubiquibacon
39

Je ne pense pas que la réponse acceptée soit exacte.

Pour répondre à la question initiale:

L'implémentation par défaut est-elle suffisante pour la plupart des cas?

La réponse est oui, dans la plupart des cas.

Vous avez seulement besoin de remplacer equals()et hashcode()si l'entité sera utilisée dans un Set(ce qui est très courant) ET l'entité sera détachée des sessions de mise en veille prolongée, puis rattachée à celles-ci (ce qui est une utilisation inhabituelle de la mise en veille prolongée).

La réponse acceptée indique que les méthodes doivent être remplacées si l' une des conditions est vraie.

Phil
la source
Cela correspond à mon observation, il est temps de savoir pourquoi .
Vlastimil Ovčáčík
"Il suffit de remplacer equals () et hashcode () si l'entité sera utilisée dans un ensemble" est complètement suffisant si certains champs identifient un objet, et vous ne voulez donc pas vous fier à Object.equals () pour identifier objets.
davidxxx
17

La meilleure equals/ hashCodeimplémentation est lorsque vous utilisez une clé métier unique .

La clé métier doit être cohérente dans toutes les transitions d'état d'entité (transitoire, attachée, détachée, supprimée), c'est pourquoi vous ne pouvez pas compter sur l'id pour l'égalité.

Une autre option consiste à passer à l'utilisation des identificateurs UUID , attribués par la logique d'application. De cette façon, vous pouvez utiliser l'UUID pour le equals/ hashCodecar l'id est attribué avant que l'entité ne soit vidée.

Vous pouvez même utiliser l'identificateur d'entité pour equalset hashCode, mais cela vous oblige à toujours renvoyer la même hashCodevaleur afin de vous assurer que la valeur de hachage d'entité est cohérente dans toutes les transitions d'état d'entité. Consultez cet article pour en savoir plus sur ce sujet .

Vlad Mihalcea
la source
+1 pour l'approche uuid. Mettez cela dans un BaseEntityet ne pensez plus jamais à ce problème. Cela prend un peu de place côté db mais ce prix-là, vous feriez mieux de payer pour le confort :)
Martin Frey
12

Lorsqu'une entité est chargée via le chargement paresseux, ce n'est pas une instance du type de base, mais un sous-type généré dynamiquement généré par javassist, donc une vérification sur le même type de classe échouera, donc n'utilisez pas:

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

utilisez plutôt:

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

ce qui est également une bonne pratique, comme expliqué dans Implémentation d'égaux dans les pratiques Java .

pour la même raison, accéder directement aux champs peut ne pas fonctionner et retourner null, au lieu de la valeur sous-jacente, donc n'utilisez pas de comparaison sur les propriétés, mais utilisez les getters, car ils pourraient déclencher le chargement des valeurs sous-jacentes.

Stivlo
la source
1
Cela fonctionne si vous comparez des objets de classes concrètes, ce qui n'a pas fonctionné dans ma situation. Je comparais des objets de super classes, auquel cas ce code fonctionnait pour moi: obj1.getClass (). IsInstance (obj2)
Tad
6

Ouais, c'est dur. Dans mon projet, equals et hashCode reposent tous deux sur l'id de l'objet. Le problème de cette solution est qu'aucune d'elles ne fonctionne si l'objet n'a pas encore été persistant, car l'identifiant est généré par la base de données. Dans mon cas, c'est tolérable car dans presque tous les cas, les objets sont conservés immédiatement. En dehors de cela, cela fonctionne très bien et est facile à mettre en œuvre.

Carlos
la source
Ce que je pense que nous avons fait, c'est d'utiliser l'identité d'objet dans le cas où l'identifiant n'a pas été généré
Kathy Van Stone
2
le problème ici est que si vous persistez l'objet, votre hashcode change. Cela peut avoir de gros résultats néfastes si l'objet fait déjà partie d'une structure de données basée sur le hachage. Donc, si vous finissez par utiliser l'identité d'objet, vous feriez mieux de continuer à utiliser obj id jusqu'à ce que l'objet soit complètement libéré (ou supprimez l'objet de toute structure basée sur le hachage, persistez, puis rajoutez-le). Personnellement, je pense qu'il serait préférable de ne pas utiliser id et de baser le hachage sur les propriétés immuables de l'objet.
Kevin Day
1

Dans la documentation d'Hibernate 5.2, il est dit que vous ne voudrez peut-être pas du tout implémenter hashCode et equals - en fonction de votre situation.

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

Généralement, deux objets chargés à partir de la même session seront égaux s'ils sont égaux dans la base de données (sans implémenter hashCode et equals).

Cela se complique si vous utilisez deux sessions ou plus. Dans ce cas, l'égalité de deux objets dépend de votre implémentation de méthode equals.

De plus, vous aurez des problèmes si votre méthode equals compare des ID qui ne sont générés que lors de la persistance d'un objet pour la première fois. Ils ne sont peut-être pas encore là quand égaux est appelé.

Nina
la source
0

Il y a un très bel article ici: https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

Citant une ligne importante de l'article:

Nous vous recommandons d'implémenter equals () et hashCode () en utilisant l'égalité des clés métier. L'égalité de clé métier signifie que la méthode equals () compare uniquement les propriétés qui forment la clé métier, une clé qui identifierait notre instance dans le monde réel (une clé candidate naturelle):

En termes simples

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 
}

}
Ravi Shekhar
la source
0

S'il vous arrive de passer outre equals, assurez-vous de respecter ses contrats: -

  • SYMÉTRIE
  • RÉFLÉCHISSANT
  • TRANSITIF
  • COHÉRENT
  • NON NULL

Et passer outre hashCode, car son contrat repose surequals mise œuvre.

Joshua Bloch (concepteur du cadre de collection) a vivement recommandé que ces règles soient respectées.

  • élément 9: remplacez toujours hashCode lorsque vous remplacez égal à

Il y a un effet involontaire grave lorsque vous ne suivez pas ces contrats. Par exemple, il List#contains(Object o)peut renvoyer une booleanvaleur erronée car le contrat général n'est pas respecté.

Awan Biru
la source