没有通过Hibernate中的@ManyToMany关系传播CascadeType.DETACH

问题描述

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

关于发布评论,我让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,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集合中?

解决方法

我想它的发生原因与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