spring boot实现软删除
如果你需要完整的代碼,請點擊: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異常。
本文的方案實現了:
解決方案
實施
初始化
新建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(); }新建倉庫類
繼承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;@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()查詢時,有被刪除的關聯數據時而發生的異常。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的spring boot实现软删除的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RxJS实践,Vue如何集成RxJS
- 下一篇: 干货 | 金融级消息队列的演进 — 蚂蚁