简体   繁体   中英

equals and hashCode with Spring Data JPA and Hibernate

After reading several articles, threads and making some research, now I am completely confused regarding to implementing a proper equals and hashCode method in my Spring Boot app.

For example, I have the following class:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Recipe {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column(length = 100)
    private String description;

    @Column(nullable = false)
    private Integer prepTime;

    @Column(nullable = false)
    private Integer cookTime;

    @Column(nullable = false)
    private Integer servings;

    @Lob
    @org.hibernate.annotations.Type(type = "org.hibernate.type.TextType")
    @Column(nullable = false)
    private String instructions;

    @Column(nullable = false)
    @Enumerated(value = EnumType.STRING)
    private Difficulty difficulty;

    @Column(nullable = false)
    @Enumerated(value = EnumType.STRING)
    private HealthLabel healthLabel;

    @ManyToOne(optional = true, fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id", referencedColumnName = "id")
    private Category category;

    @OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<RecipeIngredient> recipeIngredients = new ArrayList<>();
}

I have trouble for these issues below and trying to implement equals and hashCode methods properly:

1) As far as I know, when there is a unique field that can be used to differentiate one person from the other eg email or username, then it is enough ONLY use these fields in equals and hashCode method. Is that true?

2) If there is not any unique field except from id, then should I add ALL the fields (id and the others) to equals and hashCode method implementation?

3) When using Hibernate and Data JPA, should I follow a different approach than other situation as there are 4 states transient, managed, removed, and detached in JPA lifecycle? I think id field should not be used in this situation as it is not present in transient mode? Right?

When implementing equals() and hashCode() methods:

  1. If there is a unique field that can be used to differentiate one object from another, use only that field in the implementation.

  2. If there is no unique field, use all the fields including the id in the implementation.

  3. When using Hibernate and Data JPA, do not use the ID field in the implementation as it is not present in the transient state, instead use fields that are present in all states such as unique fields or all fields.

The problem with equals and hashCode is that their contract is broken for any mutable entity and with JPA, there aren't really any other. Ignoring JPA for a moment, by definition the id of an entity defines its identity. So it should be used for equals and hashCode . But this requires the id to be unmodifiable in an entity, but JPA requires a no args constructor and a way to set all properties, including the id.

Probably the best way around this is to

  • use the id.

  • make sure that equals and hashCode is never ever used before the id is set, and the id is never changed afterwards. Not changing the id after it is once set is normally not a problem, since the id shouldn't change from one value to another. The problem is creation of new instances. Again instances returned by JPA aren't a problem, because JPA will fully initialise them before returning them to you. Creating fresh instances in your application is the problem. Here you have the following options:

    1. create the instance and immediately assign a id. UUIDs are perfect for this. They can be generated easily and efficiently on the application server. This could be done in a static factory method on the entity class. The drawback is that UUIDs are a pain to work with for humans, since they are long and basically random. They are also large and eat more memory in the database than a traditional sequence number. But the use cases with so many rows that this actually is a problem are rare.

    2. generate the id in the database as most people do, and make sure that your new entity gets saved immediately after creation. This could be nicely done in a custom method in a repository. But it does require that you set all required properties in one place, which often can be a problem.

Using some other attribute which is supposed to be immutable, like the account name or an email works only for very few entities in the first place and even for those the fact that it is immutable now doesn't mean it stays that way.

Instead of trying to avoid the pitfalls created by JPA you could alternatively rely on it. JPA guarantees that for a given class and id only one instance is in a persistence context. Therefore, as long as you only work within a single session/transaction with an entity and don't try to compare detachd entities, there is no need to implement equals and hashCode at all.

as you are already using lombok, you can use @Data annotation as well:

@Data All together now: A shortcut for @ToString, @EqualsAndHashCode, @Getter on all fields, @Setter on all non-final fields, and @RequiredArgsConstructor!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM