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可能會(huì)更好。
- 靈活性:雖然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:查詢的根對象(查詢的任何屬性都可以從根對象中獲?。?/li>
- 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就會(huì)自動(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ī)則,它通常會(huì)實(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)文章
java 中mongodb的各種操作查詢的實(shí)例詳解
這篇文章主要介紹了java 中mongodb的各種操作查詢的實(shí)例詳解的相關(guān)資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-09-09
Spring超詳細(xì)講解創(chuàng)建BeanDefinition流程
Spring在初始化過程中,將xml中定義的對象解析到了BeanDefinition對象中,我們有必要了解一下BeanDefinition的內(nèi)部結(jié)構(gòu),有助于我們理解Spring的初始化流程2022-06-06
VSCode搭建Java開發(fā)環(huán)境的超詳細(xì)步驟
VSCode是一款多平臺的源代碼編輯器,支持多種編程語言,它輕量級、功能強(qiáng)大,通過豐富的插件生態(tài)系統(tǒng)可以支持更多語言和運(yùn)行時(shí),如C++、C#、Java、Python等,這篇文章主要介紹了VSCode搭建Java開發(fā)環(huán)境的超詳細(xì)步驟,需要的朋友可以參考下2024-10-10

