您的位置:首页 > 编程语言 > Java开发

spring boot实现软删除的示例代码

2018-07-03 13:42 639 查看

本文开发环境: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<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
@Transactional
default 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();
}

新建仓库类

继承spring的CrudRepository。

/**
* 班级
* @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;
@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<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()查询时,有被删除的关联数据时而发生的异常。

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

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

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

您可能感兴趣的文章:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring boot 软删除