热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

springboot实现软删除的示例代码

这篇文章主要介绍了springboot实现软删除的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

本文开发环境: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的查询。那么我们新建一个接口,并继承jpa的CrudRepository,然后重写其查询相关的方法。在重写过程中,加入deleted = false的查询条件。

实施

初始化

新建ClazzTest, Clazz, Teacher三个实体,新建BaseEntity抽象类实体。其中ClazzTest用于演示使用@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
  @GeneratedValue
  private 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 @GeneratedValue
  private 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 extends CrudRepository {
  @Override
  @Transactional
  @Query("select e from #{#entityName} e where e.id = ?1 and e.deleted = false")
  Optional findById(ID id);

  @Override
  @Transactional
  default boolean existsById(ID id) {
    return findById(id).isPresent();
  }

  @Override
  @Transactional
  @Query("select e from #{#entityName} e where e.deleted = false")
  Iterable findAll();

  @Override
  @Transactional
  @Query("select e from #{#entityName} e where e.id in ?1 and e.deleted = false")
  Iterable findAllById(Iterable ids);

  @Override
  @Transactional
  @Query("select count(e) from #{#entityName} e where e.deleted = false")
  long count();
}

新建仓库类

继承spring的CrudRepository。

/**
 * 班级
 * @author panjie
 */
public interface KlassRepository extends SoftDeleteCrudRepository{
}
public interface KlassTestRepository extends SoftDeleteCrudRepository {
}
public interface TeacherRepository extends CrudRepository {
}

测试

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;

  @Test
  public 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 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 teacherList = (List) 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()查询时,有被删除的关联数据时而发生的异常。

本文中,我们只给了部分示例代码。

如果你需要完整的代码,请点击:https://github.com/mengyunzhi/springBootSampleCode/tree/master/softDelete。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • 构建基于BERT的中文NL2SQL模型:一个简明的基准
    本文探讨了将自然语言转换为SQL语句(NL2SQL)的任务,这是人工智能领域中一项非常实用的研究方向。文章介绍了笔者在公司举办的首届中文NL2SQL挑战赛中的实践,该比赛提供了金融和通用领域的表格数据,并标注了对应的自然语言与SQL语句对,旨在训练准确的NL2SQL模型。 ... [详细]
  • golang常用库:配置文件解析库/管理工具viper使用
    golang常用库:配置文件解析库管理工具-viper使用-一、viper简介viper配置管理解析库,是由大神SteveFrancia开发,他在google领导着golang的 ... [详细]
  • 本文详细介绍了IBM DB2数据库在大型应用系统中的应用,强调其卓越的可扩展性和多环境支持能力。文章深入分析了DB2在数据利用性、完整性、安全性和恢复性方面的优势,并提供了优化建议以提升其在不同规模应用程序中的表现。 ... [详细]
  • 本文基于对相关论文和开源代码的研究,详细介绍了LOAM(激光雷达里程计与建图)的工作原理,并对其关键技术进行了分析。 ... [详细]
  • SQL中UPDATE SET FROM语句的使用方法及应用场景
    本文详细介绍了SQL中UPDATE SET FROM语句的使用方法,通过具体示例展示了如何利用该语句高效地更新多表关联数据。适合数据库管理员和开发人员参考。 ... [详细]
  • 资源推荐 | TensorFlow官方中文教程助力英语非母语者学习
    来源:机器之心。本文详细介绍了TensorFlow官方提供的中文版教程和指南,帮助开发者更好地理解和应用这一强大的开源机器学习平台。 ... [详细]
  • PHP 5.2.5 安装与配置指南
    本文详细介绍了 PHP 5.2.5 的安装和配置步骤,帮助开发者解决常见的环境配置问题,特别是上传图片时遇到的错误。通过本教程,您可以顺利搭建并优化 PHP 运行环境。 ... [详细]
  • 本文深入探讨 MyBatis 中动态 SQL 的使用方法,包括 if/where、trim 自定义字符串截取规则、choose 分支选择、封装查询和修改条件的 where/set 标签、批量处理的 foreach 标签以及内置参数和 bind 的用法。 ... [详细]
  • 本文介绍如何使用 Sortable.js 库实现元素的拖拽和位置交换功能。Sortable.js 是一个轻量级、无依赖的 JavaScript 库,支持拖拽排序、动画效果和多种插件扩展。通过简单的配置和事件处理,可以轻松实现复杂的功能。 ... [详细]
  • 探讨一个显示数字的故障计算器,它支持两种操作:将当前数字乘以2或减去1。本文将详细介绍如何用最少的操作次数将初始值X转换为目标值Y。 ... [详细]
  • 本文详细介绍了如何在 Linux 平台上安装和配置 PostgreSQL 数据库。通过访问官方资源并遵循特定的操作步骤,用户可以在不同发行版(如 Ubuntu 和 Red Hat)上顺利完成 PostgreSQL 的安装。 ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 本文详细介绍了如何在 Spring Boot 应用中通过 @PropertySource 注解读取非默认配置文件,包括配置文件的创建、映射类的设计以及确保 Spring 容器能够正确加载这些配置的方法。 ... [详细]
  • 本文总结了汇编语言中第五至第八章的关键知识点,涵盖间接寻址、指令格式、安全编程空间、逻辑运算指令及数据重复定义等内容。通过详细解析这些内容,帮助读者更好地理解和应用汇编语言的高级特性。 ... [详细]
author-avatar
梁言一聚
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有