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ù)是否存在的接口方法:
@Repositorypublic 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提供了若干非常實用的擴(kuò)展,將數(shù)據(jù)庫表日常的CRUD操作都進(jìn)行很好的實現(xiàn),并提供了若干擴(kuò)展機制,基于一套簡單易用的命名規(guī)則,來基于聲明式實現(xiàn)場景的數(shù)據(jù)庫查詢操作:
countByColumNameexistsByColumnName
上述兩種方式都是由Spring Data來幫助動態(tài)生成SQL的。
基于@Query方式除了基于countBy/existsBy兩種方式之外,可以直接使用@Query方式來標(biāo)注特定的SQL或者JPQL來實現(xiàn)功能,由于這種方式需要些SQL,功能上完全覆蓋,但是工作量略大,不是最優(yōu)的方式。
問題分析由于底層使用了Hibernate/JPA/Spring Data來實現(xiàn)的數(shù)據(jù)訪問層的實現(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碰到兩個相同的類名之時,其實不知道如何來生成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ī)則,就可以不用寫實現(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è)查詢實體為Doc。
1:先判斷userDepUuid (根據(jù)POJO規(guī)范,首字母變?yōu)樾懀┦欠駷椴樵儗嶓w的一個屬性,如果是,則表示根據(jù)該屬性進(jìn)行查詢;如果沒有該屬性,繼續(xù)第二步;
2:從右往左截取第一個大寫字母開頭的字符串此處為Uuid),然后檢查剩下的字符串是否為查詢實體的一個屬性,如果是,則表示根據(jù)該屬性進(jìn)行查詢;如果沒有該屬性,則重復(fù)第二步,繼續(xù)從右往左截取;最后假設(shè)user為查詢實體的一個屬性;
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:在實體類上使用@NamedQuery:
@NamedQuery(name ='UserModel.findByAge',query = 'select o from UserModelo where o.age >=?1')
2:在自己實現(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')StringnewName);
注意:
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指定了查詢語句,則使用該語句實現(xiàn)查詢;如果沒有,則查找是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則通過解析方法名字來創(chuàng)建查詢。這是querylookup-strategy 屬性的默認(rèn)值
2:create:通過解析方法名字來創(chuàng)建查詢。即使有符合的命名查詢,或者方法通過
@Query指定的查詢語句,都將會被忽略
3:use-declared-query:如果方法通過@Query指定了查詢語句,則使用該語句實現(xiàn)查詢;如果沒有,則查找是否定義了符合條件的命名查詢,如果找到,則使用該命名查詢;如果兩者都沒有找到,則拋出異常
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持好吧啦網(wǎng)。
相關(guān)文章:
1. html小技巧之td,div標(biāo)簽里內(nèi)容不換行2. HTML DOM setInterval和clearInterval方法案例詳解3. XML入門的常見問題(一)4. CSS Hack大全-教你如何區(qū)分出IE6-IE10、FireFox、Chrome、Opera5. CSS hack用法案例詳解6. 詳解盒子端CSS動畫性能提升7. msxml3.dll 錯誤 800c0019 系統(tǒng)錯誤:-2146697191解決方法8. 詳解瀏覽器的緩存機制9. 告別AJAX實現(xiàn)無刷新提交表單10. 讀大數(shù)據(jù)量的XML文件的讀取問題
