Spring Data JPA中的Specification動(dòng)態(tài)查詢詳解
寫在前面:刷完Spring Data JPA的課后,發(fā)現(xiàn)Specification動(dòng)態(tài)查詢還挺有意思的,還應(yīng)用到了規(guī)約設(shè)計(jì)模式,在此記錄下學(xué)習(xí)過程和見解。
一、應(yīng)用場景
1. 簡介
有時(shí)我們在查詢某個(gè)實(shí)體的時(shí)候,給定的條件是不固定的,這時(shí)就需要?jiǎng)討B(tài)構(gòu)建相應(yīng)的查詢語句,在Spring Data JPA中可以通過JpaSpecificationExecutor接口查詢。相比JPQL,其優(yōu)勢是類型安全,更加的面向?qū)ο蟆?/p>
Specification是一個(gè)設(shè)計(jì)模式,常常用于企業(yè)級應(yīng)用開發(fā)中,其主要目的是將業(yè)務(wù)規(guī)則從業(yè)務(wù)邏輯中分離出來。在數(shù)據(jù)查詢方面,Specification可以定義復(fù)雜的查詢,使其更易于重用和測試。
2. 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 動(dòng)態(tài)查詢:Specification允許你在運(yùn)行時(shí)構(gòu)建查詢。你可以基于用戶的輸入或程序的狀態(tài)動(dòng)態(tài)地選擇查詢的條件、排序或分組。
- 復(fù)用:你可以定義一組基本的Specification,然后在不同的查詢中重用它們。這使得你的代碼更加簡潔,也更易于測試和維護(hù)。
- 組合:你可以通過邏輯運(yùn)算符(如AND和OR)來組合Specification。這使得你可以輕松地構(gòu)建復(fù)雜的查詢,而無需編寫復(fù)雜的SQL語句。
缺點(diǎn):
- 學(xué)習(xí)曲線:對于新手來說,理解和使用Specification可能有一定的難度。你需要對JPA Criteria API有一定的了解,而這個(gè)API本身也相當(dāng)復(fù)雜。
- 性能:Specification是基于JPA Criteria API的,而這個(gè)API生成的SQL語句可能并不是最優(yōu)的。如果你需要執(zhí)行一些特別復(fù)雜或需要高性能的查詢,直接編寫SQL可能會更好。
- 靈活性:雖然Specification提供了大量的功能,但它仍然有一些限制。例如,它不支持JOIN ON子句,也不支持某些數(shù)據(jù)庫特有的功能。
3.mybatis或者mybatisPlus和Specification動(dòng)態(tài)查詢比較(對標(biāo))
MyBatis或MyBatis Plus并沒有直接對應(yīng)于Spring Data JPA中的Specification動(dòng)態(tài)查詢的功能,但是,通過其強(qiáng)大的動(dòng)態(tài)SQL功能,可以實(shí)現(xiàn)類似的效果。在MyBatis中,可以使用 <if>
標(biāo)簽來動(dòng)態(tài)的構(gòu)建SQL查詢。這允許你根據(jù)傳入?yún)?shù)的值動(dòng)態(tài)地添加或移除查詢的一部分。
二、源碼解析
Specification接口是Spring Data JPA庫中的一部分。這個(gè)接口定義了一個(gè)toPredicate
方法,該方法接收一個(gè)Root
、CriteriaQuery
和CriteriaBuilder
,并返回一個(gè)Predicate
。Predicate
是JPA Criteria API中的一個(gè)接口,用于定義查詢的條件。
- root:查詢的根對象(查詢的任何屬性都可以從根對象中獲取)
- CriteriaQuery:頂層查詢對象,自定義查詢方式(了解:一般不用)
- CriteriaBuilder:查詢的構(gòu)造器,封裝了很多的查詢條件
在Spring Data JPA中,這個(gè)接口主要被用于
JpaSpecificationExecutor
接口,這個(gè)接口定義了一些用于執(zhí)行Specification查詢的方法,如findAll(Specification<T> spec)
。
JpaSpecificationExecutor
接口的實(shí)現(xiàn)在SimpleJpaRepository
類中。例如,findAll(Specification<T> spec)
方法的實(shí)現(xiàn)如下:
@Override public List<T> findAll(@Nullable Specification<T> spec) { TypedQuery<T> query = getQuery(spec, Sort.unsorted()); return query.getResultList(); }
在這個(gè)方法中,首先調(diào)用了getQuery
方法來創(chuàng)建一個(gè)TypedQuery
,然后執(zhí)行這個(gè)查詢并返回結(jié)果。
getQuery
方法的實(shí)現(xiàn)如下:
protected TypedQuery<T> getQuery(@Nullable Specification<T> spec, Sort sort) { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<T> query = builder.createQuery(getDomainClass()); Root<T> root = applySpecificationToCriteria(spec, query); query.select(root); if (sort.isSorted()) { query.orderBy(toOrders(sort, root, builder)); } return applyRepositoryMethodMetadata(entityManager.createQuery(query)); }
在這個(gè)方法中,首先創(chuàng)建了一個(gè)CriteriaBuilder
和CriteriaQuery
,然后調(diào)用了applySpecificationToCriteria
方法來應(yīng)用Specification到CriteriaQuery
上,然后選擇查詢的結(jié)果,如果有排序的需求,就添加排序條件,最后創(chuàng)建并返回TypedQuery
。
applySpecificationToCriteria
方法的實(shí)現(xiàn)如下:
private Root<T> applySpecificationToCriteria(@Nullable Specification<T> spec, CriteriaQuery<?> query) { Root<T> root = query.from(getDomainClass()); if (spec == null) { return root; } CriteriaBuilder builder = entityManager.getCriteriaBuilder(); Predicate predicate = spec.toPredicate(root, query, builder); if (predicate != null) { query.where(predicate); } return root; }
在這個(gè)方法中,首先創(chuàng)建了一個(gè)Root
,然后如果有Specification的話,就調(diào)用toPredicate
方法來創(chuàng)建一個(gè)Predicate
,然后添加這個(gè)Predicate
到CriteriaQuery
的where條件中。
這就是Spring Data JPA中Specification動(dòng)態(tài)查詢的基本實(shí)現(xiàn)。在實(shí)際的應(yīng)用中,我們只需要實(shí)現(xiàn)Specification接口,并提供一個(gè)
toPredicate
方法來定義我們的查詢規(guī)則,Spring Data JPA就會自動(dòng)地為我們執(zhí)行查詢。
三、規(guī)約模式
規(guī)約模式(Specification Pattern)是一種特殊的設(shè)計(jì)模式,最早由Eric Evans在他的《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》一書中提出。規(guī)約模式的主要思想是將業(yè)務(wù)規(guī)則從業(yè)務(wù)對象中分離出來,這樣就可以將這些規(guī)則獨(dú)立地重用和組合。一個(gè)規(guī)約(Specification)是一個(gè)獨(dú)立的業(yè)務(wù)規(guī)則,它通常會實(shí)現(xiàn)一個(gè)方法(在Java中通常是
isSatisfiedBy
),該方法接收一個(gè)業(yè)務(wù)對象,然后檢查這個(gè)對象是否滿足規(guī)約的條件。
例如,假設(shè)我們有一個(gè)Customer
類,我們需要檢查一個(gè)客戶是否滿足“是VIP客戶”和“注冊超過一年”的規(guī)則。首先,我們可以定義一個(gè)IsVip
規(guī)約和一個(gè)IsRegisteredForMoreThanOneYear
規(guī)約:
public class IsVip implements Specification<Customer> { @Override public boolean isSatisfiedBy(Customer customer) { return customer.isVip(); } } public class IsRegisteredForMoreThanOneYear implements Specification<Customer> { @Override public boolean isSatisfiedBy(Customer customer) { return customer.getRegisteredDate().isBefore(LocalDate.now().minusYears(1)); } }
然后,我們可以將這兩個(gè)規(guī)約組合起來,檢查一個(gè)客戶是否滿足這兩個(gè)條件:
Specification<Customer> spec = new IsVip().and(new IsRegisteredForMoreThanOneYear()); boolean isSatisfied = spec.isSatisfiedBy(customer);
我們還可以使用or
方法來組合規(guī)約,檢查一個(gè)客戶是否滿足這兩個(gè)條件中的任意一個(gè):
Specification<Customer> spec = new IsVip().or(new IsRegisteredForMoreThanOneYear()); boolean isSatisfied = spec.isSatisfiedBy(customer);
在這個(gè)例子中,我們將每個(gè)業(yè)務(wù)規(guī)則封裝為一個(gè)單獨(dú)的規(guī)約,然后使用and
和or
方法將這些規(guī)約組合起來。這樣做的好處是我們可以將復(fù)雜的業(yè)務(wù)規(guī)則分解為多個(gè)簡單的規(guī)約,這些規(guī)約可以獨(dú)立地重用和測試。同時(shí),我們也可以在運(yùn)行時(shí)動(dòng)態(tài)地組合規(guī)約,從而實(shí)現(xiàn)動(dòng)態(tài)的業(yè)務(wù)規(guī)則。
四、實(shí)際應(yīng)用
單個(gè)條件查詢
/** * 根據(jù)條件,查詢單個(gè)對象 * */ @Test public void testSpec() { //匿名內(nèi)部類 /** * 自定義查詢條件 * 1.實(shí)現(xiàn)Specification接口(提供泛型:查詢的對象類型) * 2.實(shí)現(xiàn)toPredicate方法(構(gòu)造查詢條件) * 3.需要借助方法參數(shù)中的兩個(gè)參數(shù)( * root:獲取需要查詢的對象屬性 * CriteriaBuilder:構(gòu)造查詢條件的,內(nèi)部封裝了很多的查詢條件(模糊匹配,精準(zhǔn)匹配) * ) * 案例:根據(jù)客戶名稱查詢,查詢客戶名為傳智播客的客戶 * 查詢條件 * 1.查詢方式 * cb對象 * 2.比較的屬性名稱 * root對象 * */ Specification<Customer> spec = new Specification<Customer>() { @Override public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) { //1.獲取比較的屬性 Path<Object> custName = root.get("custId"); //2.構(gòu)造查詢條件 : select * from cst_customer where cust_name = '傳智播客' /** * 第一個(gè)參數(shù):需要比較的屬性(path對象) * 第二個(gè)參數(shù):當(dāng)前需要比較的取值 */ Predicate predicate = cb.equal(custName, "傳智播客");//進(jìn)行精準(zhǔn)的匹配 (比較的屬性,比較的屬性的取值) return predicate; } }; Customer customer = customerDao.findOne(spec); System.out.println(customer); }
多條件查詢
/** * 多條件查詢 * 案例:根據(jù)客戶名(傳智播客)和客戶所屬行業(yè)查詢(it教育) * */ @Test public void testSpec1() { /** * root:獲取屬性 * 客戶名 * 所屬行業(yè) * cb:構(gòu)造查詢 * 1.構(gòu)造客戶名的精準(zhǔn)匹配查詢 * 2.構(gòu)造所屬行業(yè)的精準(zhǔn)匹配查詢 * 3.將以上兩個(gè)查詢聯(lián)系起來 */ Specification<Customer> spec = new Specification<Customer>() { @Override public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) { Path<Object> custName = root.get("custName");//客戶名 Path<Object> custIndustry = root.get("custIndustry");//所屬行業(yè) //構(gòu)造查詢 //1.構(gòu)造客戶名的精準(zhǔn)匹配查詢 Predicate p1 = cb.equal(custName, "傳智播客");//第一個(gè)參數(shù),path(屬性),第二個(gè)參數(shù),屬性的取值 //2..構(gòu)造所屬行業(yè)的精準(zhǔn)匹配查詢 Predicate p2 = cb.equal(custIndustry, "it教育"); //3.將多個(gè)查詢條件組合到一起:組合(滿足條件一并且滿足條件二:與關(guān)系,滿足條件一或滿足條件二即可:或關(guān)系) Predicate and = cb.and(p1, p2);//以與的形式拼接多個(gè)查詢條件 // cb.or();//以或的形式拼接多個(gè)查詢條件 return and; } }; Customer customer = customerDao.findOne(spec); System.out.println(customer); }
模糊查詢
/** * 案例:完成根據(jù)客戶名稱的模糊匹配,返回客戶列表 * 客戶名稱以 '傳智播客‘ 開頭 * * equal :直接的到path對象(屬性),然后進(jìn)行比較即可 * gt,lt,ge,le,like : 得到path對象,根據(jù)path指定比較的參數(shù)類型,再去進(jìn)行比較 * 指定參數(shù)類型:path.as(類型的字節(jié)碼對象) */ @Test public void testSpec3() { //構(gòu)造查詢條件 Specification<Customer> spec = new Specification<Customer>() { @Override public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) { //查詢屬性:客戶名 Path<Object> custName = root.get("custName"); //查詢方式:模糊匹配 Predicate like = cb.like(custName.as(String.class), "傳智播客%"); return like; } }; // List<Customer> list = customerDao.findAll(spec); // for (Customer customer : list) { // System.out.println(customer); // } //添加排序 //創(chuàng)建排序?qū)ο?需要調(diào)用構(gòu)造方法實(shí)例化sort對象 //第一個(gè)參數(shù):排序的順序(倒序,正序) // Sort.Direction.DESC:倒序 // Sort.Direction.ASC : 升序 //第二個(gè)參數(shù):排序的屬性名稱 Sort sort = new Sort(Sort.Direction.DESC,"custId"); List<Customer> list = customerDao.findAll(spec, sort); for (Customer customer : list) { System.out.println(customer); } }
分頁查詢
/** * 分頁查詢 * Specification: 查詢條件 * Pageable:分頁參數(shù) * 分頁參數(shù):查詢的頁碼,每頁查詢的條數(shù) * findAll(Specification,Pageable):帶有條件的分頁 * findAll(Pageable):沒有條件的分頁 * 返回:Page(springDataJpa為我們封裝好的pageBean對象,數(shù)據(jù)列表,共條數(shù)) */ @Test public void testSpec4() { Specification spec = null; //PageRequest對象是Pageable接口的實(shí)現(xiàn)類 /** * 創(chuàng)建PageRequest的過程中,需要調(diào)用他的構(gòu)造方法傳入兩個(gè)參數(shù) * 第一個(gè)參數(shù):當(dāng)前查詢的頁數(shù)(從0開始) * 第二個(gè)參數(shù):每頁查詢的數(shù)量 */ Pageable pageable = new PageRequest(0,2); //分頁查詢 Page<Customer> page = customerDao.findAll(null, pageable); System.out.println(page.getContent()); //得到數(shù)據(jù)集合列表 System.out.println(page.getTotalElements());//得到總條數(shù) System.out.println(page.getTotalPages());//得到總頁數(shù) }
到此這篇關(guān)于Spring Data JPA中的Specification動(dòng)態(tài)查詢詳解的文章就介紹到這了,更多相關(guān)Spring Data JPA Specification動(dòng)態(tài)查詢內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- IntelliJ?IDEA?2022.2最新版本激活教程(親測可用版)永久激活工具分享
- IntelliJ?IDEA?2021.3永久最新激活至2099年(親測有效)
- 最新IntelliJ IDEA 2020.2永久激活碼(親測有效)
- IntelliJ?IDEA?2020.2.3永久破解激活教程(親測有效)
- IntelliJ IDEA 2019.3激活破解的詳細(xì)方法(親測有效,可激活至 2089 年)
- IntelliJ IDEA 2020最新注冊碼(親測有效,可激活至 2089 年)
- IntelliJ IDEA 2020最新激活碼(親測有效,可激活至 2089 年)
- IntelliJ?IDEA?2024.2?發(fā)布新功能介紹Spring?Data?JPA即時(shí)查詢、自動(dòng)補(bǔ)全cron表達(dá)式
相關(guān)文章
springboot配置Jackson返回統(tǒng)一默認(rèn)值的實(shí)現(xiàn)示例
在項(xiàng)目開發(fā)中,我們返回的數(shù)據(jù)或者對象沒有的時(shí)候一般直接返回的null,那么如何返回統(tǒng)一默認(rèn)值,感興趣的可以了解一下2021-07-07@Autowired 自動(dòng)注入接口失敗的原因及解決
這篇文章主要介紹了@Autowired 自動(dòng)注入接口失敗的原因及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02SpringBoot實(shí)現(xiàn)配置文件加密的方案分享
項(xiàng)目的數(shù)據(jù)庫密碼、redis 密碼等明文展示在配置文件中會有潛在的風(fēng)險(xiǎn),因此采用合適的安全防護(hù)措施是有必要的,下面小編就為大家介紹一下SpringBoot中配置文件加密的方法,希望對大家有所幫助2023-11-11Java框架搭建之Maven、Mybatis、Spring MVC整合搭建(圖文)
這篇文章主要介紹了Java框架搭建之Maven、Mybatis、Spring MVC整合搭建(圖文),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-12-12Java圖像之自定義角度旋轉(zhuǎn)(實(shí)例)
這篇文章主要介紹了Java圖像之自定義角度旋轉(zhuǎn)(實(shí)例),需要的朋友可以參考下2017-09-09Java Spring使用hutool的HttpRequest發(fā)送請求的幾種方式
文章介紹了Hutool庫中用于發(fā)送HTTP請求的工具,包括添加依賴、發(fā)送GET和POST請求的方法,以及GET請求的不同參數(shù)傳遞方式,感興趣的朋友跟隨小編一起看看吧2024-11-11Java自動(dòng)釋放鎖的三種實(shí)現(xiàn)方案
在筆者面試過程時(shí),經(jīng)常會被問到各種各樣的鎖,如樂觀鎖、讀寫鎖等等,非常繁多,下面這篇文章主要給大家介紹了關(guān)于Java自動(dòng)釋放鎖的三種實(shí)現(xiàn)方案,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06在SpringBoot中集成H2數(shù)據(jù)庫的完整指南
Spring Boot是一個(gè)簡化企業(yè)級Java應(yīng)用程序開發(fā)的強(qiáng)大框架,H2數(shù)據(jù)庫是一個(gè)輕量級的、開源的SQL數(shù)據(jù)庫,非常適合用于開發(fā)和測試,本文將指導(dǎo)您如何在Spring Boot應(yīng)用程序中集成H2數(shù)據(jù)庫,并探索一些高級配置選項(xiàng),需要的朋友可以參考下2024-10-10MyBatis?SQL映射文件的作用和結(jié)構(gòu)詳解
MyBatisSQL映射文件定義了SQL語句和參數(shù)映射規(guī)則,用于將Java代碼與數(shù)據(jù)庫操作解耦,實(shí)現(xiàn)SQL語句的靈活配置和動(dòng)態(tài)生成2025-03-03