Spring?Data?JPA?在?@Query?中使用投影的方法示例詳解
Spring Data JPA 在 @Query 中使用投影的方法
關(guān)于投影的基本使用可以參考這篇文章:https://www.baeldung.com/spring-data-jpa-projections。下文沿用了這篇文章中的示例代碼。
投影的官方文檔鏈接是:https://docs.spring.io/spring-data/jpa/docs/2.6.5/reference/html/#projections (我這里使用的是 2.6.5 的版本)。
背景鋪墊完畢,接下來開始正文。
最近在寫需求的時候用到了投影來減少數(shù)據(jù)庫查詢的字段,結(jié)果發(fā)現(xiàn)官方文檔中挖了個坑= =。官方文檔中以及另一篇示例文章中,全程使用了方法名派生
的查詢方式,而投影的文檔中卻全程沒有提到示例的內(nèi)容僅在方法名派生
的查詢方式下才有效。
那么,方法名派生
的查詢方式好用嗎?對于簡單的只有兩三個字段的查詢來說,確實(shí)方便好用,但條件一多,問題就來了,如果有五六個字段要過濾,那方法名簡直長的不能看,并且很多查詢默認(rèn)值都需要通過參數(shù)傳進(jìn)來而不是直接內(nèi)置到 SQL 中。
在這種時候我更偏好使用自定義查詢
的方式,直接面向 SQL 編程,比看巨長的方法名要容易的多。
當(dāng)我在這次需求中把投影和自定義查詢
一結(jié)合,這坑它就來了...
上面提過,使用投影是為了減少數(shù)據(jù)庫查詢的字段。而直接運(yùn)行示例代碼的時候也確實(shí)看到了這個效果:
測試代碼
@Test public void whenUsingOpenProjections_thenViewWithRequiredPropertiesIsReturned() { PersonView personView = personRepository.findByLastName("Doe"); } public interface PersonView { String getLastName(); } @Entity public class Person { @Id private Long id; private String firstName; private String lastName; }
執(zhí)行的 SQL
select person0_.last_name as col_0_0_ from person person0_ where person0_.last_name=?
然后當(dāng)我換成自定義查詢
的方式時,效果就變成了這樣:
測試代碼
@Query("select p from Person p where p.lastName = ?1") PersonView findByLastNameByQuery(String lastName); @Test public void whenUsingOpenProjections_thenViewWithRequiredPropertiesIsReturned2() { PersonView personView = personRepository.findByLastNameByQuery("Doe"); }
執(zhí)行的SQL
select person0_.id as id1_6_, person0_.first_name as first_na2_6_, person0_.last_name as last_nam3_6_ from person person0_ where person0_.last_name=?
可以看到這里是查詢了全部的字段(實(shí)在是讓人摸不著頭腦)。
后來有同事提醒說是因?yàn)槲覍懥?code>select p導(dǎo)致的,我就嘗試寫明要查詢的字段(但還是無法理解為什么在這種情況下投影直接不生效):
測試代碼
@Query("select p.lastName from Person p where p.lastName = ?1") PersonView findByLastNameByQuery(String lastName);
執(zhí)行的 SQL
select person0_.last_name as col_0_0_ from person person0_ where person0_.last_name=?
從 SQL 上來看,這樣寫已經(jīng)是實(shí)現(xiàn)了我想要的效果,可是實(shí)際上真正使用這個代碼的時候,坑就又來了:
測試代碼
@Test public void whenUsingOpenProjections_thenViewWithRequiredPropertiesIsReturned2() { PersonView personView = personRepository.findByLastNameByQuery("Doe"); assertThat(personView.getLastName()).isEqualTo("Doe"); }
加了一行斷言來模擬使用的場景
執(zhí)行結(jié)果
org.opentest4j.AssertionFailedError:
expected: "Doe"
but was: null
Expected :"Doe"
Actual :null
直接黑人問號臉。
分析了一下,執(zhí)行的 SQL 沒有問題,投影類也沒有問題,那問題就是出在結(jié)果集映射的時候了。雖然沒看過 JPA 的代碼,但是最終肯定是基于 JDBC API
的,而JDBC API
是怎么處理結(jié)果集映射的?
翻一翻 ResultSet
類可以看到一共有兩種方法獲取結(jié)果:by index
和 by name
,仔細(xì)看看執(zhí)行的 SQL,person0_.last_name as col_0_0_
last_name 自動生成了一個別名叫col_0_0_
,而投影類中能獲得的信息只有字段名last_name
而沒有別名col_0_0_
,所以 by name
的路走不通;
那么by index
呢,很明顯也不行,我這里的示例只有一個字段,假如有兩個字段,那么SQL 中的字段的順序和投影類中的字段的順序就無法保證一致,從而就無法根據(jù) index 來獲取想要的對應(yīng)的結(jié)果。
然后就是驗(yàn)證環(huán)節(jié)了,假如是因?yàn)槊钟成洳簧蠈?dǎo)致的結(jié)果為 null,那我就給你一個能對應(yīng)的名字:
測試代碼
@Query("select p.lastName as lastName from Person p where p.lastName = ?1") PersonView findByLastNameByQuery(String lastName); @Test public void whenUsingOpenProjections_thenViewWithRequiredPropertiesIsReturned2() { PersonView personView = personRepository.findByLastNameByQuery("Doe"); assertThat(personView.getLastName()).isEqualTo("Doe"); }
執(zhí)行的 SQL
select person0_.last_name as col_0_0_ from person person0_ where person0_.last_name=?
雖然執(zhí)行的 SQL 上還是用了自動生成的別名,但是斷言卻通過了,猜測是 JPA 在解析 Query 的時候存儲了手動聲明的別名信息。
最后總結(jié)一下,如果要在 @Query 中使用投影,必須要主動聲明要查詢的字段,并且主動寫明字段的別名才行。
最后的最后,再吐槽一下 JPA,文檔中提到投影除了基于接口之外,還可以基于類來實(shí)現(xiàn),然鵝當(dāng)你想在 @Query 中使用基于類的投影時,??~。
到此這篇關(guān)于Spring Data JPA 在 @Query 中使用投影的方法的文章就介紹到這了,更多相關(guān)Spring Data JPA 投影內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Quarkus中實(shí)現(xiàn)Resteasy的文件上傳下載操作
這篇文章主要為大家介紹了Quarkus中實(shí)現(xiàn)Resteasy的文件上傳下載的操作過程步驟,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-02-02Java使用PrepareStatement實(shí)現(xiàn)數(shù)據(jù)的插入與查詢操作
這篇文章主要為大家詳細(xì)介紹了Java如何使用PrepareStatement實(shí)現(xiàn)數(shù)據(jù)的插入與查詢操作,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-09-09解決JAVA遍歷List集合,刪除數(shù)據(jù)時出現(xiàn)的問題
這篇文章主要介紹了解決JAVA遍歷List集合時,刪除數(shù)據(jù)出現(xiàn)的問題,文中講解非常細(xì)致,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-07-07SpringMVC @RequestBody自動轉(zhuǎn)json Http415錯誤的解決
這篇文章主要介紹了SpringMVC @RequestBody自動轉(zhuǎn)json Http415錯誤的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04Springboot ApplicationRunner的使用解讀
這篇文章主要介紹了Springboot ApplicationRunner的使用解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05springboot2?使用activiti6?idea插件的過程詳解
這篇文章主要介紹了springboot2?使用activiti6?idea插件,本文通過截圖實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03Java實(shí)戰(zhàn)權(quán)限管理系統(tǒng)的實(shí)現(xiàn)流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SpringBoot+MyBatis+AOP+LayUI+Mysql實(shí)現(xiàn)一個權(quán)限管理系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2022-01-01