Hibernate SoftDelete 注解: 实现软删除

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 或更新版本。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值