首页 > 其他分享 >使用spring-jpa和 hibernate实现逻辑删除

使用spring-jpa和 hibernate实现逻辑删除

时间:2024-02-01 17:00:44浏览次数:34  
标签:hibernate 删除 jpa spring Hibernate 注解 import javax persistence


一、使用spring-jpa和 hibernate的@SQLDelete和@Where注解实现逻辑删除

逻辑删除定义

逻辑删除是指在删除数据库的某条记录时,并不是真正的将该条记录删除,而是通过某个字段来标识其状态为“删除”,在接下来的查询等操作时,根据此字段来过滤调被删除的记录。

使用 Hibernate 进行逻辑删除

进行逻辑删除时,需要覆盖 Hibernate 默认的删除操作,告诉 Hibernate 使用特定的 UPDATE 语句来代替 DELETE。这一操作可以使用 @SQLDelete 注解来进行。

使用者需要为 @SQLDelete 注解给定 sql 字段。Hibernate 在执行删除操作,生成原生 SQL 时,会使用该字段。因此,该字段需要是数据库原生的 SQL。之后,不论是使用 Spring Data Jpa 提供的 SimpleJpaRepository 还是使用 @Query 注解来写 JPQL来执行删除操作,Hibernate 都会使用用户指定的 sql 来代替默认的 delete 语句。当然,用户写的原生 SQL 是不会进行该替换的。

更新当前 session 中的删除状态

由于 @SQLDelete 的原理是在生成 SQL 语句的时候对默认的 delete 语句进行替换,因此,Hibernate 并不知道他所管理的 Entity 的相应字段在执行过删除操作后进行了改变。通常情况下,这并没有什么问题,因为只要 Hibernate 执行了逻辑删除的 SQL,新的实体在查询的时候就会感知到删除状态。然而,在某些情况下,如执行了 EntityManager.remove(Object entity) 后,并没有立即 flush 到数据库,并立即对该 entity 进行操作,那么此时 entity 的相应字段并不会变为删除状态。解决这种情况的方法就是通过 @PreRemove 注解来注册一个回调函数,在该函数中更新相应字段的状态。这样,Hibernate 在执行删除之前,会回调该方法,从而更新相应字段的状态。

查询时避开逻辑删除的记录

执行逻辑删除后,通常不希望在执行查询操作时查询到逻辑删除过的记录,Hibernate 给出的解决方案是通过 @Where 注解。@Where 注解类似于 @SQLDelete 注解,用户在这里提供自定义的原生 where 语句,Hibernate 在生成真正的 SQL where 语句时,会将该语句与目标语句的查询条件通过 and 进行连接。用户可以将未逻辑删除的限定条件通过该注解进行设定,之后,不论是 JPQL 还是 EntityManager 相关的查询操作,都会带上该条件。

示例

本部分给出一个简单的示例,如下所示。User 的 deleted 域标明了该实体的删除状态。通过 @SQLDelete 注解指定删除操作时要执行的 update 操作,通过 @Where 注解指定了查询时的拼接条件。通过 @PreRemove 注解指定执行删除操作时的状态变更操作。

import lombok.Data;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.*;

@Data
@Entity
@SQLDelete(sql = "update user set deleted = 1 where id = ?")
@Where(clause = "deleted = 0")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    /**
     * 逻辑删除标志位,0 未删除,1 已删除
     */
    private Integer deleted;

    @PreRemove
    public void deleteUser() {
        this.deleted = 1;
    }
}

在执行删除时,Hibernate 生成的语句如下:

Hibernate: update user set deleted = 1 where id = ?

执行查询操作时,Hibernate 生成语句如下:

Hibernate: select user0_.id as id1_0_0_, user0_.deleted as deleted2_0_0_, user0_.name as name3_0_0_ from user user0_ where user0_.id=? and ( user0_.deleted = 0)

总结

借助 Hibernate 提供的 @SQLDelete 注解、@Where 注解,可以方便的实现 JPA 的逻辑删除。配合 Spring Data JPA 等优秀的开源项目,我们可以更加灵活方便的使用 JPA。



二、实际案例

BaseEntity.java

package com.imddy.layuiadmin.common;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
import java.time.LocalDateTime;

@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    @CreatedBy
    @Column(name = "create_by", nullable = false, updatable = false)
    @JsonIgnore
    private String createBy;

    @CreatedDate
    @Column(name = "create_time", nullable = false, updatable = false)
    @JsonIgnore
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;

    @LastModifiedBy
    @Column(name = "update_by", nullable = false)
    @JsonIgnore
    private String updateBy;

    @LastModifiedDate
    @Column(name = "update_time", nullable = false)
    @JsonIgnore
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;

    /**
     * 逻辑删除标志位,0 未删除,1 已删除
     */
    @JsonIgnore
    @Column(name = "deleted")
    private Integer deleted = 0;
}

SysTestEntity.java

package com.imddy.layuiadmin.entity;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.imddy.layuiadmin.common.BaseEntity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PreRemove;
import javax.persistence.Table;
import java.util.Objects;


