问题描述
我正在尝试克隆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