首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Hibernate SoftDelete 注解: 实现软删除

Hibernate SoftDelete 注解: 实现软删除

作者头像
訾博ZiBo
发布2025-01-06 20:46:25
发布2025-01-06 20:46:25
1K0
举报

Hibernate SoftDelete 注解

1、简介

在本文中,我们将看到如何使用 Hibernate 的 @SoftDelete 注解来为 JPA 实体启用软删除功能。

2、软删除模型

Tag 实体的映射如下:

代码语言:javascript
复制
@Entity
@Table(name = "tag")
@SoftDelete
public class Tag {
    
    @Id
    @GeneratedValue
    private Long id;
    
    @NaturalId
    private String name;
}

@SoftDelete 注解是在 Hibernate 6.4 中引入的,允许我们启用原生的软删除机制。

Post 实体的映射如下:

代码语言:javascript
复制
@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 表之间重用主键,如下所示:

代码语言:javascript
复制
@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 实体如下:

代码语言:javascript
复制
@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 实体:

代码语言:javascript
复制
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 实体时:

代码语言:javascript
复制
Tag miscTag = entityManager.unwrap(Session.class)
    .bySimpleNaturalId(Tag.class)
    .getReference("Misc");

entityManager.remove(miscTag);

Hibernate 将执行以下 UPDATE 语句,将 deleted 列设置为 true

代码语言:javascript
复制
UPDATE tag
SET
    deleted = true
WHERE
    id = 4 AND
    deleted = false

Tag 实体被软删除后,我们可以看到 JPQL 查询无法找到它:

代码语言:javascript
复制
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 谓词:

代码语言:javascript
复制
SELECT
    count(t.id)=1
FROM
    tag t
WHERE
    t.name = 'Misc' AND
    t.deleted = false

4、测试 PostDetails 实体上的 Hibernate @SoftDelete 注解

考虑到我们有一个 Post 实体,它有一个一对一的 PostDetails 子实体:

代码语言:javascript
复制
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 时:

代码语言:javascript
复制
Post post = entityManager.find(Post.class, 1L);
assertNotNull(post.getDetails());

post.removeDetails();

Hibernate 生成以下 SQL UPDATE 语句,软删除 post_details 记录:

代码语言:javascript
复制
UPDATE
    post_details
SET
    deleted = true
WHERE
    id = 1 AND
    deleted = false

PostDetails 实体被软删除后,我们将无法使用 find 方法获取它:

代码语言:javascript
复制
assertNull(entityManager.find(PostDetails.class, 1L));

5、测试 PostComment 实体上的 Hibernate @SoftDelete 注解

假设我们有一个 Post 实体,它有两个 PostComment 子实体:

代码语言:javascript
复制
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 实体时:

代码语言:javascript
复制
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 语句:

代码语言:javascript
复制
UPDATE
    post_comment
SET
    deleted = true
WHERE
    id = 2 AND
    deleted = false

PostComment 被软删除后,Hibernate 会在尝试直接通过 find 方法或间接通过获取 comments 集合时隐藏 PostComment 实体:

代码语言:javascript
复制
Post post = entityManager.find(Post.class, 1L);
assertEquals(1, post.getComments().size());

assertNull(entityManager.find(PostComment.class, 2L));

6、测试 Post 实体上的 Hibernate @SoftDelete 注解

如果我们创建一个包含所有关联的 Post 实体,如以下示例所示:

代码语言:javascript
复制
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 实体时:

代码语言:javascript
复制
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 语句:

代码语言:javascript
复制
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 或更新版本。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-01-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Hibernate SoftDelete 注解
    • 1、简介
    • 2、软删除模型
    • 3、测试 Tag 实体上的 Hibernate @SoftDelete 注解
    • 4、测试 PostDetails 实体上的 Hibernate @SoftDelete 注解
    • 5、测试 PostComment 实体上的 Hibernate @SoftDelete 注解
    • 6、测试 Post 实体上的 Hibernate @SoftDelete 注解
    • 7、结论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档