javascript
jpa 动态查询条件 数组_Spring data jpa 复杂动态查询方式总结
一.Spring data jpa 簡(jiǎn)介
首先JPA是Java持久層API,由Sun公司開發(fā),?希望整合ORM技術(shù),實(shí)現(xiàn)天下歸一.??誕生的緣由是為了整合第三方ORM框架,建立一種標(biāo)準(zhǔn)的方式,目前也是在按照這個(gè)方向發(fā)展,但是還沒能完全實(shí)現(xiàn)。在ORM框架中,Hibernate是一支很大的部隊(duì),使用很廣泛,也很方便,能力也很強(qiáng),同時(shí)Hibernate也是和JPA整合的比較良好,我們可以認(rèn)為JPA是標(biāo)準(zhǔn),事實(shí)上也是,JPA幾乎都是接口,實(shí)現(xiàn)都是Hibernate在做,宏觀上面看,在JPA的統(tǒng)一之下Hibernate很良好的運(yùn)行。
Spring-data-jpa,Spring與jpa的整合
Spring主要是在做第三方工具的整合?不重新造輪子.?而在與第三方整合這方面,Spring做了持久化這一塊的工作,于是就有了Spring-data-**這一系列包。包括,Spring-data-jpa,Spring-data-template,Spring-data-mongodb,Spring-data-redis
Spring-data-jpa,目的少使用sql
我們都知道,在使用持久化工具的時(shí)候,一般都有一個(gè)對(duì)象來操作數(shù)據(jù)庫(kù),在原生的Hibernate中叫做Session,在JPA中叫做EntityManager,在MyBatis中叫做SqlSession,通過這個(gè)對(duì)象來操作數(shù)據(jù)庫(kù)。我們一般按照三層結(jié)構(gòu)來看的話,Service層做業(yè)務(wù)邏輯處理,Dao層和數(shù)據(jù)庫(kù)打交道,在Dao中,就存在著上面的對(duì)象。那么ORM框架本身提供的功能有什么呢?答案是基本的CRUD(增刪改查),所有的基礎(chǔ)CRUD框架都提供,我們使用起來感覺很方便,很給力,業(yè)務(wù)邏輯層面的處理ORM是沒有提供的,如果使用原生的框架,業(yè)務(wù)邏輯代碼我們一般會(huì)自定義,會(huì)自己去寫SQL語(yǔ)句,然后執(zhí)行。在這個(gè)時(shí)候,Spring-data-jpa的威力就體現(xiàn)出來了,ORM提供的能力他都提供,ORM框架沒有提供的業(yè)務(wù)邏輯功能Spring-data-jpa也提供,全方位的解決用戶的需求。使用Spring-data-jpa進(jìn)行開發(fā)的過程中,常用的功能,我們幾乎不需要寫一條sql語(yǔ)句,至少在我看來,企業(yè)級(jí)應(yīng)用基本上可以不用寫任何一條sql,當(dāng)然spring-data-jpa也提供自己寫sql的方式
返回值為對(duì)象的意義
是jpa查詢表內(nèi)容返回值基本上都是對(duì)象,但是僅僅需要一個(gè)字段返回整體對(duì)象不是會(huì)有很多數(shù)據(jù)冗余嗎,其實(shí)大多數(shù)情況對(duì)一個(gè)數(shù)據(jù)表的查詢不可能只有一次或者說這個(gè)表不僅僅是這一次會(huì)用到,如果我寫好一個(gè)返回對(duì)象的方法,之后都可以直接調(diào)用,一般情況下多出一點(diǎn)數(shù)據(jù)對(duì)網(wǎng)絡(luò)的壓力可以忽略不計(jì),而這樣對(duì)開發(fā)效率的提示還是很大的.如果僅僅想得到一部分字段也可以新建一個(gè)只有想要字段的Entity.
二.Spring data jpa 基本使用
1.核心方法
查詢所有數(shù)據(jù) findAll()
修改 添加數(shù)據(jù)? S save(S entity)
分頁(yè)查詢 Page findAll(Example example, Pageable pageable)
根據(jù)id查詢 findOne()
根據(jù)實(shí)體類屬性查詢: findByProperty (type Property); 例如:findByAge(int age)
刪除 voiddelete(T entity)
計(jì)數(shù) 查詢 long count() 或者 根據(jù)某個(gè)屬性的值查詢總數(shù) countByAge(int age)
是否存在? ?booleanexistsById(ID primaryKey)
2.查詢關(guān)鍵字
-and
And 例如:findByUsernameAndPassword(String user, Striang pwd);
-or
Or 例如:findByUsernameOrAddress(String user, String addr);
-between
Between 例如:SalaryBetween(int max, int min);
-"
LessThan 例如: findBySalaryLessThan(int max);
-">"
GreaterThan 例如: findBySalaryGreaterThan(int min);
-is null
IsNull 例如: findByUsernameIsNull();
-is not null
IsNotNull NotNull 與 IsNotNull 等價(jià) 例如: findByUsernameIsNotNull();
-like
Like 例如: findByUsernameLike(String user);
-not like
NotLike 例如: findByUsernameNotLike(String user);
-order by
OrderBy 例如: findByUsernameOrderByNameAsc(String user);直接通過name正序排序
-"!="
Not 例如: findByUsernameNot(String user);
-in
In 例如: findByUsernameIn(Collection userList) ,方法的參數(shù)可以是 Collection 類型,也可以是數(shù)組或者不定長(zhǎng)參數(shù);
-not in
NotIn 例如: findByUsernameNotIn(Collection userList) ,方法的參數(shù)可以是 Collection 類型,也可以是數(shù)組或者不定長(zhǎng)參數(shù);
-Top/Limit
查詢方法結(jié)果的數(shù)量可以通過關(guān)鍵字來限制,first?或者?top都可以使用。top/first加數(shù)字可以指定要返回最大結(jié)果的大小 默認(rèn)為1
例如:
User?findFirstByOrderByLastnameAsc();User?findTopByOrderByAgeDesc();Page?queryFirst10ByLastname(String?lastname,?Pageable?pageable);Slice?findTop3ByLastname(String?lastname,?Pageable?pageable);List?findFirst10ByLastname(String?lastname,?Sort?sort);List?findTop10ByLastname(String?lastname,?Pageable?pageable);
詳細(xì)查詢語(yǔ)法
關(guān)鍵詞示例對(duì)應(yīng)的sql片段
And
findByLastnameAndFirstname
… where x.lastname = ?1 and x.firstname = ?2
Or
findByLastnameOrFirstname
… where x.lastname = ?1 or x.firstname = ?2
Is,Equals
findByFirstname,findByFirstnameIs,findByFirstnameEquals
… where x.firstname = ?1
Between
findByStartDateBetween
… where x.startDate between ?1 and ?2
LessThan
findByAgeLessThan
… where x.age < ?1
LessThanEqual
findByAgeLessThanEqual
… where x.age <= ?1
GreaterThan
findByAgeGreaterThan
… where x.age > ?1
GreaterThanEqual
findByAgeGreaterThanEqual
… where x.age >= ?1
After
findByStartDateAfter
… where x.startDate > ?1
Before
findByStartDateBefore
… where x.startDate < ?1
IsNull
findByAgeIsNull
… where x.age is null
IsNotNull,NotNull
findByAge(Is)NotNull
… where x.age not null
Like
findByFirstnameLike
… where x.firstname like ?1
NotLike
findByFirstnameNotLike
… where x.firstname not like ?1
StartingWith
findByFirstnameStartingWith
… where x.firstname like ?1?(parameter bound with appended?%)
EndingWith
findByFirstnameEndingWith
… where x.firstname like ?1?(parameter bound with prepended?%)
Containing
findByFirstnameContaining
… where x.firstname like ?1?(parameter bound wrapped in?%)
OrderBy
findByAgeOrderByLastnameDesc
… where x.age = ?1 order by x.lastname desc
Not
findByLastnameNot
… where x.lastname <> ?1
In
findByAgeIn(Collection ages)
… where x.age in ?1
NotIn
findByAgeNotIn(Collection ages)
… where x.age not in ?1
True
findByActiveTrue()
… where x.active = true
False
findByActiveFalse()
… where x.active = false
IgnoreCase
findByFirstnameIgnoreCase
… where UPPER(x.firstame) = UPPER(?1)
3.內(nèi)置方法
Sort_排序
Sort?sort?=new?Sort(Sort.Direction.ASC,"id");
//其中第一個(gè)參數(shù)表示是降序還是升序(此處表示升序)
//第二個(gè)參數(shù)表示你要按你的?entity(記住是entity中聲明的變量,不是數(shù)據(jù)庫(kù)中表對(duì)應(yīng)的字段)中的那個(gè)變量進(jìn)行排序
PageRequest_分頁(yè)
PageRequest?pageRequest?=?new?PageRequest(index,?num,?sort);
//index偏移量?num查詢數(shù)量?sort排序
分頁(yè)+排序?qū)崿F(xiàn):
DemoBean?demoBean?=?new?DemoBean();
demoBean.setAppId(appId);?//查詢條件
//創(chuàng)建查詢參數(shù)
Example?example?=?Example.of(demoBean);
//獲取排序?qū)ο?/p>
Sort?sort?=?new?Sort(Sort.Direction.DESC,?"id");
//創(chuàng)建分頁(yè)對(duì)象
PageRequest?pageRequest?=?new?PageRequest(index,?num,?sort);
//分頁(yè)查詢
return?demoRepository.findAll(example,?pageRequest).getContent();
Example_實(shí)例查詢
創(chuàng)建一個(gè)ExampleMatcher對(duì)象,最后再用Example的of方法構(gòu)造相應(yīng)的Example對(duì)象并傳遞給相關(guān)查詢方法。我們看看Spring的例子。
Person?person?=?new?Person();
person.setFirstname("Dave");??//Firstname?=?'Dave'
ExampleMatcher?matcher?=?ExampleMatcher.matching()
.withMatcher("name",?GenericPropertyMatchers.startsWith())?//姓名采用“開始匹配”的方式查詢
.withIgnorePaths("int");??//忽略屬性:是否關(guān)注。因?yàn)槭腔绢愋?#xff0c;需要忽略掉
Example?example?=?Example.of(person,?matcher);??//Example根據(jù)域?qū)ο蠛团渲脛?chuàng)建一個(gè)新的ExampleMatcher
ExampleMatcher用于創(chuàng)建一個(gè)查詢對(duì)象,上面的代碼就創(chuàng)建了一個(gè)查詢對(duì)象。withIgnorePaths方法用來排除某個(gè)屬性的查詢。withIncludeNullValues方法讓空值也參與查詢,就是我們?cè)O(shè)置了對(duì)象的姓,而名為空值.
1、概念定義:
上面例子中,是這樣創(chuàng)建“實(shí)例”的:Example ex = Example.of(customer, matcher);我們看到,Example對(duì)象,由customer和matcher共同創(chuàng)建。
A、實(shí)體對(duì)象:在持久化框架中與Table對(duì)應(yīng)的域?qū)ο?#xff0c;一個(gè)對(duì)象代表數(shù)據(jù)庫(kù)表中的一條記錄,如上例中Customer對(duì)象。在構(gòu)建查詢條件時(shí),一個(gè)實(shí)體對(duì)象代表的是查詢條件中的“數(shù)值”部分。如:要查詢名字是“Dave”的客戶,實(shí)體對(duì)象只能存儲(chǔ)條件值“Dave”。
B、匹配器:ExampleMatcher對(duì)象,它是匹配“實(shí)體對(duì)象”的,表示了如何使用“實(shí)體對(duì)象”中的“值”進(jìn)行查詢,它代表的是“查詢方式”,解釋了如何去查的問題。如:要查詢FirstName是“Dave”的客戶,即名以“Dave"開頭的客戶,該對(duì)象就表示了“以什么開頭的”這個(gè)查詢方式,如上例中:withMatcher("name", GenericPropertyMatchers.startsWith())
C、實(shí)例:即Example對(duì)象,代表的是完整的查詢條件。由實(shí)體對(duì)象(查詢條件值)和匹配器(查詢方式)共同創(chuàng)建。
再來理解“實(shí)例查詢”,顧名思義,就是通過一個(gè)例子來查詢。要查詢的是Customer對(duì)象,查詢條件也是一個(gè)Customer對(duì)象,通過一個(gè)現(xiàn)有的客戶對(duì)象作為例子,查詢和這個(gè)例子相匹配的對(duì)象。
2、特點(diǎn)及約束(局限性):
A、支持動(dòng)態(tài)查詢。即支持查詢條件個(gè)數(shù)不固定的情況,如:客戶列表中有多個(gè)過濾條件,用戶使用時(shí)在“地址”查詢框中輸入了值,就需要按地址進(jìn)行過濾,如果沒有輸入值,就忽略這個(gè)過濾條件。對(duì)應(yīng)的實(shí)現(xiàn)是,在構(gòu)建查詢條件Customer對(duì)象時(shí),將address屬性值置具體的條件值或置為null。
B、不支持過濾條件分組。即不支持過濾條件用 or(或) 來連接,所有的過濾查件,都是簡(jiǎn)單一層的用 and(并且) 連接。
C、僅支持字符串的開始/包含/結(jié)束/正則表達(dá)式匹配 和 其他屬性類型的精確匹配。查詢時(shí),對(duì)一個(gè)要進(jìn)行匹配的屬性(如:姓名 name),只能傳入一個(gè)過濾條件值,如以Customer為例,要查詢姓“劉”的客戶,“劉”這個(gè)條件值就存儲(chǔ)在表示條件對(duì)象的Customer對(duì)象的name屬性中,針對(duì)于“姓名”的過濾也只有這么一個(gè)存儲(chǔ)過濾值的位置,沒辦法同時(shí)傳入兩個(gè)過濾值。正是由于這個(gè)限制,有些查詢是沒辦法支持的,例如要查詢某個(gè)時(shí)間段內(nèi)添加的客戶,對(duì)應(yīng)的屬性是 addTime,需要傳入“開始時(shí)間”和“結(jié)束時(shí)間”兩個(gè)條件值,而這種查詢方式?jīng)]有存兩個(gè)值的位置,所以就沒辦法完成這樣的查詢。
3、ExampleMatcher的使用?:
一些問題:
(1)Null值的處理。當(dāng)某個(gè)條件值為Null,是應(yīng)當(dāng)忽略這個(gè)過濾條件呢,還是應(yīng)當(dāng)去匹配數(shù)據(jù)庫(kù)表中該字段值是Null的記錄?
(2)基本類型的處理。如客戶Customer對(duì)象中的年齡age是int型的,當(dāng)頁(yè)面不傳入條件值時(shí),它默認(rèn)是0,是有值的,那是否參與查詢呢?
(3)忽略某些屬性值。一個(gè)實(shí)體對(duì)象,有許多個(gè)屬性,是否每個(gè)屬性都參與過濾?是否可以忽略某些屬性?
(4)不同的過濾方式。同樣是作為String值,可能“姓名”希望精確匹配,“地址”希望模糊匹配,如何做到?
(5)大小寫匹配。字符串匹配時(shí),有時(shí)可能希望忽略大小寫,有時(shí)則不忽略,如何做到?
一些方法:
1、關(guān)于基本數(shù)據(jù)類型。
實(shí)體對(duì)象中,避免使用基本數(shù)據(jù)類型,采用包裝器類型。如果已經(jīng)采用了基本類型,
而這個(gè)屬性查詢時(shí)不需要進(jìn)行過濾,則把它添加到忽略列表(ignoredPaths)中。
2、Null值處理方式。
默認(rèn)值是 IGNORE(忽略),即當(dāng)條件值為null時(shí),則忽略此過濾條件,一般業(yè)務(wù)也是采用這種方式就可滿足。當(dāng)需要查詢數(shù)據(jù)庫(kù)表中屬性為null的記錄時(shí),可將值設(shè)為INCLUDE,這時(shí),對(duì)于不需要參與查詢的屬性,都必須添加到忽略列表(ignoredPaths)中,否則會(huì)出現(xiàn)查不到數(shù)據(jù)的情況。
3、默認(rèn)配置、特殊配置。
默認(rèn)創(chuàng)建匹配器時(shí),字符串采用的是精確匹配、不忽略大小寫,可以通過操作方法改變這種默認(rèn)匹配,以滿足大多數(shù)查詢條件的需要,如將“字符串匹配方式”改為CONTAINING(包含,模糊匹配),這是比較常用的情況。對(duì)于個(gè)別屬性需要特定的查詢方式,可以通過配置“屬性特定查詢方式”來滿足要求。
4、非字符串屬性
如約束中所談,非字符串屬性均采用精確匹配,即等于。
5、忽略大小寫的問題。
忽略大小的生效與否,是依賴于數(shù)據(jù)庫(kù)的。例如 MySql 數(shù)據(jù)庫(kù)中,默認(rèn)創(chuàng)建表結(jié)構(gòu)時(shí),字段是已經(jīng)忽略大小寫的,所以這個(gè)配置與否,都是忽略的。如果業(yè)務(wù)需要嚴(yán)格區(qū)分大小寫,可以改變數(shù)據(jù)庫(kù)表結(jié)構(gòu)屬性來實(shí)現(xiàn),具體可百度。
一些例子:
綜合使用:
//創(chuàng)建查詢條件數(shù)據(jù)對(duì)象
Customer?customer?=?new?Customer();
customer.setName("zhang");
customer.setAddress("河南省");
customer.setRemark("BB");
//創(chuàng)建匹配器,即如何使用查詢條件
ExampleMatcher?matcher?=?ExampleMatcher.matching()?//構(gòu)建對(duì)象
.withStringMatcher(StringMatcher.CONTAINING)?//改變默認(rèn)字符串匹配方式:模糊查詢
.withIgnoreCase(true)?//改變默認(rèn)大小寫忽略方式:忽略大小寫
.withMatcher("address",?GenericPropertyMatchers.startsWith())?//地址采用“開始匹配”的方式查詢
.withIgnorePaths("focus");??//忽略屬性:是否關(guān)注。因?yàn)槭腔绢愋?#xff0c;需要忽略掉
//創(chuàng)建實(shí)例
Example?ex?=?Example.of(customer,?matcher);
//查詢
List?ls?=?dao.findAll(ex);
查詢null值:
//創(chuàng)建查詢條件數(shù)據(jù)對(duì)象
Customer?customer?=?new?Customer();
//創(chuàng)建匹配器,即如何使用查詢條件
ExampleMatcher?matcher?=?ExampleMatcher.matching()?//構(gòu)建對(duì)象
.withIncludeNullValues()?//改變“Null值處理方式”:包括
.withIgnorePaths("id","name","sex","age","focus","addTime","remark","customerType");??//忽略其他屬性
//創(chuàng)建實(shí)例
Example?ex?=?Example.of(customer,?matcher);
//查詢
List?ls?=?dao.findAll(ex);
三.Spring data jpa 注解
1.Repository注解
@Modifying?//做update操作時(shí)需要添加
@Query?// 自定義Sql
@Query(value?=?"SELECT?*?FROM?USERS?WHERE?X?=??1",?nativeQuery?=?true)
User?findByEmailAddress(String?X);
@Query("select?u?from?User?u?where?u.firstname?=?:firstname")?//不加nativeQuery應(yīng)使用HQL
User?findByLastnameOrFirstname(@Param("lastname")?String?lastname);
@Transactional?//事務(wù)
@Async?//異步操作
2.Entity注解
@Entity?//不寫@Table默認(rèn)為user
@Table(name="t_user")?//自定義表名
public?class?user?{
@Id?//主鍵
@GeneratedValue(strategy?=?GenerationType.AUTO)//采用數(shù)據(jù)庫(kù)自增方式生成主鍵
//JPA提供的四種標(biāo)準(zhǔn)用法為TABLE,SEQUENCE,IDENTITY,AUTO.
//TABLE:使用一個(gè)特定的數(shù)據(jù)庫(kù)表格來保存主鍵。
//SEQUENCE:根據(jù)底層數(shù)據(jù)庫(kù)的序列來生成主鍵,條件是數(shù)據(jù)庫(kù)支持序列。
//IDENTITY:主鍵由數(shù)據(jù)庫(kù)自動(dòng)生成(主要是自動(dòng)增長(zhǎng)型)
//AUTO:主鍵由程序控制。
@Transient?//此字段不與數(shù)據(jù)庫(kù)關(guān)聯(lián)
@Version//此字段加上樂觀鎖
//字段為name,不允許為空,用戶名唯一
@Column(name?=?"name",?unique?=?true,?nullable?=?false)
private?String?name;
@Temporal(TemporalType.DATE)//生成yyyy-MM-dd類型的日期
//出參時(shí)間格式化
@JsonFormat(pattern?=?"yyyy-MM-dd?HH:mm:ss",?timezone?=?"GMT+8")
//入?yún)r(shí),請(qǐng)求報(bào)文只需要傳入yyyymmddhhmmss字符串進(jìn)來,則自動(dòng)轉(zhuǎn)換為Date類型數(shù)據(jù)
@DateTimeFormat(pattern?=?"yyyy-MM-dd?HH:mm")
private?Date?createTime;
public?String?getName()?{
return?name;
}
public?void?setName(String?name)?{
this.name?=?name;
}
}
四.繼承JpaSpecificationExecutor接口進(jìn)行復(fù)雜查詢
spring data jpa 通過創(chuàng)建方法名來做查詢,只能做簡(jiǎn)單的查詢,那如果我們要做復(fù)雜一些的查詢呢,多條件分頁(yè)怎么辦,這里,spring data jpa為我們提供了JpaSpecificationExecutor接口,只要簡(jiǎn)單實(shí)現(xiàn)toPredicate方法就可以實(shí)現(xiàn)復(fù)雜的查詢
參考:https://www.cnblogs.com/happyday56/p/4661839.html
1.首先讓我們的接口繼承于JpaSpecificationExecutor
public?interface?TaskDao?extends?JpaSpecificationExecutor{
}
2.JpaSpecificationExecutor提供了以下接口
public?interface?JpaSpecificationExecutor?{
T?findOne(Specification?spec);
List?findAll(Specification?spec);
Page?findAll(Specification?spec,?Pageable?pageable);
List?findAll(Specification?spec,?Sort?sort);
long?count(Specification?spec);
}
//其中Specification就是需要我們傳入查詢方法的參數(shù),它是一個(gè)接口
public?interface?Specification?{
Predicate?toPredicate(Root?root,?CriteriaQuery>?query,?CriteriaBuilder?cb);
}
提供唯一的一個(gè)方法toPredicate,我們只要按照J(rèn)PA 2.0 criteria api寫好查詢條件就可以了,關(guān)于JPA 2.0 criteria api的介紹和使用,歡迎參考
http://blog.csdn.net/dracotianlong/article/details/28445725
http://developer.51cto.com/art/200911/162722.htm
3.接下來我們?cè)趕ervice bean
@Service
public?class?TaskService?{
@Autowired?TaskDao?taskDao?;
/**
*?復(fù)雜查詢測(cè)試
*?@param?page
*?@param?size
*?@return
*/
public?Page?findBySepc(int?page,?int?size){
PageRequest?pageReq?=?this.buildPageRequest(page,?size);
Page?tasks?=?this.taskDao.findAll(new?MySpec(),?pageReq);
//傳入了new?MySpec()?既下面定義的匿名內(nèi)部類?其中定義了查詢條件
return?tasks;
}
/**
*?建立分頁(yè)排序請(qǐng)求
*?@param?page
*?@param?size
*?@return
*/
private?PageRequest?buildPageRequest(int?page,?int?size)?{
Sort?sort?=?new?Sort(Direction.DESC,"createTime");
return?new?PageRequest(page,size,?sort);
}
/**
*?建立查詢條件
*/
private?class?MySpec?implements?Specification{
@Override
public?Predicate?toPredicate(Root?root,?CriteriaQuery>?query,?CriteriaBuilder?cb)?{
//1.混合條件查詢
Path?exp1?=?root.get("taskName");
Path??exp2?=?root.get("createTime");
Path?exp3?=?root.get("taskDetail");
Predicate?predicate?=?cb.and(cb.like(exp1,?"%taskName%"),cb.lessThan(exp2,?new?Date()));
return?cb.or(predicate,cb.equal(exp3,?"kkk"));
/*?類似的sql語(yǔ)句為:
Hibernate:
select
count(task0_.id)?as?col_0_0_
from
tb_task?task0_
where
(
task0_.task_name?like??
)
and?task0_.create_time
or?task0_.task_detail=?
*/
//2.多表查詢
Join?join?=?root.join("project",?JoinType.INNER);
Path?exp4?=?join.get("projectName");
return?cb.like(exp4,?"%projectName%");
/*?Hibernate:
select
count(task0_.id)?as?col_0_0_
from
tb_task?task0_
inner?join
tb_project?project1_
on?task0_.project_id=project1_.id
where
project1_.project_name?like??*/
return?null?;
}
}
}
4.實(shí)體類task代碼如下
@Entity
@Table(name?=?"tb_task")
public?class?Task?{
private?Long?id?;
private?String?taskName?;
private?Date?createTime?;
private?Project?project;
private?String?taskDetail?;
@Id
@GeneratedValue(strategy?=?GenerationType.IDENTITY)
public?Long?getId()?{
return?id;
}
public?void?setId(Long?id)?{
this.id?=?id;
}
@Column(name?=?"task_name")
public?String?getTaskName()?{
return?taskName;
}
public?void?setTaskName(String?taskName)?{
this.taskName?=?taskName;
}
@Column(name?=?"create_time")
@DateTimeFormat(pattern?=?"yyyy-MM-dd?hh:mm:ss")
public?Date?getCreateTime()?{
return?createTime;
}
public?void?setCreateTime(Date?createTime)?{
this.createTime?=?createTime;
}
@Column(name?=?"task_detail")
public?String?getTaskDetail()?{
return?taskDetail;
}
public?void?setTaskDetail(String?taskDetail)?{
this.taskDetail?=?taskDetail;
}
@ManyToOne(fetch?=?FetchType.LAZY)
@JoinColumn(name?=?"project_id")
public?Project?getProject()?{
return?project;
}
public?void?setProject(Project?project)?{
this.project?=?project;
}
}
通過重寫toPredicate方法,返回一個(gè)查詢 Predicate,spring data jpa會(huì)幫我們進(jìn)行查詢。
也許你覺得,每次都要寫一個(gè)類來實(shí)現(xiàn)Specification很麻煩,那或許你可以這么寫
public?class?TaskSpec?{
public?static?Specification?method1(){
return?new?Specification(){
@Override
public?Predicate?toPredicate(Root?root,?CriteriaQuery>?query,?CriteriaBuilder?cb)?{
return?null;
}
};
}
public?static?Specification?method2(){
return?new?Specification(){
@Override
public?Predicate?toPredicate(Root?root,?CriteriaQuery>?query,?CriteriaBuilder?cb)?{
return?null;
}
};
}
}
那么用的時(shí)候,我們就這么用
Page?tasks?=?this.taskDao.findAll(TaskSpec.method1(),?pageReq);
五.Spring data jpa + QueryDSL 進(jìn)行復(fù)雜查詢
QueryDSL僅僅是一個(gè)通用的查詢框架,專注于通過Java API構(gòu)建類型安全的SQL查詢。
Querydsl可以通過一組通用的查詢API為用戶構(gòu)建出適合不同類型ORM框架或者是SQL的查詢語(yǔ)句,也就是說QueryDSL是基于各種ORM框架以及SQL之上的一個(gè)通用的查詢框架。
借助QueryDSL可以在任何支持的ORM框架或者SQL平臺(tái)上以一種通用的API方式來構(gòu)建查詢。目前QueryDSL支持的平臺(tái)包括JPA,JDO,SQL,Java Collections,RDF,Lucene,Hibernate Search。P.s.配置可以根據(jù)官網(wǎng)介紹來配置
1 實(shí)體類
城市類:
@Entity
@Table(name?=?"t_city",?schema?=?"test",?catalog?=?"")
public?class?TCity?{
//省略JPA注解標(biāo)識(shí)
private?int?id;
private?String?name;
private?String?state;
private?String?country;
private?String?map;
}
旅館類:
@Entity
@Table(name?=?"t_hotel",?schema?=?"test",?catalog?=?"")
public?class?THotel?{
//省略JPA注解標(biāo)識(shí)
private?int?id;
private?String?name;
private?String?address;
private?Integer?city;//保存著城市的id主鍵
}
2 單表動(dòng)態(tài)分頁(yè)查詢
Spring Data JPA中提供了QueryDslPredicateExecutor接口,用于支持QueryDSL的查詢操作
public?interface?tCityRepository?extends?JpaRepository,?QueryDslPredicateExecutor?{
}
這樣的話單表動(dòng)態(tài)查詢就可以參考如下代碼:
//查找出Id小于3,并且名稱帶有`shanghai`的記錄.
//動(dòng)態(tài)條件
QTCity?qtCity?=?QTCity.tCity;?//SDL實(shí)體類
//該P(yáng)redicate為querydsl下的類,支持嵌套組裝復(fù)雜查詢條件
Predicate?predicate?=?qtCity.id.longValue().lt(3).and(qtCity.name.like("shanghai"));
//分頁(yè)排序
Sort?sort?=?new?Sort(new?Sort.Order(Sort.Direction.ASC,"id"));
PageRequest?pageRequest?=?new?PageRequest(0,10,sort);
//查找結(jié)果
Page?tCityPage?=?tCityRepository.findAll(predicate,pageRequest);
3 多表動(dòng)態(tài)查詢
QueryDSL對(duì)多表查詢提供了一個(gè)很好地封裝,看下面代碼:
/**
*?關(guān)聯(lián)查詢示例,查詢出城市和對(duì)應(yīng)的旅店
*?@param?predicate?查詢條件
*?@return?查詢實(shí)體
*/
@Override
public?List?findCityAndHotel(Predicate?predicate)?{
JPAQueryFactory?queryFactory?=?new?JPAQueryFactory(em);
JPAQuery?jpaQuery?=?queryFactory.select(QTCity.tCity,QTHotel.tHotel)
.from(QTCity.tCity)
.leftJoin(QTHotel.tHotel)
.on(QTHotel.tHotel.city.longValue().eq(QTCity.tCity.id.longValue()));
//添加查詢條件
jpaQuery.where(predicate);
//拿到結(jié)果
return?jpaQuery.fetch();
}
城市表左連接旅店表,當(dāng)該旅店屬于這個(gè)城市時(shí)查詢出兩者的詳細(xì)字段,存放到一個(gè)Tuple的多元組中.相比原生sql,簡(jiǎn)單清晰了很多.
那么該怎么調(diào)用這個(gè)方法呢?
@Test
public?void?findByLeftJoin(){
QTCity?qtCity?=?QTCity.tCity;
QTHotel?qtHotel?=?QTHotel.tHotel;
//查詢條件
Predicate?predicate?=?qtCity.name.like("shanghai");
//調(diào)用
List?result?=?tCityRepository.findCityAndHotel(predicate);
//對(duì)多元組取出數(shù)據(jù),這個(gè)和select時(shí)的數(shù)據(jù)相匹配
for?(Tuple?row?:?result)?{
System.out.println("qtCity:"+row.get(qtCity));
System.out.println("qtHotel:"+row.get(qtHotel));
System.out.println("--------------------");
}
System.out.println(result);
}
這樣做的話避免了返回Object[]數(shù)組,下面是自動(dòng)生成的sql語(yǔ)句:
select
tcity0_.id?as?id1_0_0_,
thotel1_.id?as?id1_1_1_,
tcity0_.country?as?country2_0_0_,
tcity0_.map?as?map3_0_0_,
tcity0_.name?as?name4_0_0_,
tcity0_.state?as?state5_0_0_,
thotel1_.address?as?address2_1_1_,
thotel1_.city?as?city3_1_1_,
thotel1_.name?as?name4_1_1_
from
t_city?tcity0_
left?outer?join
t_hotel?thotel1_
on?(
cast(thotel1_.city?as?signed)=cast(tcity0_.id?as?signed)
)
where
tcity0_.name?like???escape?'!'
4 多表動(dòng)態(tài)分頁(yè)查詢
分頁(yè)查詢對(duì)于queryDSL無(wú)論什么樣的sql只需要寫一遍,會(huì)自動(dòng)轉(zhuǎn)換為相應(yīng)的count查詢,也就避免了文章開始的問題4,下面代碼是對(duì)上面的查詢加上分頁(yè)功能:
@Override
public?QueryResults?findCityAndHotelPage(Predicate?predicate,Pageable?pageable)?{
JPAQueryFactory?queryFactory?=?new?JPAQueryFactory(em);
JPAQuery?jpaQuery?=?queryFactory.select(QTCity.tCity.id,QTHotel.tHotel)
.from(QTCity.tCity)
.leftJoin(QTHotel.tHotel)
.on(QTHotel.tHotel.city.longValue().eq(QTCity.tCity.id.longValue()))
.where(predicate)
.offset(pageable.getOffset())
.limit(pageable.getPageSize());
//拿到分頁(yè)結(jié)果
return?jpaQuery.fetchResults();
}
和上面不同之處在于這里使用了offset和limit限制查詢結(jié)果.并且返回一個(gè)QueryResults,該類會(huì)自動(dòng)實(shí)現(xiàn)count查詢和結(jié)果查詢,并進(jìn)行封裝.
調(diào)用形式如下:
@Test
public?void?findByLeftJoinPage(){
QTCity?qtCity?=?QTCity.tCity;
QTHotel?qtHotel?=?QTHotel.tHotel;
//條件
Predicate?predicate?=?qtCity.name.like("shanghai");
//分頁(yè)
PageRequest?pageRequest?=?new?PageRequest(0,10);
//調(diào)用查詢
QueryResults?result?=?tCityRepository.findCityAndHotelPage(predicate,pageRequest);
//結(jié)果取出
for?(Tuple?row?:?result.getResults())?{
System.out.println("qtCity:"+row.get(qtCity));
System.out.println("qtHotel:"+row.get(qtHotel));
System.out.println("--------------------");
}
//取出count查詢總數(shù)
System.out.println(result.getTotal());
}
生成的原生count查詢sql,當(dāng)該count查詢結(jié)果為0的話,則直接返回,并不會(huì)再進(jìn)行具體數(shù)據(jù)查詢:
select
count(tcity0_.id)?as?col_0_0_
from
t_city?tcity0_
left?outer?join
t_hotel?thotel1_
on?(
cast(thotel1_.city?as?signed)=cast(tcity0_.id?as?signed)
)
where
tcity0_.name?like???escape?'!'
生成的原生查詢sql:
select
tcity0_.id?as?id1_0_0_,
thotel1_.id?as?id1_1_1_,
tcity0_.country?as?country2_0_0_,
tcity0_.map?as?map3_0_0_,
tcity0_.name?as?name4_0_0_,
tcity0_.state?as?state5_0_0_,
thotel1_.address?as?address2_1_1_,
thotel1_.city?as?city3_1_1_,
thotel1_.name?as?name4_1_1_
from
t_city?tcity0_
left?outer?join
t_hotel?thotel1_
on?(
cast(thotel1_.city?as?signed)=cast(tcity0_.id?as?signed)
)
where
tcity0_.name?like???escape?'!'?limit??
查看打印,可以發(fā)現(xiàn)對(duì)應(yīng)的city也都是同一個(gè)對(duì)象,hotel是不同的對(duì)象.
5 改造
有了上面的經(jīng)驗(yàn),改造就變得相當(dāng)容易了.
首先前面的一堆sql可以寫成如下形式,無(wú)非是多了一些select和left join
JPAQueryFactory?factory?=?new?JPAQueryFactory(entityManager);
factory.select($.pcardCardOrder)
.select($.pcardVcardMake.vcardMakeDes)
.select($.pcardVtype.cardnumRuleId,$.pcardVtype.vtypeNm)
.select($.pcardCardbin)
.leftJoin($.pcardVcardMake).on($.pcardCardOrder.makeId.eq($.pcardVcardMake.vcardMakeId))
//......省略
查詢條件使用Predicate代替,放在service拼接,或者寫一個(gè)生產(chǎn)條件的工廠都可以.
jpaQuery.where(predicate);
最后的分頁(yè)處理就和之前的一樣了
jpaQuery.offset(pageable.getOffset())
.limit(pageable.getPageSize());
return?jpaQuery.fetchResults();
寫在最后:
個(gè)人認(rèn)為jpa的意義就在于少用原生sql 為了方便開發(fā) 封裝已經(jīng)是在所難免了. 推薦多使用簡(jiǎn)單查詢,需要使用動(dòng)態(tài)查詢的時(shí)候推薦使用JpaSpecificationExecutor個(gè)人認(rèn)為比較好用.
雖然我還是喜歡原生的寫法...
另外很多時(shí)候簡(jiǎn)單的條件可以在server層進(jìn)行判斷調(diào)用不同的Dao層方法就可以。
P.s.參考資料
總結(jié)
以上是生活随笔為你收集整理的jpa 动态查询条件 数组_Spring data jpa 复杂动态查询方式总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jquery 乱码 传参_jquery获
- 下一篇: win101909要不要更新_win10