[英]Spring Data JPA save() method not following the hashcode/equals contract
[英]equals and hashCode with Spring Data JPA and Hibernate
在阅读了几篇文章、线程并进行了一些研究之后,现在我对在我的 Spring Boot 应用程序中实现适当的 equals 和 hashCode 方法感到完全困惑。
例如,我有以下 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<>();
}
我遇到以下这些问题并尝试正确实现 equals 和 hashCode 方法时遇到麻烦:
1)据我所知,当有一个唯一字段可以用来区分一个人与另一个人时,例如 email 或用户名,那么只在 equals 和 hashCode 方法中使用这些字段就足够了。 真的吗?
2)如果除了 id 之外没有任何唯一字段,那么我应该将所有字段(id 和其他)添加到 equals 和 hashCode 方法实现中吗?
3)当使用 Hibernate 和数据 JPA 时,我是否应该采用与其他情况不同的方法,因为在 JPA 生命周期中有 4 个状态 transient、managed、removed 和 detached? 我认为在这种情况下不应使用 id 字段,因为它在瞬态模式下不存在? 正确的?
实现 equals() 和 hashCode() 方法时:
如果有可用于区分一个 object 与另一个的唯一字段,请仅在实现中使用该字段。
如果没有唯一字段,则在实现中使用包括 id 在内的所有字段。
当使用 Hibernate 和数据 JPA 时,不要在实现中使用 ID 字段,因为它不存在于瞬态 state 中,而是使用在所有状态中都存在的字段,例如唯一字段或所有字段。
equals
和hashCode
的问题在于它们的合同对于任何可变实体都是无效的,而对于 JPA,实际上没有任何其他实体。 暂时忽略 JPA,根据定义,实体的 id 定义了它的身份。 所以它应该用于equals
和hashCode
。 但这要求 id 在实体中是不可修改的,但是 JPA 需要一个无参数构造函数和一种设置所有属性的方法,包括 id。
可能最好的解决方法是
使用身份证。
确保在设置 id 之前永远不会使用equals
和hashCode
,并且之后永远不会更改 id。 id 一旦设置后不更改通常不是问题,因为 id 不应该从一个值更改为另一个值。 问题是创建新实例。 同样,JPA 返回的实例不是问题,因为 JPA 将在将它们返回给您之前完全初始化它们。 在您的应用程序中创建新实例是问题所在。 在这里您有以下选项:
创建实例并立即分配一个 id。 UUID 非常适合这个。 它们可以在应用程序服务器上轻松高效地生成。 这可以在实体 class 上的 static 工厂方法中完成。缺点是 UUID 对人类来说很难使用,因为它们很长而且基本上是随机的。 它们也很大,比传统的序列号在数据库中吃得更多 memory。 但是具有如此多的行以至于这实际上是一个问题的用例很少见。
像大多数人一样在数据库中生成 id,并确保您的新实体在创建后立即得到保存。 这可以在存储库中的自定义方法中很好地完成。 但它确实要求您在一个地方设置所有必需的属性,这通常会成为一个问题。
使用一些其他应该是不可变的属性,例如帐户名称或 email 首先仅适用于极少数实体,即使对于那些现在不可变的事实并不意味着它会保持这种状态。
与其试图避免 JPA 造成的陷阱,不如您可以依赖它。 JPA 保证对于给定的 class 和 id 只有一个实例在持久性上下文中。 因此,只要您只在一个实体的单个会话/事务中工作并且不尝试比较分离的实体,就根本不需要实现equals
和hashCode
。
因为您已经在使用 lombok,所以您也可以使用@Data
注释:
@Data 现在全部在一起:@ToString、@EqualsAndHashCode、所有字段上的@Getter、所有非最终字段上的@Setter 和@RequiredArgsConstructor 的快捷方式!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.