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