【问题标题】:JPA - Mapping Composite Primary Key OneToManyJPA - 映射复合主键 OneToMany
【发布时间】:2021-05-08 16:32:18
【问题描述】:

假设有 2 个实体,它们代表博客文章及其 cmets。一个博客可以有多个 cmets:

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

它适用于第一个帖子持久化,但是如果我向持久化帖子添加新的 cmets,则不会调用实体侦听器。我也尝试使用类似的逻辑为 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. 如果保留示例中的映射,当Post 属性未更新但Comment 是新的并且应该保留时,是否可以使@PrePersistComment 工作?


【问题讨论】:

    标签: java hibernate jpa spring-data-jpa


    【解决方案1】:

    我认为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 关联。

    【讨论】:

    • 这不会生成comment.id.id 值。我应该如何设置它?我现在正在使用 JPA 回调。
    • this answer 可能会有所帮助。但我建议你问问自己:为什么要Comment有复合PK
    • 我认为从领域的角度来看它更正确。评论由它所属的帖子及其序号标识。此外,它允许我进行诸如“获取特定帖子的所有 cmets”之类的查询,而无需额外的索引
    • 我认为这种方法不会带来好处,只会带来开销复杂性。评论很容易有层次结构,这会破坏你的顺序模型。恕我直言,简单的 PK 用于评论和 FK 用于发布的方法看起来更加简单/透明,可以进一步支持/扩展。
    猜你喜欢
    • 2019-04-22
    • 2021-04-12
    • 1970-01-01
    • 2017-01-30
    • 2020-10-04
    • 2021-10-07
    • 2011-11-26
    • 1970-01-01
    • 2017-02-21
    相关资源
    最近更新 更多