Comment les équivalents et le hashcode de la classe de modèle doivent-ils être implémentés 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 de gestion?
Il me semble qu’il est assez difficile de fonctionner correctement dans toutes les situations, lorsqu’on prend en compte la récupération paresseuse, la génération d’identificateurs, le proxy, etc.
Hibernate a une longue et belle description de quand/comment remplacer equals()
hashCode()
dans documentation
L’essentiel est que vous n’ayez à vous en préoccuper que si votre entité fera partie de 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:
equals()
/hashCode()
sur une clé d'entreprise - 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).equals()
/hashCode()
sur la clé primaire SI c'est défini et identité d'objet/System.identityHashCode()
sinon. La partie _/important ici est que vous devez recharger votre ensemble après que la nouvelle entité y ait été ajoutée et persistée; sinon, vous risquez de vous retrouver avec un comportement étrange (entraînant des erreurs et/ou une corruption des données) car votre entité peut être affectée à un compartiment ne correspondant pas à sa hashCode()
actuelle.Je ne pense pas que la réponse acceptée est 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.
Vous devez uniquement remplacer equals()
et hashcode()
si l’entité sera utilisée dans une Set
(ce qui est très courant)ETl’entité sera détachée puis réattachée à des sessions hibernate (qui est une utilisation inhabituelle d'hibernation).
La réponse acceptée indique que les méthodes doivent être remplacées si la condition soit est vraie.
Lorsqu'une entité est chargée via un chargement différé, il ne s'agit pas d'une instance du type de base, mais d'un sous-type généré dynamiquement généré par javassist. Par conséquent, la vérification du même type de classe échouera. 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'égal à égal dans les pratiques Java .
pour la même raison, l'accès direct aux champs peut ne pas fonctionner et renvoyer null au lieu de la valeur sous-jacente; n'utilisez donc pas de comparaison sur les propriétés, mais utilisez les options d'accès, car elles pourraient déclencher le chargement des valeurs sous-jacentes.
La meilleure implémentation de equals
hashCode
consiste à utiliser une _/clé commerciale unique .
La clé de gestion doit être cohérente pour toutes les transitions d'état entity (transitoire, attaché, détaché, supprimé), c'est pourquoi vous ne pouvez pas compter sur id pour l'égalité.
Une autre option consiste à utiliser les identificateurs UUID , affectés par la logique d'application. De cette façon, vous pouvez utiliser l'UUID pour equals
hashCode
, car l'ID est attribué avant le vidage de l'entité.
Vous pouvez même utiliser l'identifiant d'entité pour 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 dans toutes les transitions d'état de l'entité. Consultez _/cet article pour plus d'informations sur ce sujet .
Oui, c'est dur. Dans mon projet equals et hashCode reposent tous les deux sur l'id de l'objet. Le problème de cette solution est qu’aucun d’eux ne fonctionne si l’objet n’a pas encore été conservé, l’id étant généré par la base de données. Dans mon cas, c'est tolérable, car dans presque tous les cas, les objets persistent immédiatement. En dehors de cela, cela fonctionne très bien et est facile à mettre en œuvre.
Si vous écrasez equals
, assurez-vous de respecter ses contrats: -
Et annulez hashCode
, car son contrat repose sur la mise en œuvre de equals
.
Joshua Bloch (concepteur du cadre Collection) a vivement recommandé que ces règles soient suivies.
Si vous ne respectez pas ces contrats, il y a un grave effet inattendu. Par exemple, List.contains(Object o)
peut renvoyer une valeur boolean
erronée car le contrat général n'est pas rempli.
Dans la documentation d'Hibernate 5.2, il est indiqué que vous ne souhaitez peut-être pas implémenter hashCode et equals - en fonction de votre situation.
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émentation de hashCode et d'égal à égal).
Cela devient compliqué si vous utilisez deux sessions ou plus. Dans ce cas, l'égalité de deux objets dépend de votre implémentation de la méthode equals.
De plus, vous aurez des problèmes si votre méthode equals compare les identifiants générés uniquement lors de la persistance d'un objet pour la première fois. Ils pourraient ne pas être encore là quand égaux est appelé.
Il existe 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 () à l'aide de la clé Business égalité. L'égalité de clé commerciale signifie que la méthode equals () compare uniquement les propriétés qui constituent la clé métier, une clé qui identifierait notre instance dans le monde réel (un candidat naturel clé):
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
}
}