首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Spring数据REST/JPA -用复合键更新OneToMany集合

Spring数据REST/JPA -用复合键更新OneToMany集合
EN

Stack Overflow用户
提问于 2017-02-02 10:32:37
回答 1查看 1.4K关注 0票数 0

使用Spring和Spring,我希望更新聚合根上的子实体集合。作为一个演示的例子,假设我有一个Post实体,它与Comment实体有一对多的关系。Post有自己的Spring数据存储库;Comment没有,因为它只能通过Post访问。

令人讨厌的是,由于现有的数据库设计,Comment有一个包含Post外键的复合密钥。因此,在没有双向关系的情况下,我无法找到使外键成为Comment组合密钥的一部分的方法,尽管我不需要双向关系。

类与Lombok注释类似:

代码语言:javascript
复制
@Entity
@Data
public class Post {

    @Id
    @GeneratedValue
    private long id;

    @OneToMany(mappedBy = "post", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<Comment> comments = new HashSet<>();

    private String title;
}

还有评论:

代码语言:javascript
复制
@Entity
@IdClass(Comment.CommentPk.class)
@Data
@EqualsAndHashCode(exclude = "post")
@ToString(exclude = "post")
public class Comment {

    @Id
    private long id;

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    @RestResource(exported = false)
    @JsonIgnore
    private Post post;

    private String content;

    @Data
    static class CommentPk implements Serializable {
        private long id;

        private Post post;
    }
}

和储存库:

代码语言:javascript
复制
public interface PostRepository extends JpaRepository<Post, Long> {
}

如果我尝试用一个Post创建一个Comment,就会出现一个异常,即POST_ID不能为NULL。换句话说,它在试图持久化的Post中缺少对父Comment的反向引用。

可以通过将@PrePersist方法添加到维护此反向引用的Post中来解决这个问题:

代码语言:javascript
复制
@PrePersist
private void maintainParentBackreference() {
    for (Comment comment : this.comments) {
        comment.setPost(this);
    }
}

当创建一个新的Post时,上面的内容很好,但是当尝试将Comment添加到现有的Post (例如,使用PUT请求)时却没有帮助,因为在尝试插入注释时会发生以下错误:

代码语言:javascript
复制
NULL not allowed for column "POST_ID"; SQL statement:
insert into comment (content, id, post_id) values (?, ?, ?) [23502-193]

概括地说,复制的步骤是:

  1. 发布一个没有PostComment
  2. 放到使用Post a Comment创建的Comment

使用Spring向现有的Comment更新/添加Post的最简单方法是什么?

演示这一点的示例项目可以在这里找到:https://github.com/shakuzen/aggregate-child-update-sample/tree/composite-key

这个特殊的设置位于存储库的composite-key分支中。要用这段代码再现上面描述的失败,您可以遵循自述的手动再现步骤,或者运行集成测试AggregateCompositeKeyUpdateTests.canAddCommentWithPut

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-02-02 14:13:30

您确实不应该使用@PrePersist@PreUpdate回调来管理这些回退引用,因为它们的调用通常取决于Post的状态是否被实际操作。

相反,这些关系应该是控制器或某些业务服务调用的特定域代码的一部分。我通常更喜欢在更多领域驱动的设计方法中抽象这些类型的关系:

代码语言:javascript
复制
public class Post {
  @OneToMany(mappedBy = "Post", cascade = CascadeType.ALL, ...)
  private Set<Comment> comments;

  // Allows access to the getter, but it protects the internal collection
  // from manipulation from the outside, forcing users to use the DDD methods.
  public Set<Comment> getComments() {
     return Collections.unmodifiableSet( comments );
  }

  // Do not expose the setter because we want to control adding/removing
  // of comments through a more DDD style.
  private void setComments(Set<Comment> comments) {
     this.comments = comments;
  }

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

  public void removeComment(Comment comment) {
    if ( this.comments != null ) {
      for ( Iterator<Comment> i = comments.iterator(); i.hasNext(); ) {
        final Comment postComment = i.next();
        if ( postComment.equals( comment ) ) {
          // uses #getCompositeId() equality
          iterator.remove();
          comment.setPost( null );
          return;
        }
      }
    }
    throw new InvalidArgumentException( "Comment not associated with post" );
  }

正如您从代码中看到的,如果Post实体对象的用户希望操作相关的注释,他们将被迫使用#addComment#removeComment。这些方法确保反向引用设置正确。

代码语言:javascript
复制
final Comment comment = new Comment();
// set values on comment
final Post post = entityManager.find( Post.class, postId );
post.addComment( comment );
entityManager.merge( post );

更新- Spring数据REST解决方案

为了让Spring数据REST直接应用这个逻辑,您可以编写侦听器或回调类。

听众的一个例子是:

代码语言:javascript
复制
public class BeforeSavePostEventListener extends AbstractRepositoryEventListener {
  @Override
  public void onBeforeSave(Object entity) {
    // logic to do by inspecting entity before repository saves it.
  }
}

带注释的处理程序的一个例子是:

代码语言:javascript
复制
@RepositoryEventHandler 
public class PostEventHandler {
  @HandleBeforeSave
  public void handlePostSave(Post p) {
  }
  @HandleBeforeSave
  public void handleCommentSave(Comment c) {
  } 
}

接下来,您只需要确保通过扫描指定各种@Component构造型之一,或者在配置类中将其指定为@Bean,从而确保获取该bean。

这两种方法最大的区别在于,第二种方法是类型安全的,实体类型是由各种带注释的方法的第一个参数决定的。

您可以找到关于这个这里的更多细节。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41999835

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档