本文中,我们只给了部分示例代码。
如果你需要完整的代码,请点击:https://github.com/mengyunzhi/springBootSampleCode/tree/master/softDelete。

本文开发环境:spring-boot:2.0.3.RELEASE + java1.8

WHY TO DO

软删除:即不进行真正的删除操作。由于我们实体间的约束性(外键)的存在,删除某些数据后,将导致其它的数据不完整。比如,计算机1801班的教师是张三,此时,我们如果把张三删除掉,那么在查询计算机1801班时,由于张三不存了,所以就会报EntityNotFound的错误。当然了,在有外键约束的数据库中,如果张三是1801班的教师,那么我们直接删除张三将报一个约束性的异常。也就是说:直接删除张三这个行为是无法执行的。

但有些时候,我们的确有删除的需求。比如说,有个员工离职了,然后我们想在员工管理中删除该员工。但是:该员工由于在数据表中存在历史记录。比如我们记录了17年第二学期的数据结构是张三教的。那么,由于约束性的存在,删除张三时就会报约束性错误。也就是说:出现了应该删除,但却删除不了的尴尬。

这就用到了本文所提到的软删除,所谓软删除,就是说我并不真正的删除数据表中的数据,而是在给这条记录加一个是否删除的标记。

spring jpa是支持软删除的,我们可以找到较多质量不错的文章来解决这个问题。大体步骤为:1. 加入@SqlDelete("update xxxx set deleted = 1 where id = ?")。2.加入@Where(clause = "deleted = false")的注解。但这个解决方案并不完美。具体表现在:
我们还以张三是1801班的教师举例。
加入注解后,我们的确是可以做到可以成功的删除张三了,删除操作后,我们查看数据表,张三的记录的确也还在。但此时,如果我们进行all或是page查询,将得到一个500 EntiyNotFound错误。这是由于在all查询时,jpa自动加入了@Where中的的查询参数,由于关联数据的deleted = true,进而发生了未找到关联实体的异常。

但事实是:实体虽然被删除,但实际在还在,我们想将其应用到关联查询中。并不希望其发生500 EntiyNotFound异常。

本文的方案实现了:

  1. 可以成功的实现软删除。
  2. 再进行关联删除时,不发生500 EntiyNotFound错误。

解决方案

  1. 即然500是由于注解@Where(clause = "deleted = false")引起的,那么我们弃用该注解。
  2. 我们需要在查询时,加入deleted = false的查询。那么我们新建一个接口,并继承jpaCrudRepository,然后重写其查询相关的方法。在重写过程中,加入deleted = false的查询条件。

实施

初始化

新建KlassTest, Klass, Teacher三个实体,新建BaseEntity抽象类实体。其中KlassTest用于演示使用@Where(clause = "deleted = false")注解时发生的异常。

package com.mengyunzhi.springbootsamplecode.softdelete.entity;import javax.persistence.MappedSuperclass;@MappedSuperclass
public abstract class BaseEntity {private Boolean deleted = false;// setter and getter
}
package com.mengyunzhi.springbootsamplecode.softdelete.entity;import org.hibernate.annotations.SQLDelete;import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;/*** 班级*/
@Entity
@SQLDelete(sql = "update `klass` set deleted = 1 where id = ?")
public class Klass extends BaseEntity {@Id@GeneratedValueprivate Long id;private String name;// setter and getter
}
@Entity
@SQLDelete(sql = "update `klass_test` set deleted = 1 where id = ?")
@Where(clause = "deleted = false")
public class KlassTest extends BaseEntity {@Id @GeneratedValueprivate Long id;private String name;
}

重写CrudRepository

package com.mengyunzhi.springbootsamplecode.softdelete.core;import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.NoRepositoryBean;import javax.transaction.Transactional;
import java.util.Optional;/*** 应用软删除* 默认的@Where(clause = "deleted = 0")会导致hibernate内部进行关联查询时,发生ObjectNotFound的异常* 在此重新定义接口* 参考:https://stackoverflow.com/questions/19323557/handling-soft-deletes-with-spring-jpa/22202469* @author 河北工业大学 梦云智软件开发团队*/
@NoRepositoryBean
public interface SoftDeleteCrudRepository<T, ID> extends CrudRepository<T, ID> {@Override@Transactional@Query("select e from #{#entityName} e where e.id = ?1 and e.deleted = false")Optional<T> findById(ID id);@Override@Transactionaldefault boolean existsById(ID id) {return findById(id).isPresent();}@Override@Transactional@Query("select e from #{#entityName} e where e.deleted = false")Iterable<T> findAll();@Override@Transactional@Query("select e from #{#entityName} e where e.id in ?1 and e.deleted = false")Iterable<T> findAllById(Iterable<ID> ids);@Override@Transactional@Query("select count(e) from #{#entityName} e where e.deleted = false")long count();
}

