Spring Boot JPA Repository之existsBy查詢方法失效的解決
引言: Spring Boot號稱微服務(wù)的利器,在結(jié)合了Spring Data與JPA之后,更是如虎添翼,開發(fā)快速的不像話,本文將講述一個關(guān)于JPA中一個詭異問題的診斷分析過程以及修復(fù)方法。
環(huán)境介紹
JDK 1.8 Spring 4.2 Spring Boot 1.5.9
問題描述
在Spring Data中的Repository接口中創(chuàng)建了一個檢查數(shù)據(jù)是否存在的接口方法:
@Repository public interface VideoEntityRepository extends JpaRepository<VideoEntity, Long> { ........ public boolean existsByUserIdAndName(long userId, String name); }
VideoEntity的類如下:
@SuppressWarnings("serial") @Table(name="flook_video") @Entity @Data @EqualsAndHashCode(callSuper=true) public class VideoEntity extends BaseEntity { @Column(name="user_id") private long userId; @Column(name="name") private String name; @Column(name="change_version") private double changeVersion; }
在調(diào)用方法existsBy方法的時候,返回的結(jié)果一直為false, 結(jié)果不正確,在偶的期望中,其執(zhí)行結(jié)果應(yīng)該不會出錯的?那問題出在哪里呢?
關(guān)于existsBy的介紹
Spring Data提供了若干非常實(shí)用的擴(kuò)展,將數(shù)據(jù)庫表日常的CRUD操作都進(jìn)行很好的實(shí)現(xiàn),并提供了若干擴(kuò)展機(jī)制,基于一套簡單易用的命名規(guī)則,來基于聲明式實(shí)現(xiàn)場景的數(shù)據(jù)庫查詢操作:
countByColumName existsByColumnName
上述兩種方式都是由Spring Data來幫助動態(tài)生成SQL的。
基于@Query方式
除了基于countBy/existsBy兩種方式之外,可以直接使用@Query方式來標(biāo)注特定的SQL或者JPQL來實(shí)現(xiàn)功能,由于這種方式需要些SQL,功能上完全覆蓋,但是工作量略大,不是最優(yōu)的方式。
問題分析
由于底層使用了Hibernate/JPA/Spring Data來實(shí)現(xiàn)的數(shù)據(jù)訪問層的實(shí)現(xiàn),所以,最好的方式當(dāng)然是查看動態(tài)生成的SQL了,于是找到了生成的SQL語句:
select videoentit0_.id as col_0_0_ from video videoentit0_ where videoentit0_.user_id=? and videoentit0_.name=? limit ?
表的名稱是video,這個名稱是不對的。這個情況是怎么發(fā)生的呢?
問題的解決
經(jīng)過分析發(fā)現(xiàn),是由于在代碼中存在兩個類名完全相同VideoEntity的類,雖然在Repository中我們的的確確沒有引用錯誤相關(guān)的,問題應(yīng)該出在當(dāng)Spring Data碰到兩個相同的類名之時,其實(shí)不知道如何來生成SQL的,換句話說,其應(yīng)該是基于類名而非類路徑來動態(tài)生成執(zhí)行SQL的。
TODO: 基于源代碼中找到相關(guān)部分內(nèi)容。
問題解決
將當(dāng)前的VideoEntity重新命名,確保不存在重名的問題,即使類的路徑不一樣,但是類名一樣,也是會產(chǎn)生這樣的問題。
從一個側(cè)面來分析,在Spring Data中所有的DataBean都是需要使用全路徑的類名的,否則同樣會出現(xiàn)問題。
JpaRepository 查詢規(guī)范
1.JpaRepository支持接口規(guī)范方法名查詢
意思是如果在接口中定義的查詢方法符合它的命名規(guī)則,就可以不用寫實(shí)現(xiàn),目前支持的關(guān)鍵字如下。
Keyword |
Sample |
JPQL snippet |
IsNotNull |
findByAgeNotNull |
... where x.age not null |
Like |
findByNameLike |
... where x.name like ?1 |
NotLike |
findByNameNotLike |
... where x.name not like ?1 |
StartingWith |
findByNameStartingWith |
... where x.name like ?1(parameter bound with appended %) |
EndingWith |
findByNameEndingWith |
... where x.name like ?1(parameter bound with prepended %) |
Containing |
findByNameContaining |
... where x.name like ?1(parameter bound wrapped in %) |
OrderBy |
findByAgeOrderByName |
... where x.age = ?1 order by x.name desc |
Not |
findByNameNot |
... where x.name <> ?1 |
In |
findByAgeIn |
... where x.age in ?1 |
NotIn |
findByAgeNotIn |
... where x.age not in ?1 |
True |
findByActiveTrue |
... where x.avtive = true |
Flase |
findByActiveFalse |
... where x.active = false |
And |
findByNameAndAge |
... where x.name = ?1 and x.age = ?2 |
Or |
findByNameOrAge |
... where x.name = ?1 or x.age = ?2 |
Between |
findBtAgeBetween |
... where x.age between ?1 and ?2 |
LessThan |
findByAgeLessThan |
... where x.age < ?1 |
GreaterThan |
findByAgeGreaterThan |
... where x.age > ?1 |
After/Before |
... |
... |
IsNull |
findByAgeIsNull |
... where x.age is null |
2.JpaRepository相關(guān)查詢功能
a.Spring DataJPA框架在進(jìn)行方法名解析時,會先把方法名多余的前綴截取掉,比如find、findBy、read、readBy、get、getBy,然后對剩下部分進(jìn)行解析。
b.假如創(chuàng)建如下的查詢:findByUserDepUuid(),框架在解析該方法時,首先剔除findBy,然后對剩下的屬性進(jìn)行解析,假設(shè)查詢實(shí)體為Doc。
1:先判斷userDepUuid (根據(jù)POJO規(guī)范,首字母變?yōu)樾懀┦欠駷椴樵儗?shí)體的一個屬性,如果是,則表示根據(jù)該屬性進(jìn)行查詢;如果沒有該屬性,繼續(xù)第二步;
2:從右往左截取第一個大寫字母開頭的字符串此處為Uuid),然后檢查剩下的字符串是否為查詢實(shí)體的一個屬性,如果是,則表示根據(jù)該屬性進(jìn)行查詢;如果沒有該屬性,則重復(fù)第二步,繼續(xù)從右往左截取;最后假設(shè)user為查詢實(shí)體的一個屬性;
3:接著處理剩下部分(DepUuid),先判斷user所對應(yīng)的類型是否有depUuid屬性,如果有,則表示該方法最終是根據(jù)“Doc.user.depUuid” 的取值進(jìn)行查詢;否則繼續(xù)按照步驟2的規(guī)則從右往左截取,最終表示根據(jù)“Doc.user.dep.uuid” 的值進(jìn)行查詢。
4:可能會存在一種特殊情況,比如Doc包含一個user的屬性,也有一個userDep 屬性,此時會存在混淆??梢悦鞔_在屬性之間加上"_"以顯式表達(dá)意圖,比如"findByUser_DepUuid()"或者"findByUserDep_uuid()"
c.特殊的參數(shù): 還可以直接在方法的參數(shù)上加入分頁或排序的參數(shù),比如:
Page<UserModel>findByName(String name, Pageable pageable); List<UserModel>findByName(String name, Sort sort);
d.也可以使用JPA的NamedQueries,方法如下:
1:在實(shí)體類上使用@NamedQuery:
@NamedQuery(name ="UserModel.findByAge",query = "select o from UserModel o where o.age >=?1")
2:在自己實(shí)現(xiàn)的DAO的Repository接口里面定義一個同名的方法,示例如下:
publicList<UserModel> findByAge(int age);
3:然后就可以使用了,Spring會先找是否有同名的NamedQuery,如果有,那么就不會按照接口定義的方法來解析。
e.還可以使用@Query來指定本地查詢,只要設(shè)置nativeQuery為true,比如:
@Query(value="select* from tbl_user where name like %?1" ,nativeQuery=true) publicList<UserModel> findByUuidOrAge(String name);
注意:當(dāng)前版本的本地查詢不支持翻頁和動態(tài)的排序
f.使用命名化參數(shù),使用@Param即可,比如:
@Query(value="selecto from UserModel o where o.name like %:nn") publicList<UserModel> findByUuidOrAge(@Param("nn") String name);
g.同樣支持更新類的Query語句,添加@Modifying即可,比如:
@Modifying @Query(value="updateUserModel o set o.name=:newName where o.name like %:nn") public intfindByUuidOrAge(@Param("nn") String name,@Param("newName")String newName);
注意:
1:方法的返回值應(yīng)該是int,表示更新語句所影響的行數(shù)
2:在調(diào)用的地方必須加事務(wù),沒有事務(wù)不能正常執(zhí)行
f.創(chuàng)建查詢的順序
Spring Data JPA在為接口創(chuàng)建代理對象時,如果發(fā)現(xiàn)同時存在多種上述情況可用,它該優(yōu)先采用哪種策略呢?
<jpa:repositories>提供了query-lookup-strategy 屬性,用以指定查找的順序。
它有如下三個取值:
1:create-if-not-found:如果方法通過@Query指定了查詢語句,則使用該語句實(shí)現(xiàn)查詢;如果沒有,則查找是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則通過解析方法名字來創(chuàng)建查詢。這是querylookup-strategy 屬性的默認(rèn)值
2:create:通過解析方法名字來創(chuàng)建查詢。即使有符合的命名查詢,或者方法通過
@Query指定的查詢語句,都將會被忽略
3:use-declared-query:如果方法通過@Query指定了查詢語句,則使用該語句實(shí)現(xiàn)查詢;如果沒有,則查找是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則拋出異常
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring Boot整合Elasticsearch實(shí)現(xiàn)全文搜索引擎案例解析
ElasticSearch作為基于Lucene的搜索服務(wù)器,既可以作為一個獨(dú)立的服務(wù)部署,也可以簽入Web應(yīng)用中。SpringBoot作為Spring家族的全新框架,使得使用SpringBoot開發(fā)Spring應(yīng)用變得非常簡單,在本案例中我們給大家介紹Spring Boot整合Elasticsearch實(shí)現(xiàn)全文搜索引擎2017-11-11Java?處理樹形結(jié)構(gòu)數(shù)據(jù)的過程
這篇文章主要介紹了Java?處理樹形結(jié)構(gòu)數(shù)據(jù)的過程,本文給大家分析具體實(shí)現(xiàn)過程,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-08-08Springboot整合Netty實(shí)現(xiàn)RPC服務(wù)器的示例代碼
這篇文章主要介紹了Springboot整合Netty實(shí)現(xiàn)RPC服務(wù)器的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01JDBC對MySQL數(shù)據(jù)庫布爾字段的操作方法
這篇文章主要介紹了JDBC對MySQL數(shù)據(jù)庫布爾字段的操作方法,實(shí)例分析了JDBC操作mysql布爾字段的原理與實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-02-02分析java中全面的單例模式多種實(shí)現(xiàn)方式
單例模式是一種常用的軟件設(shè)計模式,單例對象的類只能允許一個實(shí)例存在。許多時候整個系統(tǒng)只需要擁有一個的全局對象,有利于協(xié)調(diào)系統(tǒng)整體的行為。比如在某個服務(wù)器程序中,該服務(wù)器的配置信息存放在一個文件中。本文將介紹它的思想和多種實(shí)現(xiàn)方式2021-06-06