equals and hashCode with Spring Data JPA and Hibernate



在阅读了几篇文章,线程并进行了一些研究之后,现在我完全困惑于在我的Spring Boot应用程序中实现适当的equals和hashCode方法。

例如,我有以下类:

@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<>();
}

我遇到了下面这些问题,并试图正确实现equals和hashCode方法的麻烦:

1)据我所知,当有一个唯一的字段,可以用来区分一个人从另一个,如电子邮件或用户名,那么它是足够的,只有在等号和hashCode方法中使用这些字段。这是真的吗?

2)如果除了id之外没有任何唯一字段,那么我应该将所有字段(id和其他字段)添加到equals和hashCode方法实现中吗?

3)当使用Hibernate和Data JPA时,我是否应该遵循与其他情况不同的方法,因为在JPA生命周期中有4种状态:暂态、管理、删除和分离?我认为id字段不应该在这种情况下使用,因为它不存在于瞬态模式?对吧?

在实现equals()和hashCode()方法时:

  1. 如果有一个唯一的字段可以用来区分一个对象和另一个对象,在实现中只使用该字段。

  2. 如果没有唯一字段,则使用实现中包括id在内的所有字段

  3. 当使用Hibernate和Data JPA时,不要在实现中使用ID字段,因为它不存在于瞬态,而是使用存在于所有状态的字段,例如唯一字段或所有字段。

equalshashCode的问题是它们的合同对于任何可变实体和JPA都是无效的,实际上没有任何其他的。暂时忽略JPA,根据定义,实体的id定义了其标识。因此,对于equalshashCode,应该使用它。但是这要求id在实体中是不可修改的,但是JPA需要一个无参数的构造函数和一种设置所有属性的方法,包括id。

最好的方法可能是

  • 使用id。

  • 确保equalshashCode在id设置之前从未使用过,并且id之后从未更改过。在id设置之后不更改它通常不是问题,因为id不应该从一个值更改为另一个值。问题在于新实例的创建。同样,JPA返回的实例也不是问题,因为JPA将在将它们返回给您之前完全初始化它们。在应用程序中创建新实例是个问题。这里有以下选项:

    1. 创建实例并立即分配id。uuid是完美的选择。它们可以在应用服务器上轻松高效地生成。这可以在实体类的静态工厂方法中完成。缺点是uid对于人类来说是一个痛苦的工作,因为它们很长并且基本上是随机的。它们也很大,比传统的序列号占用更多的数据库内存。但是,用例中有如此多的行,这实际上是一个问题是罕见的。

    2. 像大多数人一样在数据库中生成id,并确保在创建后立即保存新实体。这可以在存储库中的自定义方法中很好地完成。但是它确实要求你在一个地方设置所有必需的属性,这常常会成为一个问题。

使用其他一些应该是不可变的属性,比如帐户名或电子邮件,首先只适用于极少数实体,即使对这些实体来说,它现在是不可变的,也不意味着它会一直保持这种状态。

您可以选择依赖它,而不是试图避免JPA创建的陷阱。JPA保证对于给定的类和id,只有一个实例处于持久化上下文中。因此,只要你只在一个实体的单个会话/事务中工作,并且不尝试比较分离的实体,就根本不需要实现equalshashCode

既然您已经在使用lombok了,那么您也可以使用@Data注释:

@ data现在所有这些:@ToString、@EqualsAndHashCode、所有字段的@Getter、所有非final字段的@Setter和@RequiredArgsConstructor的快捷方式!

最新更新