JPA - 映射复合主键 OneToMany

时间:2021-05-08 16:32:18

标签: java hibernate jpa spring-data-jpa

假设有 2 个实体,分别代表一篇博客文章及其评论。一个博客可以有多条评论:

@Entity
public class Post {

   @Id
   @GeneratedValue
   private Long id;

   private String name;

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

@Entity
@IdClass(CommentPK.class)
public class Comment {

  @Id
  private Long id;  

  @Id
  @ManyToOne
  private Post post;

  private String content;
}

class CommentPK implements Serializable {
   private Long post;
   private Long id;
}

我希望Comment有(post_id,comment_id)的复合PK,其中comment_id是一个帖子内的一个序列,例如:

| post_id | comment_id | content  |
| 1       | 1          | 123      |
| 1       | 2          | 456      |
| 1       | 3          | Hello SO |
| 2       | 1          | New Post | << Post #2 has comment IDs starting from 1 again

虽然映射有效,但我无法保存帖子,因为评论没有 ID。这就是我添加实体侦听器的原因:

@PrePersist
@PreUpdate
public void preProcess(Post post) {
  int id = 1;
  for (Comment comment : post.getComments()) {
    comment.setPost(post);
    comment.setId(id++);
  }
}

它适用于第一个帖子,但是如果我向持久帖子添加新评论,则不会调用实体侦听器。我也尝试使用类似的逻辑为 Comment 添加实体侦听器,但它没有被调用

@Test
public void testPersistAndUpdate() {
  Post post = new Post();
  post.setName("Test");

  Comment comment1 = new Comment();
  comment1.setPost(post);
  comment1.setContent("Comment 1");

  List<Comment> comments = new ArrayList<>();
  comments.add(comment1);
  post.setComments(comments);

  Post savedPost = postRepository.saveAndFlush(post); // Spring Data JPA repository, all OK for now
  
  // create new Comment
  Comment comment2 = new Comment();
  comment2.setPost(post);
  comment2.setContent("Comment 2");

  savedPost.getComments().add(comment2);
  savedPost = postRepository.saveAndFlush(savedPost); // here entity listener is not invoked and exception is thrown

}

我收到一个异常:

<块引用>

javax.persistence.PersistenceException:org.hibernate.HibernateException:复合标识符的任何部分都不能为空

问题

  1. 建模这些关系的常见做法是什么? (仍然保持复合键的要求)因为现在通过 JPA/Hibernate 处理它感觉很不舒服

  2. 如果保留示例中的映射,是否可以使 @PrePersistComment 工作,当 Post 属性未更新,但 Comment是新的,应该保留吗?


1 个答案:

答案 0 :(得分:0)

我不认为使用 JPA Callbacks 是解决您问题的正确方法(另请参阅 this article)。我建议您使用 @MapsId 注释。您可以通过以下方式更正映射:

@Entity
public class Post {

   @Id
   @GeneratedValue
   private Long id;

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

@Entity
public class Comment {

  @EmbeddedId
  CommentPK id;
  

  @MapsId("post")
  @ManyToOne
  private Post post;

  // 
}

@Embeddable
class CommentPK implements Serializable {
   private Long post;
   private Long id;
   
   // getters, setters, equals, hashCode
}

CommentPK.post 值将从 Comment.post 关联派生。