新建仓库类

继承springCrudRepository

/*** 班级* @author panjie*/
public interface KlassRepository extends SoftDeleteCrudRepository<Klass, Long>{
}
public interface KlassTestRepository extends SoftDeleteCrudRepository<KlassTest, Long> {
}
public interface TeacherRepository extends CrudRepository<Teacher, Long> {
}

测试

package com.mengyunzhi.springbootsamplecode.softdelete.repository;import com.mengyunzhi.springbootsamplecode.softdelete.entity.Klass;
import com.mengyunzhi.springbootsamplecode.softdelete.entity.KlassTest;
import com.mengyunzhi.springbootsamplecode.softdelete.entity.Teacher;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.orm.jpa.JpaObjectRetrievalFailureException;
import org.springframework.test.context.junit4.SpringRunner;import java.util.List;
import java.util.Optional;/*** @author panjie*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class TeacherRepositoryTest {private final static Logger logger = LoggerFactory.getLogger(TeacherRepositoryTest.class);@Autowired KlassRepository klassRepository;@Autowired KlassTestRepository klassTestRepository;@Autowired TeacherRepository teacherRepository;@Testpublic void findById() {logger.info("新建一个有Klass和KlassTest的教师");Klass klass = new Klass();klassRepository.save(klass);KlassTest klassTest = new KlassTest();klassTestRepository.save(klassTest);Teacher teacher = new Teacher();teacher.setKlass(klass);teacher.setKlassTest(klassTest);teacherRepository.save(teacher);logger.info("查找教师,断言查找了实体,并且不发生异常");Optional<Teacher> teacherOptional = teacherRepository.findById(teacher.getId());Assertions.assertThat(teacherOptional.get()).isNotNull();logger.info("删除关联的Klass, 再查找教师实体,断言查找到了实体,不发生异常。断言教师实体中,仍然存在已经删除的Klass实体");klassRepository.deleteById(klass.getId());teacherOptional = teacherRepository.findById(teacher.getId());Assertions.assertThat(teacherOptional.get()).isNotNull();Assertions.assertThat(teacherOptional.get().getKlass().getId()).isEqualTo(klass.getId());logger.info("查找教师列表,不发生异常。断言教师实体中,存在已删除的Klass实体记录");List<Teacher> teacherList = (List<Teacher>) teacherRepository.findAll();for (Teacher teacher1 : teacherList) {Assertions.assertThat(teacher1.getKlass().getId()).isEqualTo(klass.getId());}logger.info("删除关联的KlassTest,再查找教师实体, 断言找到了删除的klassTest");klassTestRepository.deleteById(klassTest.getId());teacherOptional = teacherRepository.findById(teacher.getId());Assertions.assertThat(teacherOptional.get()).isNotNull();Assertions.assertThat(teacherOptional.get().getKlassTest().getId()).isEqualTo(klassTest.getId());logger.info("再查找教师列表,断言将发生JpaObjectRetrievalFailureException(EntityNotFound 异常被捕获后,封装抛出)异常");Boolean catchException = false;try {teacherRepository.findAll();} catch (JpaObjectRetrievalFailureException e) {catchException = true;}Assertions.assertThat(catchException).isTrue();}}

总结

使用默认的@SqlDelete以及@Where注解时,jpa data能够很好的处理findById()方法,但却未能很好的处理findAll()方法。在此,我们通过重写CrunRepository的方法,实现了,将进行基本的查询时,使用我们自定义的加入了deleted = true的方法。而当jpa进行关联查询时,由于我们未设置@Where注解,所以将查询出所有的数据,进而避免了当进行findAll()查询时,有被删除的关联数据时而发生的异常。

spring boot实现软删除相关推荐

  1. Spring Boot登录选项快速指南

    "我喜欢编写身份验证和授权代码." 〜从来没有Java开发人员. 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证. 在本文中 ...

  2. aws 删除ec2实例_如何在AWS EC2实例上部署Spring Boot应用程序

    aws 删除ec2实例 你好朋友, 在本教程中,我们将看到如何在AWS EC2实例上部署Spring Boot应用程序. 这是我们将要执行的步骤. 1.使用Spring Boot Initialise ...

  3. Spring Boot删除嵌入式Tomcat服务器,启用Jetty服务器

    快速指南,在Spring Boot应用程序中排除嵌入式tomcat服务器并添加Jetty Server. 配置删除tomcat并添加Jetty Server. 1.简介 在本教程中,我们将学习如何从S ...

  4. Spring Boot三合一实验(添加人员,修改人员,删除人员)

    目录 基本理论 演示 源码 基本理论 添加和编辑的理论已经在另外一篇博文中体现出来了! Spring Boot修改添加界面二合一 https://blog.csdn.net/qq78442761/ar ...

  5. spring boot项目自定义数据源,mybatisplus分页、逻辑删除无效解决方法

    Spring Boot项目中数据源的配置可以通过两种方式实现: 1.application.yml或者application.properties配置 2.注入DataSource及SqlSessio ...

  6. fastdfs删除过期文件_Spring Boot 系列:使用 Spring Boot 集成 FastDFS

    这篇文章我们介绍如何使用 Spring Boot 将文件上传到分布式文件系统 FastDFS 中. 这个项目会在上一个项目的基础上进行构建. 1.pom 包配置 org.csource fastdfs ...

  7. 【中软国际实习】Day 11:Spring Boot:TNews项目实现新闻评论

    文章目录 新闻评论 实体类 Controller层 Service层 前端界面 小结 新闻评论 实体类 @Entity @Table(name = "t_comment") pub ...

  8. MyBatis-Plus,搭配 Spring Boot 使用,一篇就够了

    以下文章来源方志朋的博客,回复"666"获面试宝典 MyBatis-plus 是一款 Mybatis 增强工具,用于简化开发,提高效率.下文使用缩写 mp来简化表示 MyBatis ...

  9. Spring Boot+Gradle+ MyBatisPlus3.x搭建企业级的后台分离框架

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:任务加油站 原文:toutiao.com/i6861 ...

最新文章

  1. JVM虚拟机参数配置官方文档
  2. CENTOS/RHEL 7 系统中设置SYSTEMD SERVICE的ULIMIT资源限制
  3. java按升序冒泡排序_Java实现冒泡排序算法
  4. 【数理知识】《数值分析》李庆扬老师-第7章-非线性方程与方程组的数值解法
  5. 观察者模式--模拟3D彩票公众号
  6. 获取eclipse 运行时,bundle的相对路径
  7. jQuery对HTML进行添加元素
  8. oracle数据库怎么删除数据库,oracle数据库如何删除 oracle数据库删除方法
  9. boost python导出c++ map_使用Boost生成的Python模块:与C++签名不匹配
  10. python yield 简单用法_通过实例简单了解python yield使用方法
  11. dedecms 在模板里引入php文件夹,dedecms如何添加并引入php文件
  12. Django框架 之 Ajax
  13. 使用命令行刷新Magento索引管理 Rebuilt Magento Indexes in terminal with php-cli
  14. C++ 虚函数 纯虚函数 抽象类 代码示例
  15. 截取字符串_妙用字符串的替换和截取让Shell脚本精准得到你心中的那个“她”...
  16. 大数据之多数据源综合管理系统:数据源配置管理
  17. linux 命令 下载 sz,linux - rz/sz 命令上传下载
  18. SE11字段过多时怎么快速建表
  19. CGO 之 Dll调用
  20. android 的一些编译问题

热门文章

  1. 如何通过c语言获取ipv6邻居表,急求在vc++6.0中获取IPV6地址的方法,高手请进,谢谢!!...
  2. python3.7.2安装包_Win10下python 2.7与python 3.7双环境安装教程图解
  3. laravel修改storage目录和bootstrap目录
  4. 无法启动程序 计算机丢失QT,Win7系统打开程序提示计算机中丢失qt5core.dll如何解决...
  5. python3.6与3.7有什么区别_Python3.6.6和Python3.7.0的坑
  6. python相机拍照显示时间_python让图片按照exif信息里的创建时间进行排序的方法...
  7. 【考前必知】软考考前注意事项
  8. 备考信息系统项目管理师5点必过经验
  9. 2021年5月信息系统项目管理师案例分析真题+视频讲解(3)
  10. 笔记-高项案例题-2018年上-质量管理