web-dev-qa-db-fra.com

Entities est égal à (), hashCode () et toString (). Comment les implémenter correctement?

J'implémente equals(), hashCode() et toString() de mes entités en utilisant tous les champs disponibles dans le bean.

Je reçois une exception Lazy init Exception sur le frontend lorsque j'essaie de comparer l'égalité ou que j'imprime l'état obj. En effet, certaines listes de l'entité peuvent être initialisées paresseuses.

Je me demande quelle est la bonne façon d'implémenter equals() et toString() sur un objet entité.

30
spike07

equals() et hashCode() doivent être implémentés à l’aide de business key - c’est-à-dire un ensemble de propriétés qui identifient de manière unique l’objet, mais ne sont pas son ID généré automatiquement.

dans toString(), vous pouvez mettre toute information intéressante - par exemple, tous les champs.

Utilisez votre IDE (Eclipse, NetBeans, IntelliJ) pour générer tout cela pour vous.

Afin d'éviter LazyInitializationException, que vous utilisiez la fonction equals() ou votre vue (jsp), vous pouvez utiliser OpenSessionInView .

16
Bozho

Lorsque vous implémentez les méthodes equals et hashCode pour les objets Hibernate, il est important de: 

  1. Utilisez des accesseurs au lieu d'accéder directement aux propriétés de la classe. 
  2. Pas directement comparer les classes d'objets, mais utilisez plutôt instanceof

Plus d'information:

Stackoverflow: écraser-et-hashcode-in-Java

Documentation Hibernate: Equals et HashCode

Edit: les mêmes règles concernant l’absence d’accès direct aux propriétés de la classe s’appliquent également à la méthode toString - seul l’utilisation des getters garantit le renvoi des informations réellement contenues dans la classe.

10
simon
  1. Si deux objets sont égaux, ils doivent avoir le même hashcode.
  2. la méthode equals (), par défaut, vérifie si deux références font référence à la même instance en mémoire sur le tas Java

Vous pouvez compter sur l’identificateur d’entité pour comparer votre entité à l’aide d’égal à égal

public boolean equals(Object o) {
    if(o == null)
        return false;

   Account account = (Account) o;
   if(!(getId().equals(account.getId())))
       return false;

   return true;
}

Mais que se passe-t-il quand vous avez une entité non persistante? Cela ne fonctionnera pas car son identifiant n'a pas été attribué. 

Voyons maintenant ce que Java Persistence with Hibernate Book en parle

Une clé métier est une propriété, ou une combinaison de propriétés, unique pour chaque instance ayant la même identité de base de données .

Alors 

C’est la clé naturelle que vous utiliseriez si vous n’utilisiez pas une clé primaire de substitution à la place.

Supposons donc que vous avez une entité utilisateur et que ses clés naturelles sont firstName et lastName (au moins, ses prénom et nom ne changent pas souvent). Donc, il serait mis en œuvre comme

public boolean equals(Object o) {
    if(o == null)
        return false;

    if(!(o instanceof User))
        return false;

    // Our natural key has not been filled
    // So we must return false;
    if(getFirstName() == null && getLastName() == null)
        return false;

    User user = (User) o;
    if(!(getFirstName().equals(user.getFirstName())))
        return false;

    if(!(getLastName().equals(user.getLastName())))
        return false;

   return true;
}

// default implementation provided by NetBeans
public int hashcode() {
    int hash = 3;

    hash = 47 * hash + ((getFirstName() != null) ? getFirstName().hashcode() : 0)
    hash = 47 * hash + ((getLastName() != null) ? getLastName().hashcode() : 0)

    retrun hash;
}

Ça fonctionne bien! J'utilise même avec des objets Mock tels que des référentiels, des services, etc.

Et à propos de la méthode toString (), comme l'a dit @Bozho, vous pouvez mettre n'importe quelle information intéressante. Mais rappelez-vous que certains frameworks Web, tels que Wicket et Vaadin, utilisent cette méthode pour afficher ses valeurs.

7
Arthur Ronald

S'il vous est arrivé de remplacer equals () sur des entités Hibernate, assurez-vous de remplir ses contrats: - 

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

Et annulez hashCode, car son contrat repose sur la mise en œuvre de equals.

Joshua Bloch (concepteur du framework Collection) insiste fortement sur cette règle

  • élément 9: Toujours écraser hashCode lorsque vous écrasez est égal à

Il y a un grave effet inattendu lorsque vous ne respectez pas son contrat. Par exemple, List.contains(Object o) peut renvoyer une valeur boolean erronée car le contrat général n'est pas rempli.

0
Awan Biru

Outre ce que les autres ont dit, je pense également qu'un objet Hibernate doit toujours être attaché à la session pour récupérer des informations paresseuses. Sans connexion à la base de données, ces listes ne peuvent pas être chargées :)

0
extraneon

C'est probablement la meilleure et la plus simple des manières de le faire:

