What is the correct way of overriding hashCode () and equals () methods of persistent entity?

后端 未结 7 2057
青春惊慌失措
青春惊慌失措 2020-12-14 10:28

I have a simple class Role:

@Entity
@Table (name = \"ROLE\")
public class Role implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;         


        
相关标签:
7条回答
  • 2020-12-14 10:32

    Bauer and King's book Java Persistence with Hibernate advises against using the key field for equals and hashCode. They advise you should pick out what would be the object's business key fields (if there was no artificial key) and use those to test equality. So in this case if role name was not a necessary field you would find the fields that were necessary and use them in combination. In the case of the code you post where rolename is all you have besides the id, rolename would be what I'd go with.

    Here's a quote from page 398:

    We argue that essentially every entity class should have some business key, even if it includes all properties of the class (this would be appropriate for some immutable classes). The business key is what the user things of as uniquely identifying a particular record, whereas the surrogate key is what the application and database use.

    Business key equality means that the equals() method compares only the properties that form the business key. This is a perfect solution that avoids all the problems presented earlier. The only downside is that it requires extra thought to identify the correct business key in the first place. This effort is required anyway; it's important to identify any unique keys if your database must ensure data integrity via constraint checking.

    An easy way I use to construct an equals and hashcode method is to create a toString method that returns the values of the 'business key' fields, then use that in the equals() and hashCode() methods. CLARIFICATION: This is a lazy approach for when I am not concerned about performance (for instance, in rinky-dink internal webapps), if performance is expected to be an issue then write the methods yourself or use your IDE's code generation facilities.

    0 讨论(0)
  • 2020-12-14 10:37

    Knowing when overriding the hashCode and equals is not an easy task, there is another discussion where you have example and documentation link here What issues should be considered when overriding equals and hashCode in Java?

    0 讨论(0)
  • 2020-12-14 10:38

    As already mentioned you have to use a business key to implement equal and hashCode. Additionally you have to make your equals and hashCode implementation null-safe or add not null contraints (and invariant checks into your code) to ensure that the business key is never null.

    I suppose adding constraints is the right approach for your problem. Otherwise Role instances without names would be allowed and all these physical instances would be considered equal.

    0 讨论(0)
  • 2020-12-14 10:38

    Please find below simple instructions how to create hashCode, equals and toString methods using Apache commons builders.

    hashCode

    1. If two objects are equal according to the equals() method, they must have the same hashCode() value
    2. It is possible that two distinct objects could have the same hashCode().
    3. Please use unique Business ID for the hashCode creation (it is mean that you should use some unique property that represent business entity, for example, name)
    4. Hibernate Entity: please do NOT use Hibernate id for the hashCode creation
    5. You may call for .appendSuper(super.hashCode()) in case your class is subclass

      @Override
      public int hashCode() {
          return new HashCodeBuilder()
                  .append(getName())
                  .toHashCode();
      }
      

    equals

    1. Please compare Business ID (it is mean that you should use some unique property that represent business entity, for example, name)
    2. Hibernate Entity: please do NOT compare Hibernate id
    3. Hibernate Entity: use getters when you access the other object field to let to Hibernate to load the property
    4. You may call for .appendSuper(super.equals(other)) in case your class is subclass

      @Override
      public boolean equals(final Object other) {
          if (this == other)
              return true;
          if (!(other instanceof TreeNode))
              return false;
          TreeNode castOther = (TreeNode) other;
          return new EqualsBuilder()
                  .append(getName(), castOther.getName())
                  .isEquals();
      }
      

    toString

    1. Please ensure that toString will not throw NullPointerException.
    2. You may call for .appendSuper(super.toString()) in case your class is subclass

      @Override
      public String toString() {
          return new ToStringBuilder(this)
                  .append("Name", getName())
                  .toString();
      }
      
    0 讨论(0)
  • 2020-12-14 10:39

    I use Lombok on my project here's a tally of the rules that apply to JPA entities I moved the ones specific for equals() and hashCode() to the top

    Rules for building entity classes

    When creating, make sure of the following

    • EqualsHashCode should only be done on "business keys"

    • Composite keys are generally business keys. so they must not be EqualsHashCode.Exclude

    • EqualsHashCode.Exclude on "surrogate keys" / "technical IDs" such as generated IDs, Version, audit fields, values that get updated by @PrePersist or @PreUpdate

    • ManyToOne must have EqualsHashCode.Exclude and ToString.Exclude to prevent stack overflow

    • ManyToOne must have a corresponding EqualsHashCode.Include and ToString.Include to get the ID field of the referenced object. The method must be suffixed with Id and should not start with get


    • @Entity class names must be singular. The @Repository class names would be plural.
    • @Entity must have @Data
    • @Entity must be Serializable
    • @Entity must have @NoArgsConstructor if a constructor is provided
    • Composite keys must have @Data
    • @OneToMany must have cascadeType=Cascade.ALL
    • @OneToMany must have mappedBy
    • @OneToMany must have orphanRemoval=true
    • @OneToMany must be a Set
    • ManyToOne must have optional=false
    • ManyToOne must have a @JoinColumn

    What constitutes as business keys?

    Use the analogy of two equivalent deck of cards that are soiled and are shuffled together. A collection would be a deck that should contain cards that have a uniquely identifiable value.

    • The business key would be the suit, the number even the card back. These uniquely identify the value of the card.

    • The soil pattern provided it is only in the face of the card is not relevant. As such it is not part of the business key.

    • The physical existence of the card itself is also not part of the business key. This is similar to a generated private key for the card. This is also referred to as a surrogate key or technical ID.

    0 讨论(0)
  • 2020-12-14 10:44

    I'm sorry to jump in late with criticism, but nobody else has mentioned it and there is a serious flaw here. Possibly two, actually.

    First, others have mentioned how to handle the possibility of null, but one critical element of a good hashcode() and equals() method pair is that they must obey the contract, and your code above does not do this.

    The contract is that objects for which equals() returns true must return equal hashcode values, but in your class above, the fields id and roleName are independent.

    This is fatally flawed practice: you could easily have two objects with the same roleName value, but different id values.

    The practice is to use the same fields to generate the hashcode value as are used by the equals() method, and in the same order. Below is my replacement for your hashcode method:

    
    public int hashCode () {
        return ((roleName==null) ? 0 : roleName.hashcode()); 
    }
    

    Note: I don't know what you intended by the use of the id field as hashcode, or what you meant to do with the id field. I see from the annotation that it's generated, but it's externally generated, so the class as written fails to fulfill the contract.

    If for some reason you find yourself in a situation where this class is exclusively managed by another which faithfully generates "id" values for roleNames which do fulfill the contract, you wouldn't have a functionality problem, but it would still be bad practice, or at least have what people refer to as a "code smell". Besides the fact that there's nothing in the class definition to guarantee that the class is only usable in that way, hashcodes aren't ids, so ids aren't hashcodes.

    That doesn't mean you couldn't use a guaranteed-equal-for-equal-rolename-values identifier as the hashcode, but they're not conceptually the same, so at the very least, you should have a block of comment to explain your departure from expected practice.

    And as a good general rule, if you find yourself having to do that, you've probably made a design error. Not always, but probably. One reason for that? People don't always read comments, so even if you create a perfectly functioning system, over time, someone will "misuse" your class and cause problems.

    Having the class itself manage the generation of hashcode values avoids that. And you could still save and make available the externally generated id, for whatever purpose you use it.

    0 讨论(0)
提交回复
热议问题