本文開發環境: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
異常。
本文的方案實現了:
- 可以成功的實現軟刪除。
- 再進行關聯刪除時,不發生500 entiynotfound錯誤。
解決方案
- 即然500是由于注解@where(clause = "deleted = false")引起的,那么我們棄用該注解。
- 我們需要在查詢時,加入deleted = false的查詢。那么我們新建一個接口,并繼承jpa的crudrepository,然后重寫其查詢相關的方法。在重寫過程中,加入deleted = false的查詢條件。
實施
初始化
新建clazztest, clazz, teacher
三個實體,新建baseentity
抽象類實體。其中clazztest
用于演示使用@where(clause = "deleted = false")
注解時發生的異常。
1
2
3
4
5
6
7
8
9
|
package com.mengyunzhi.springbootsamplecode.softdelete.entity; import javax.persistence.mappedsuperclass; @mappedsuperclass public abstract class baseentity { private boolean deleted = false ; // setter and getter } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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 } |
1
2
3
4
5
6
7
8
|
@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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
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。
1
2
3
4
5
6
|
/** * 班級 * @author panjie */ public interface klassrepository extends softdeletecrudrepository<klass, long >{ } |
1
2
|
public interface klasstestrepository extends softdeletecrudrepository<klasstest, long > { } |
1
2
|
public interface teacherrepository extends crudrepository<teacher, long > { } |
測試
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
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。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://segmentfault.com/a/1190000015459505