【问题标题】:Recursive relationship in Spring Data and JPA?Spring Data和JPA中的递归关系?
【发布时间】:2018-11-06 07:57:42
【问题描述】:

我的Comment 实体是具有subComment 集的自联接实体。

@Entity
public class Comment {

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

@OneToMany(mappedBy = "parentComment", cascade = CascadeType.ALL)
private Set<Comment> subComments = new HashSet<>();

@ManyToOne
@JoinColumn(referencedColumnName = "id")
private Comment parentComment;

在我的addComment 方法中

public ResponseEntity<Comment> addComment(Comment comment) {
    Comment currComment = commentRepository.save(comment);
    if (currComment.getParentId() != null) {
        Comment parent = commentRepository.findById(currComment.getParentId()).orElse(null);
        if (parent != null) {
            parent.addSubComment(currComment);
            currComment.setParentId(parent.getId());
            currComment.setParentComment(parent);
            commentRepository.save(parent);
        }
    }
    Comment responseComment = commentRepository.save(currComment);
    return ResponseEntity.ok(responseComment);
}

当我试图建立反向关系(拥有方)时, comment.setParentComment(parent); 导致错误

comment.setParentComment(parent); 导致错误:java.lang.IllegalStateException: Cannot call sendError() after the response has been committed

Full Comment实体类

@OneToMany(mappedBy = "parentComment", cascade = CascadeType.ALL)
private Set<Comment> subComments = new HashSet<>();

@ManyToOne
@JoinColumn(referencedColumnName = "id")
private Comment parentComment;

private boolean isParent;
private String parentId;

public String getParentId() {
    return parentId;
}

public void setParentId(String parentId) {
    this.parentId = parentId;
}

public Set<Comment> getSubComments() {
    return subComments;
}

public void setSubComments(Set<Comment> subComments) {
    this.subComments = subComments;
}

public Comment addSubComment(Comment comment) {
    this.subComments.add(comment);
    return this;
}

public Comment getParentComment() {
    return parentComment;
}

public void setParentComment(Comment parentComment) {
    this.parentComment = parentComment;
}

public boolean getIsParent() {
    return isParent;
}

public void setIsParent(boolean isParent) {
    this.isParent = isParent;
}

【问题讨论】:

    标签: java spring hibernate h2


    【解决方案1】:
    @ManyToOne
    @JoinColumn(referencedColumnName = "id")
    @JsonBackReference
    private Comment parentComment;
    

    我添加了解决java.lang.IllegalStateException: Cannot call sendError() after the response has been committed 错误的@JsonBackReference。父 Comment 也可以看到 subComments 集合。

    【讨论】:

      【解决方案2】:

      您似乎过于复杂了。

      1) 你有@JoinColumn(referencedColumnName = "id") 但这是多余的。无论如何,这就是外键引用的内容,因此您无需明确说明。不用担心,但不要编写不需要的代码。

      2) 如果您在新的 subComment 中有 parentId,则无需查找并将其添加到父评论的列表中。您在这里缺少的概念是“拥有”实体概念。查看mappedBy 的Javadoc。由于parentComment 字段进行映射,它定义了拥有实体。当然,它是同一个实体,但关键是控制持久性的是 parentComment 字段。您不需要向subCommentsSet 添加任何内容以保持关系。如果你愿意,你可以这样做,但 JPA 会忽略它。您只需要设置parentComment 字段。例如

      编辑:此示例使用 JPA 而不是 Spring Data,但在底层是相同的。

      您的实体只需:

      @Entity
      public class Comment {
          @Id @GeneratedValue
          private Integer id;
      
          @OneToMany(mappedBy="parentComment")
          private Set<Comment> subComments;
      
          @ManyToOne
          private Comment parentComment;
      

      然后你像这样使用它:

      private void run() {
          runWrite();
          runRead();
          Comment comment = new Comment();
          comment.setId(1);
          Comment subComment = new Comment();
          subComment.setParentComment(comment);
          runSaveSubComment(subComment);
          runRead();
      }
      private void runWrite() {
          EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistence");
          em = emf.createEntityManager();
          tx = em.getTransaction();
          try {
              tx.begin();
              Comment comment = new Comment();
              Comment subComment = new Comment();
              subComment.setParentComment(comment);
              em.persist(comment);
              em.persist(subComment);
      
              tx.commit();
          } finally {
              emf.close();
          }        
      }
      private void runRead() {
          EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistence");
          em = emf.createEntityManager();
          try {
              Comment comment = em.createQuery("select c from Comment c left join fetch c.subComments where c.id = :id", Comment.class).setParameter("id", 1).getSingleResult();
              System.out.println(comment + Arrays.toString( comment.getSubComments().toArray()) );
          } finally {
              emf.close();
          }
      }
      private void runSaveSubComment(Comment subComment) {
          EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistence");
          em = emf.createEntityManager();
          tx = em.getTransaction();
          try {
              tx.begin();
              em.persist(subComment);
              tx.commit();
          } finally {
              emf.close();
          }        
      }
      

      或者如果你想使用 Spring Data。

      Comment comment = new Comment();
      Comment sub1 = new Comment();
      sub1.setParentComment(comment);
      repo.save(comment);
      repo.save(sub1);
      Comment parentComment = repo.fetchSubCommentsById(1);
      System.out.println(parentComment + Arrays.toString(parentComment.getSubComments().toArray()));
      Comment sub2 = new Comment();
      sub2.setParentComment(parentComment);
      repo.save(sub2);
      parentComment = repo.fetchSubCommentsById(1);
      System.out.println(parentComment + Arrays.toString(parentComment.getSubComments().toArray()));
      // or 
      Comment p = new Comment();
      p.setId(1);
      Comment sub3 = new Comment();
      sub3.setParentComment(p);
      repo.save(sub3);
      parentComment = repo.fetchSubCommentsById(1);
      System.out.println(parentComment + Arrays.toString(parentComment.getSubComments().toArray()));
      

      【讨论】:

      • 关注您的 cmets。我用的是Spring boot,有什么办法可以简化吗?
      • 我不是,只是使用写入数据库的第一个父级的 id,我碰巧知道是 1。通常,当前 Comment 会以其 id 和当用户添加新的子评论时,视图会返回 id。因此,与其从数据库中获取带有 id 的父评论,不如创建一个新评论并从视图中设置 id。 save() 调用不关心父注释的任何其他字段,因此它为您节省了数据库调用。 spring 示例显示它是双向完成的。
      • 另外,自定义 fetchSubCommentsById 获取父母和子评论,所以请注意这一点。通常,如果您只是从数据库中获得评论,则不会填写子评论。如果你试图访问它们,你会得到一个惰性初始化异常。 fetchSubCommentsById 没有很好的命名。我只是将查询从 JPA 部分复制到存储库接口中的自定义方法。请参阅stackoverflow.com/questions/29602386/… 上的 fetchMode 讨论。
      • @K 只是一个注释,因为它可能对其他人有帮助:您可以通过使用 @Transactional 注释方法来避免惰性初始化异常。与实体保持连接到实体管理器有关,允许实体管理器延迟加载关系。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-07-19
      • 2017-11-09
      • 1970-01-01
      • 2018-04-14
      • 2021-02-10
      • 2021-05-22
      • 1970-01-01
      相关资源
      最近更新 更多