public String toString() {
    return "userId: " + this.userId + ", firstName: " + this.firstName + ", lastName: " + this.lastName + ", dir: " + this.dir + ", unit: " + this.unit + ", contractExpiryDate: " + this.contractExpiryDate + ", email: " + this.email + ", employeeNumber: " + this.employeeNumber + ", employeeType: " + this.employeeType + ", phone: " + this.phone + ", officeName: " + this.officeName + ", title: " + this.title + ", userType: " + this.userType;
}

public boolean equals(Object obj) {
[...]
return (toString().equals(other.toString()));
}

public int hashCode() {
return toString().hashCode();
}
0
doofus

Nous implémentons equals () et hashCode () dans notre super classe. Cela fonctionne parfaitement, en particulier dans les cartes et les listes, etc. Cela a dû se faire car nous faisons beaucoup de persistance transitive.

équivaut à():

/**
 * Compare two entity objects, following hibernate semantics for equality. Here we assume that
 * new objects are always different unless they are the same object. If an object is loaded from
 * the database it has a valid id and therefore we can check against object ids.
 *
 * @see com.dolby.persist.bean.EntityObject#equals(Java.lang.Object)
 */
@SuppressWarnings("unchecked")
@Override
public final boolean equals(final Object object) {
    if (this == object) return true;
    if (object == null || this.getClass() != object.getClass()) return false;
    final AbstractModelObject<ID> other = (AbstractModelObject<ID>) object;
    if (this.getId() == null || other.getId() == null) return false;
    return this.getId().equals(other.getId());
}

hashCode ():

/**
 * Returns an enttiy objects hashcode.
 * <p>
 * What we are doing here is ensuring that once a hashcode value is used, it never changes for
 * this object. This allows us to use object identity for new objects and not run into the
 * problems.
 * </p>
 * <p>
 * In fact the only case where this is a problem is when we save a new object, keep it around
 * after we close the session, load a new instance of the object in a new session and then
 * compare them.
 * </p>
 * <p>
 * in this case we get A==B but a.hashcode != b.hashcode
 * </p>
 * <p>
 * This will work in all other scenarios and don't lead to broken implementations when the
 * propety of the object are edited. The whole point in generating synthetic primary keys in the
 * first place is to avoid having a primary key which is dependant on an object property and
 * which therefore may change during the life time of the object.
 * </p>
 *
 * @see Java.lang.Object#hashCode()
 */
@Override
public final synchronized int hashCode() {
    if (this.hashcodeValue == null) {
        if (this.getId() == null) {
            this.hashcodeValue = new Integer(super.hashCode());
        }
        else {
            final int generateHashCode = this.generateHashCode(this.getId());
            this.hashcodeValue = new Integer(generateHashCode);
        }
    }
    return this.hashcodeValue.intValue();
}
0
Kango_V
  1. Si vous avez une clé business , vous devriez l’utiliser pour equals/hashCode.
  2. Si vous ne possédez pas de clé d'entreprise, vous ne devez pas la laisser avec les implémentations par défaut Object égal à et hashCode, car cela ne fonctionne pas après merge et entité.
  3. Vous pouvez utiliser l'identifiant d'entité comme suggéré dans cet article . Le seul problème est que vous devez utiliser une implémentation hashCode qui renvoie toujours la même valeur, comme ceci:

    @Entity
    public class Book implements Identifiable<Long> {
    
        @Id
        @GeneratedValue
        private Long id;
    
        private String title;
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Book)) return false;
            Book book = (Book) o;
            return getId() != null && 
               Objects.equals(getId(), book.getId());
        }
    
        @Override
        public int hashCode() {
            return 31;
        }
    
        //Getters and setters omitted for brevity
    }
    
0
Vlad Mihalcea

Mon implémentation des entités toString () pour Hibernate est la suivante:

@Override
public String toString() {
    return String.format("%s(id=%d)", this.getClass().getSimpleName(), this.getId());
}

Chaque sous-classe de mon AbstractEntity (ci-dessus) remplace cette méthode si nécessaire:

@Override
public String toString() {
    return String.format("%s(id=%d, name='%s', status=%s)",
            this.getClass().getSimpleName(),
            this.getId(),
            this.getName(),
            this.getStatus());
}

Pour hashCode () et equals (), gardez à l’esprit que Hibernate utilise souvent des classes proxy. Donc, mon équivalent () ressemble habituellement à ceci:

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;

    Class<AbstractEntity> c1 = Hibernate.getClass(this);
    Class<AbstractEntity> c2 = Hibernate.getClass(obj);
    if (!c1.equals(c2)) return false;

    final AbstractEntity other = (AbstractEntity) obj;
    if (this.getId() == null) {
        if (other.getId() != null) return false;
    }
    else if (!this.getId().equals(other.getId())) return false;

    return true;
}

Et comme d’autres l’ont déjà dit, soyez prudent lorsque vous accédez à des propriétés chargées paresseux! Un simple toString () ou même log.debug (entité) peut provoquer une activité considérable en cascade dans plusieurs objets et propriétés chargés paresseux.

0
Daniel Bleisteiner