@SQLDelete(sql = "update sys_test set deleted = 1 where id = ?")
@Where(clause = "deleted = 0")
@JsonIgnoreProperties(value = {"hibernateLazyInitializer"})
@Data
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
@Entity
@Table(name = "sys_test", schema = "layuiadmin", catalog = "")
public class SysTestEntity extends BaseEntity {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    @Column(name = "id")
    private int id;
    @Basic
    @Column(name = "testkey")
    private String key;
    @Basic
    @Column(name = "testvalue")
    private String value;

    @PreRemove
    public void deleteSysTest() {
        this.setDeleted(1);
    }

}

测试代码

@ResponseBody
    @RequestMapping("/test003")
    public SysTestEntity test003() {
        SysTestEntity sysTestEntity = sysTestRepository.getById(5);
        sysTestRepository.delete(sysTestEntity);
        return sysTestEntity;
    }

测试结果

使用spring-jpa和 hibernate实现逻辑删除_java

使用spring-jpa和 hibernate实现逻辑删除_java_02

还有待解决的问题

这样好像只有逻辑删除了,没法物理删除了。可以考虑见第三里面的其他实现。




三、另外2种分别是

A、一种是继承SimpleJpaRepository实现类

B、一种是折中定义Repository的自定义的基础类,然后由其@Query+@Modifying注解进行实现。

C、Hibernate 实现软删除,需要hibernate>=6.4,有@SoftDelete来实现。单独写一篇来记录。

这3种后面单独介绍。












标签:hibernate,删除,jpa,spring,Hibernate,注解,import,javax,persistence
From: https://blog.51cto.com/lenglingx/9534534

相关文章

  • springboot实现文件上传
    编程环境介绍:springboot2.7.4接收前端提交的文件请求方式:Post请求路径:/upload实现步骤前端: 后端:接受类型为mutipartFile新建controller内方法用于接收文件获取文件内容输入流,写入到本地磁盘@PostMapping("upload")publicResult<String>upload(MultipartFile......
  • Spring Data审计功能@CreatedDate、@CreatedBy、@LastModifiedDate、@LastModifiedBy
    在SpringJPA中,在实体bean的属性上加注解@CreatedDate、@CreatedBy、@LastModifiedDate、@LastModifiedBy,可以再实体插入数据库时进行设置值,这样以来,在对普通实体类的添加、修改操作时,就不用关心创建时间、更新时间这些信息了。本文以SpringBoot为例1、引入依赖<dependency><......
  • 深入理解spring注解之@ComponentScan注解
    今天主要从以下几个方面来介绍一下@ComponentScan注解:@ComponentScan注解是什么@ComponentScan注解的详细使用 1,@ComponentScan注解是什么 其实很简单,@ComponentScan主要就是定义扫描的路径从中找出标识了需要装配的类自动装配到spring的bean容器中  2,@Compone......
  • springboot整合mybatisplus
    1、引入依赖<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency&g......
  • springAOP的理解和具体实现
    AOP:Aspect-OrientedProgramming面向切面编程。其实是对OOP(面向对象编程)的补充和完善简单点描述就是OOP有些事做起来很麻烦,很不方便,而利用AOP是可以很快很便捷的处理。OOP引入封装、继承、多态等概念去公共对象的处理。但对于公共行为的时候就不好弄。此时就衍生出AOP。比如对......
  • springboot整合mybatis(纯注解版)
    springboot整合mybatis1、注解:参考表@ResponseBody:表示该方法的返回结果直接写入HTTPresponsebody中,一般在异步获取数据时使用,用于构建RESTful的api。在使用@RequestMapping后,返回值通常解析为跳转路径,加上@responsebody后返回结果不会被解析为跳转路径,而是直接写入HTTPres......
  • META-INF.spring
    META-INF/spring 目录通常用于存放 Spring 框架相关的配置文件。这个目录位于 Java 或 Spring 应用的类路径(classpath)中,使得 Spring 框架能够在应用启动时自动加载这些配置文件。在 Spring 应用中,META-INF/spring 目录可能包含以下类型的文件:1. Spring 配置文件:这些......
  • 面试官:SpringCloudGateway过滤器类型有哪些?
    在SpringCloudGateway中,过滤器是在请求到达目标服务之前或之后,执行某些特定操作的一种机制。例如,它可以实现对传入的请求进行验证、修改、日志记录、身份验证、流量控制等各种功能。在SpringCloudGateway中,过滤器总共分为以下两大类:局部过滤器:只作用于某一个路由(route......
  • Spring自带的这11个工具类,真香!
    前言最近有些小伙伴,希望我分享一些好用的工具类,帮他们提升开发效率。今天这篇文章专门跟大家一起总结一下,Spring框架本身自带的一些好用的工具类,希望对你会有所帮助。1Assert很多时候,我们需要在代码中做判断:如果不满足条件,则抛异常。有没有统一的封装呢?其实Spring给我们......
  • spring boot分页
    0verridepublicPair<List<ApplyDto>,Long>selectDbApplyList(ApplyDtoapplyDto){Longid=applyDto.getId();//办事顺信DbSupervisiondbSupervision=abSupervisionMapper.selectDbSupervisionByDbId(id);if(dbSupervision==null){thrownewRuntimeExcep......