本文中,咱们只给了部分示例代码。
若是你须要完整的代码,请点击: https://github.com/mengyunzhi/springBootSampleCode/tree/master/softDelete。
本文开发环境:spring-boot:2.0.3.RELEASE
+ java1.8
java
软删除:即不进行真正的删除操做。因为咱们实体间的约束性(外键)的存在,删除某些数据后,将致使其它的数据不完整。好比,计算机1801班的教师是张三,此时,咱们若是把张三删除掉,那么在查询计算机1801班时,因为张三不存了,因此就会报EntityNotFound的错误。固然了,在有外键约束的数据库中,若是张三是1801班的教师,那么咱们直接删除张三将报一个约束性的异常。也就是说:直接删除张三这个行为是没法执行的。git
但有些时候,咱们的确有删除的需求。好比说,有个员工离职了,而后咱们想在员工管理中删除该员工。可是:该员工因为在数据表中存在历史记录。好比咱们记录了17年第二学期的数据结构是张三教的。那么,因为约束性的存在,删除张三时就会报约束性错误。也就是说:出现了应该删除,但却删除不了的尴尬。github
这就用到了本文所提到的软删除,所谓软删除,就是说我并不真正的删除数据表中的数据,而是在给这条记录加一个是否删除的标记。spring
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
,进而发生了未找到关联实体的异常。sql
但事实是:实体虽然被删除,但实际在还在,咱们想将其应用到关联查询中。并不但愿其发生500 EntiyNotFound
异常。数据库
本文的方案实现了:api
500 EntiyNotFound
错误。@Where(clause = "deleted = false")
引发的,那么咱们弃用该注解。deleted = false
的查询。那么咱们新建一个接口,并继承jpa
的CrudRepository
,而后重写其查询相关的方法。在重写过程当中,加入deleted = false
的查询条件。新建KlassTest
, Klass
, Teacher
三个实体,新建BaseEntity
抽象类实体。其中KlassTest
用于演示使用@Where(clause = "deleted = false")
注解时发生的异常。springboot
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()
查询时,有被删除的关联数据时而发生的异常。app