简体   繁体   English

JPA @OneToMany - Set vs List - 使用 Set 时无法从双向关联中删除一个子实体

[英]JPA @OneToMany - Set vs List - Not able to delete one child entity from bidirectional association when using Set

Not able to delete child entity from OneToMany association if Set is used.如果使用Set ,则无法从 OneToMany 关联中删除子实体。 Everything works fine if I use List instead of Set如果我使用List而不是Set一切正常

Post.java帖子.java

@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Comment> comments;

public void addComment(Comment comment) {
    if (comments == null) {
        comments = new ArrayList<>();
    }
    comment.setPost(this);
    comments.add(comment);
}

public void removeComment(Comment comment) {
    if (comments == null || comments.isEmpty()) {
        return;
    }
    comments.remove(comment);
    comment.setPost(null);
}

Comment.java评论.java

@ManyToOne(fetch = FetchType.LAZY)
@EqualsAndHashCode.Include
private Post post;

Code for sample application is available on Github Github上提供了示例应用程序的代码

  • master branch is using Set and test case fails master分支正在使用Set并且测试用例失败
  • list branch is using 'List` and test case passes list分支正在使用“列表”并且测试用例通过

Not sure whether I am making any mistake.不知道我是否犯了任何错误。 Please suggest.请建议。

Your test asserts on the size of Post.comments .您的测试断言Post.comments的大小。 It is also @Transactional , meaning postRepository.getOne() returns the exact same instance of Post that you already have in the persistence context.它也是@Transactional ,这意味着postRepository.getOne()返回您在持久性上下文中已经拥有的完全相同Post实例。 This means dbPost == post , and so your problem has nothing to do with JPA, it is purely a Java problem.这意味着dbPost == post ,因此您的问题与 JPA 无关,纯粹是 Java 问题。

Try putting a breakpoint at the comments.remove(comment) line.尝试在comments.remove(comment)行设置断点。 What you'll likely see is that it returns false for the Set version.您可能会看到它为Set版本返回false That's because hashCode/equals is not implemented correctly.那是因为hashCode/equals没有正确实现。 Try using comments.removeIf(element -> element.getId().equals(comment.getId()) instead to see if the problem goes away.尝试使用comments.removeIf(element -> element.getId().equals(comment.getId())来查看问题是否消失。

(As a side note: there are multiple ways equals/hashCode can be implemented for JPA entities, but as a rule of thumb, making it compare a referenced entity is not a good idea, especially when that referenced entity is supposed to be lazily fetched). (附带说明:对于 JPA 实体,可以通过多种方式实现equals/hashCode ,但根据经验,比较引用的实体并不是一个好主意,尤其是当引用的实体应该被延迟获取时)。

EDIT:编辑:

The reason why the code works for List , and fails for Set is that:代码对List有效而对Set无效的原因是:

  • a List doesn't use hashCode at all,一个List根本不使用hashCode
  • while looking up elements, a HashSet uses the hashCode value before equals to determine potential equality在查找元素时, HashSet使用equals之前的hashCode值来确定潜在的相等性
  • the hashCode of an element already in the HashSet is not recalculated automatically when that element is updated.更新该元素时,不会自动重新计算HashSet中已有元素的hashCode

In the test, when you add Comment s to the Post , the hashCode of each comment is calculated based on the Post.id being null .在测试中,当你在Post中添加Comment时,每条评论的hashCode是根据Post.idnull计算的。 However, as soon as you call entityManager.flush() , Post.id becomes non-null, and the hashCode of the Comment you're trying to remove changes.但是,只要您调用entityManager.flush()Post.id就会变为非空,并且您尝试删除更改的CommenthashCode Hence, remove cannot find an element in the Set with the same (original) hashCode as the (newly calculated) hashCode for the comment passed as the parameter.因此,对于作为参数传递的commentremove无法在Set中找到具有与(新计算的) hashCode相同(原始) hashCode的元素。 This is desipte the fact that equals would have returned true for one of the elements .这是因为对于其中一个元素, equals会返回true的事实 A List does not have this problem, because all it ever uses for element comparison is equals . List没有这个问题,因为它用于元素比较的只是equals

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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