Comment les égaux et le hashcode doivent-ils être mis en œuvre lors de l’utilisation de JPA et Hibernate?

Comment les classes de modèle doivent-elles être égales et le hashcode doit-il être implémenté dans Hibernate? Quels sont les pièges courants? L’implémentation par défaut est-elle suffisante dans la plupart des cas? Est-il judicieux d’utiliser des clés professionnelles?

Il me semble que c’est assez difficile de travailler correctement dans toutes les situations, lorsque la récupération paresseuse, la génération d’identifiants, le proxy, etc. sont pris en compte.

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

L’essentiel est que vous ne devez vous en préoccuper que si votre entité fera partie d’un Set ou si vous allez détacher / attacher ses instances. Ce dernier n’est pas si commun. 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’atsortingbuts qui ne changera pas pendant la durée de vie d’un object (ou du moins d’une 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’object / System.identityHashCode() cas contraire. La partie importante ici est que vous devez recharger votre Set après avoir ajouté une nouvelle entité et persisté; sinon, vous risquez de vous retrouver avec un comportement étrange (entraînant finalement des erreurs et / ou une corruption des données) car votre entité peut être affectée à un compartiment ne correspondant pas à son hashCode() actuel.

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 dans la plupart des cas?

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

Il suffit 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 d’hibernation (ce qui est un usage peu courant) d’hibernation).

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

Lorsqu’une entité est chargée via un chargement différé, ce n’est pas une instance du type de base, mais un sous-type généré dynamicment 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é sur la mise en œuvre d’égal à égal dans les pratiques Java .

pour la même raison, accéder directement aux champs peut ne pas fonctionner et renvoyer 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.

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

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

Une autre option consiste à utiliser des identificateurs UUID , atsortingbués par la logique de l’application. De cette façon, vous pouvez utiliser l’UUID pour le hashCode equals / hashCode car l’ID est atsortingbué avant que l’entité soit vidée.

Vous pouvez même utiliser l’identificateur d’entité pour les equals et hashCode , mais cela vous oblige à toujours renvoyer la même valeur hashCode afin de vous assurer que la valeur hashCode de l’entité est cohérente pour toutes les transitions d’état d’entité. Consultez ce post pour plus d’informations sur ce sujet .

Oui, c’est dur. Dans mon projet, égal et hashCode s’appuient tous deux sur l’id de l’object. Le problème de cette solution est que ni l’un ni l’autre ne fonctionne si l’object n’a pas encore été conservé, car l’identifiant est généré par la firebase database. Dans mon cas, c’est tolérable car dans presque tous les cas, les objects sont conservés immédiatement. En dehors de cela, cela fonctionne très bien et est facile à mettre en œuvre.

Si vous avez surpassé les equals , assurez-vous de respecter ses contrats: –

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

Et remplacez hashCode , car son contrat repose sur une implémentation equals .

Joshua Bloch (concepteur du framework Collection) a vivement recommandé ces règles.

  • item 9: Toujours surpasser le hashCode quand vous remplacez égal à

Il y a un effet involontaire grave lorsque vous ne suivez pas ces contrats. Par exemple, List.contains(Object o) peut renvoyer une valeur boolean incorrecte car le contrat général n’est pas rempli.

Dans la documentation de Hibernate 5.2, il est dit que vous ne souhaitez peut-être pas implémenter hashCode et que vous êtes égal à tous, selon votre situation.

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

En général, deux objects chargés à partir de la même session seront égaux s’ils sont égaux dans la firebase database (sans implémenter hashCode et égal à).

Cela devient compliqué si vous utilisez deux sessions ou plus. Dans ce cas, l’égalité de deux objects dépend de l’implémentation de votre méthode égale.

De plus, vous rencontrerez des problèmes si votre méthode égale est la comparaison des identifiants uniquement générés lors de la première persistance d’un object. Ils ne sont peut-être pas encore là quand ils sont appelés à égalité.

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é des clés métier signifie que la méthode equals () ne compare que les propriétés qui constituent la clé métier, une clé permettant d’identifier 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 } }