本文開發(fā)環(huán)境:spring-boot:2.0.3.RELEASE + java1.8
10年積累的做網(wǎng)站、成都網(wǎng)站建設(shè)經(jīng)驗(yàn),可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先網(wǎng)站制作后付款的網(wǎng)站建設(shè)流程,更有坡頭免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
WHY TO DO
軟刪除:即不進(jìn)行真正的刪除操作。由于我們實(shí)體間的約束性(外鍵)的存在,刪除某些數(shù)據(jù)后,將導(dǎo)致其它的數(shù)據(jù)不完整。比如,計算機(jī)1801班的教師是張三,此時,我們?nèi)绻褟埲齽h除掉,那么在查詢計算機(jī)1801班時,由于張三不存了,所以就會報EntityNotFound的錯誤。當(dāng)然了,在有外鍵約束的數(shù)據(jù)庫中,如果張三是1801班的教師,那么我們直接刪除張三將報一個約束性的異常。也就是說:直接刪除張三這個行為是無法執(zhí)行的。
但有些時候,我們的確有刪除的需求。比如說,有個員工離職了,然后我們想在員工管理中刪除該員工。但是:該員工由于在數(shù)據(jù)表中存在歷史記錄。比如我們記錄了17年第二學(xué)期的數(shù)據(jù)結(jié)構(gòu)是張三教的。那么,由于約束性的存在,刪除張三時就會報約束性錯誤。也就是說:出現(xiàn)了應(yīng)該刪除,但卻刪除不了的尷尬。
這就用到了本文所提到的軟刪除,所謂軟刪除,就是說我并不真正的刪除數(shù)據(jù)表中的數(shù)據(jù),而是在給這條記錄加一個是否刪除的標(biāo)記。
spring jpa是支持軟刪除的,我們可以找到較多質(zhì)量不錯的文章來解決這個問題。大體步驟為:1. 加入@SqlDelete("update xxxx set deleted = 1 where id = ?")。2.加入@Where(clause = "deleted = false")
的注解。但這個解決方案并不完美。具體表現(xiàn)在:
我們還以張三是1801班的教師舉例。
加入注解后,我們的確是可以做到可以成功的刪除張三了,刪除操作后,我們查看數(shù)據(jù)表,張三的記錄的確也還在。但此時,如果我們進(jìn)行all
或是page
查詢,將得到一個500 EntiyNotFound
錯誤。這是由于在all查詢時,jpa自動加入了@Where
中的的查詢參數(shù),由于關(guān)聯(lián)數(shù)據(jù)的deleted = true
,進(jìn)而發(fā)生了未找到關(guān)聯(lián)實(shí)體的異常。
但事實(shí)是:實(shí)體雖然被刪除,但實(shí)際在還在,我們想將其應(yīng)用到關(guān)聯(lián)查詢中。并不希望其發(fā)生500 EntiyNotFound
異常。
本文的方案實(shí)現(xiàn)了:
解決方案
實(shí)施
初始化
新建ClazzTest, Clazz, Teacher
三個實(shí)體,新建BaseEntity
抽象類實(shí)體。其中ClazzTest
用于演示使用@Where(clause = "deleted = false")
注解時發(fā)生的異常。
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; /** * 應(yīng)用軟刪除 * 默認(rèn)的@Where(clause = "deleted = 0")會導(dǎo)致hibernate內(nèi)部進(jìn)行關(guān)聯(lián)查詢時,發(fā)生ObjectNotFound的異常 * 在此重新定義接口 * 參考:https://stackoverflow.com/questions/19323557/handling-soft-deletes-with-spring-jpa/22202469 * @author 河北工業(yè)大學(xué) 夢云智軟件開發(fā)團(tuán)隊(duì) */ @NoRepositoryBean public interface SoftDeleteCrudRepositoryextends 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("查找教師,斷言查找了實(shí)體,并且不發(fā)生異常"); OptionalteacherOptional = teacherRepository.findById(teacher.getId()); Assertions.assertThat(teacherOptional.get()).isNotNull(); logger.info("刪除關(guān)聯(lián)的Klass, 再查找教師實(shí)體,斷言查找到了實(shí)體,不發(fā)生異常。斷言教師實(shí)體中,仍然存在已經(jīng)刪除的Klass實(shí)體"); 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("查找教師列表,不發(fā)生異常。斷言教師實(shí)體中,存在已刪除的Klass實(shí)體記錄"); List teacherList = (List ) teacherRepository.findAll(); for (Teacher teacher1 : teacherList) { Assertions.assertThat(teacher1.getKlass().getId()).isEqualTo(klass.getId()); } logger.info("刪除關(guān)聯(lián)的KlassTest,再查找教師實(shí)體, 斷言找到了刪除的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("再查找教師列表,斷言將發(fā)生JpaObjectRetrievalFailureException(EntityNotFound 異常被捕獲后,封裝拋出)異常"); Boolean catchException = false; try { teacherRepository.findAll(); } catch (JpaObjectRetrievalFailureException e) { catchException = true; } Assertions.assertThat(catchException).isTrue(); } }
總結(jié)
使用默認(rèn)的@SqlDelete以及@Where注解時,jpa data能夠很好的處理findById()方法,但卻未能很好的處理findAll()方法。在此,我們通過重寫CrunRepository的方法,實(shí)現(xiàn)了,將進(jìn)行基本的查詢時,使用我們自定義的加入了deleted = true的方法。而當(dāng)jpa進(jìn)行關(guān)聯(lián)查詢時,由于我們未設(shè)置@Where注解,所以將查詢出所有的數(shù)據(jù),進(jìn)而避免了當(dāng)進(jìn)行findAll()查詢時,有被刪除的關(guān)聯(lián)數(shù)據(jù)時而發(fā)生的異常。
本文中,我們只給了部分示例代碼。
如果你需要完整的代碼,請點(diǎn)擊:https://github.com/mengyunzhi/springBootSampleCode/tree/master/softDelete。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。