Hibernate SoftDelete 注解
文章目录
1、简介
在本文中,我们将看到如何使用 Hibernate 的 @SoftDelete
注解来为 JPA 实体启用软删除功能。
2、软删除模型
Tag
实体的映射如下:
@Entity
@Table(name = "tag")
@SoftDelete
public class Tag {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String name;
}
@SoftDelete
注解是在 Hibernate 6.4 中引入的,允许我们启用原生的软删除机制。
Post
实体的映射如下:
@Entity
@Table(name = "post")
@SoftDelete
public class Post {
@Id
private Long id;
private String title;
@OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
@OneToOne(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY
)
private PostDetails details;
@ManyToMany
@JoinTable(
name = "post_tag",
joinColumns = @JoinColumn(name = "post_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id")
)
@SoftDelete
private List<Tag> tags = new ArrayList<>();
public Post addComment(PostComment comment) {
comments.add(comment);
comment.setPost(this);
return this;
}
public Post removeComment(PostComment comment) {
comments.remove(comment);
comment.setPost(null);
return this;
}
public Post addDetails(PostDetails details) {
this.details = details;
details.setPost(this);
return this;
}
public Post removeDetails() {
this.details.setPost(null);
this.details = null;
return this;
}
public Post addTag(Tag tag) {
tags.add(tag);
return this;
}
}
注意,Post
实体和 tags
集合都使用了 @SoftDelete
Hibernate 注解。
前者用于软删除 post
表记录,后者用于软删除 post_tag
表行。
post_details
表通过 @MapsId
注解映射到 PostDetails
实体,该注解允许我们在父 post
和子 post_details
表之间重用主键,如下所示:
@Entity
@Table(name = "post_details")
@SoftDelete
public class PostDetails {
@Id
private Long id;
@Column(name = "created_on")
private Date createdOn;
@Column(name = "created_by")
private String createdBy;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id")
@MapsId
private Post post;
}
post_comments
表映射到 PostComment
实体如下:
@Entity
@Table(name = "post_comment")
@SoftDelete
public class PostComment {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@NotFound(action = NotFoundAction.EXCEPTION)
private Post post;
private String review;
}
当我们使用 @SoftDelete
注解时,还需要在使用 FetchType.LAZY
策略的 @ManyToOne
关联上添加 @NotFound
注解。这是因为外键列的存在并不一定意味着父实体仍然存在,因为它可能已经被软删除。
3、测试 Tag
实体上的 Hibernate @SoftDelete
注解
假设我们创建了以下 Tag
实体:
entityManager.persist(
new Tag().setName("Java")
);
entityManager.persist(
new Tag().setName("JPA")
);
entityManager.persist(
new Tag().setName("Hibernate")
);
entityManager.persist(
new Tag().setName("Misc")
);
当删除其中一个 Tag
实体时:
Tag miscTag = entityManager.unwrap(Session.class)
.bySimpleNaturalId(Tag.class)
.getReference("Misc");
entityManager.remove(miscTag);
Hibernate 将执行以下 UPDATE
语句,将 deleted
列设置为 true
:
UPDATE tag
SET
deleted = true
WHERE
id = 4 AND
deleted = false
在 Tag
实体被软删除后,我们可以看到 JPQL 查询无法找到它:
Boolean exists = entityManager.createQuery("""
select count(t) = 1
from Tag t
where t.name = :name
""", Boolean.class)
.setParameter("name", "Misc")
.getSingleResult();
assertFalse(exists);
在后台,Hibernate 在引用带有 @SoftDelete
注解的实体时,会在 WHERE
子句中添加 deleted = false
谓词:
SELECT
count(t.id)=1
FROM
tag t
WHERE
t.name = 'Misc' AND
t.deleted = false
4、测试 PostDetails
实体上的 Hibernate @SoftDelete
注解
考虑到我们有一个 Post
实体,它有一个一对一的 PostDetails
子实体:
Post post = new Post()
.setId(1L)
.setTitle("High-Performance Java Persistence");
post.addDetails(
new PostDetails()
.setCreatedOn(
Timestamp.valueOf(
LocalDateTime.of(2023, 7, 20, 12, 0, 0)
)
)
);
entityManager.persist(post);
当通过父 Post
实体上的 removeDetails
方法删除 PostDetails
时:
Post post = entityManager.find(Post.class, 1L);
assertNotNull(post.getDetails());
post.removeDetails();
Hibernate 生成以下 SQL UPDATE
语句,软删除 post_details
记录:
UPDATE
post_details
SET
deleted = true
WHERE
id = 1 AND
deleted = false
在 PostDetails
实体被软删除后,我们将无法使用 find
方法获取它:
assertNull(entityManager.find(PostDetails.class, 1L));
5、测试 PostComment
实体上的 Hibernate @SoftDelete
注解
假设我们有一个 Post
实体,它有两个 PostComment
子实体:
entityManager.persist(
new Post()
.setId(1L)
.setTitle("High-Performance Java Persistence")
.addComment(
new PostComment()
.setId(1L)
.setReview("Great!")
)
.addComment(
new PostComment()
.setId(2L)
.setReview("To read")
)
);
当通过父 Post
实体上的 removeComment
方法删除一个 PostComment
实体时:
Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());
assertNotNull(entityManager.find(PostComment.class, 2L));
post.removeComment(post.getComments().get(1));
Hibernate 生成以下 UPDATE
语句:
UPDATE
post_comment
SET
deleted = true
WHERE
id = 2 AND
deleted = false
在 PostComment
被软删除后,Hibernate 会在尝试直接通过 find
方法或间接通过获取 comments
集合时隐藏 PostComment
实体:
Post post = entityManager.find(Post.class, 1L);
assertEquals(1, post.getComments().size());
assertNull(entityManager.find(PostComment.class, 2L));
6、测试 Post
实体上的 Hibernate @SoftDelete
注解
如果我们创建一个包含所有关联的 Post
实体,如以下示例所示:
entityManager.persist(
new Post()
.setId(1L)
.setTitle("High-Performance Java Persistence")
.addComment(
new PostComment()
.setId(1L)
.setReview("Great!")
)
.addComment(
new PostComment()
.setId(2L)
.setReview("To read")
)
);
当删除 Post
实体时:
Post post = entityManager.createQuery("""
select p
from Post p
join fetch p.comments
join fetch p.details
where p.id = :id
""", Post.class)
.setParameter("id", 1L)
.getSingleResult();
entityManager.remove(post);
Hibernate 生成以下 SQL UPDATE
语句:
Query:["update post_tag set deleted=true where post_id=? and deleted=false"], Params:[(1)]
Query:["update post_comment set deleted=true where id=? and deleted=false"], Params:[(1)]
Query:["update post_comment set deleted=true where id=? and deleted=false"], Params:[(2)]
Query:["update post_details set deleted=true where id=? and deleted=false"], Params:[(1)]
Query:["update post set deleted=true where id=? and deleted=false"], Params:[(1)]
注意,通过简单地使用 @SoftDelete
Hibernate 注解,每个表记录都被软删除。
如果你喜欢这篇文章,我相信你也会喜欢我的书和视频课程。
7、结论
与我们之前必须实现的机制相比,Hibernate @SoftDelete
注解非常容易使用。
如果你想受益于这种原生机制,那么你应该将你的 Hibernate 版本升级到 6.4 或更新版本。