【问题标题】:CascadeType.DETACH isn't being propagated through a @ManyToMany relationship in HibernateCascadeType.DETACH 没有通过 Hibernate 中的 @ManyToMany 关系传播
【发布时间】:2021-01-01 13:10:36
【问题描述】:

我正在尝试克隆一个帖子对象。为此,我将其 ID 设置为 null,然后使用 entityManager 将其分离,然后保存。

关于帖子 cmets,我让 CascadeType.DETACH 完成传播更改的工作。考虑下一段代码来说明问题:

// PostService.java
private void clonePost(Post post) {
  post.setId(null);
  post.getComments().forEach(comment -> comment.setId(null));

  entityManager.detach(post); // CascadeType will take care of detaching the comments
  postRepository.save(post);  // now we have a duplicated post with the same comments

  // Since it's a detached entity and has no ID, Hibernate will treat it as a 
  // new entity and save it creating a new record
}

现在,有一个问题。这是Post 类:

public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(allocationSize = 50, initialValue = 1)
    private Long id;
    
    // ...
    
    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Comment> comments = new ArrayList<>();
    
    // ...
    
    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.DETACH)
    @JoinTable(name = "post_tags", 
          joinColumns = @JoinColumn(name = "post_fk", referencedColumnName = "id"), 
          inverseJoinColumns = @JoinColumn(name = "post_tag_fk", referencedColumnName = "id"))
    @Fetch(FetchMode.SUBSELECT)
    private Set<PostTag> tags = new HashSet<>();
}

帖子tags 使用@ManyToMany 的单向关系,Post 是其所有者,并且也是懒惰的获取。通常,分离帖子也会导致标签分离,因为cascade = CascadeType.DETACH设置在@ManyToMany中。

但是,这并没有发生。标签没有被分离。懒惰似乎确实有某种影响,可以防止分离充分传播,因为手动执行或初始化标签可以解决问题:

private void clonePost(Post post) {
    // ...
    
    // option 1 - manual detaching
    post.getTags().forEach(tag -> entityManager.detach(tag)); 
    
    // option 2 - initializing tags allows the detach of the post to propagate to them
    post.getTags().forEach(tag -> Hibernate.initialize(tag));

    entityManager.detach(post);
    postRepository.save(post);
}

如果标签设置为急切加载,这两个选项都可以避免:

public class Post {
    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.DETACH)
    @JoinTable(name = "post_tags", 
          joinColumns = @JoinColumn(name = "post_fk", referencedColumnName = "id"), 
          inverseJoinColumns = @JoinColumn(name = "post_tag_fk", referencedColumnName = "id"))
    // @Fetch(FetchMode.JOIN) // this fetch mode will also make the tags to load eager
    private Set<PostTag> tags = new HashSet<>();
}

为什么会这样?为什么父级分离时CascadeType.DETACH 不传播到延迟加载的@ManyToMany 集合?

【问题讨论】:

    标签: spring hibernate spring-data-jpa hibernate-mapping cascade


    【解决方案1】:

    我猜它的发生原因与REMOVE 相同。休眠文档says

    对于@ManyToMany 关联,REMOVE 实体状态转换没有意义级联,因为它会传播到链接表之外。由于另一端可能被父端的其他实体引用,因此自动删除可能会以ConstraintViolationException 结束。

    例如,如果定义了 @ManyToMany(cascade = CascadeType.ALL) 并且第一个人将被删除,Hibernate 将抛出异常,因为另一个人仍与被删除的地址相关联。

    Person person1 = entityManager.find(Person.class, personId);
    entityManager.remove(person1);
    
    Caused by: javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
    Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
    Caused by: java.sql.SQLIntegrityConstraintViolationException: integrity constraint violation: foreign key no action; FKM7J0BNABH2YR0PE99IL1D066U table: PERSON_ADDRESS
    

    【讨论】:

    • 感谢您的回答,@SternK。据我了解,REMOVEDETACH 的行为不同。事实上,如果集合设置为急切加载,DETACH 传播将按预期工作。当集合延迟加载时,它不会传播。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-21
    • 1970-01-01
    • 2017-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多