Spring Data JPA 在 @Query 中使用投影的方法示例詳解
Spring Data JPA 在 @Query 中使用投影的方法
關於投影的基本使用可以參考這篇文章: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 的版本)。
背景鋪墊完畢,接下來開始正文。
最近在寫需求的時候用到瞭投影來減少數據庫查詢的字段,結果發現官方文檔中挖瞭個坑= =。官方文檔中以及另一篇示例文章中,全程使用瞭方法名派生
的查詢方式,而投影的文檔中卻全程沒有提到示例的內容僅在方法名派生
的查詢方式下才有效。
那麼,方法名派生
的查詢方式好用嗎?對於簡單的隻有兩三個字段的查詢來說,確實方便好用,但條件一多,問題就來瞭,如果有五六個字段要過濾,那方法名簡直長的不能看,並且很多查詢默認值都需要通過參數傳進來而不是直接內置到 SQL 中。
在這種時候我更偏好使用自定義查詢
的方式,直接面向 SQL 編程,比看巨長的方法名要容易的多。
當我在這次需求中把投影和自定義查詢
一結合,這坑它就來瞭…
上面提過,使用投影是為瞭減少數據庫查詢的字段。而直接運行示例代碼的時候也確實看到瞭這個效果:
測試代碼
@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; }
執行的 SQL
select person0_.last_name as col_0_0_ from person person0_ where person0_.last_name=?
然後當我換成自定義查詢
的方式時,效果就變成瞭這樣:
測試代碼
@Query("select p from Person p where p.lastName = ?1") PersonView findByLastNameByQuery(String lastName); @Test public void whenUsingOpenProjections_thenViewWithRequiredPropertiesIsReturned2() { PersonView personView = personRepository.findByLastNameByQuery("Doe"); }
執行的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=?
可以看到這裡是查詢瞭全部的字段(實在是讓人摸不著頭腦)。
後來有同事提醒說是因為我寫瞭select p
導致的,我就嘗試寫明要查詢的字段(但還是無法理解為什麼在這種情況下投影直接不生效):
測試代碼
@Query("select p.lastName from Person p where p.lastName = ?1") PersonView findByLastNameByQuery(String lastName);
執行的 SQL
select person0_.last_name as col_0_0_ from person person0_ where person0_.last_name=?
從 SQL 上來看,這樣寫已經是實現瞭我想要的效果,可是實際上真正使用這個代碼的時候,坑就又來瞭:
測試代碼
@Test public void whenUsingOpenProjections_thenViewWithRequiredPropertiesIsReturned2() { PersonView personView = personRepository.findByLastNameByQuery("Doe"); assertThat(personView.getLastName()).isEqualTo("Doe"); }
加瞭一行斷言來模擬使用的場景
執行結果
org.opentest4j.AssertionFailedError:
expected: "Doe"
but was: null
Expected :"Doe"
Actual :null
直接黑人問號臉。
分析瞭一下,執行的 SQL 沒有問題,投影類也沒有問題,那問題就是出在結果集映射的時候瞭。雖然沒看過 JPA 的代碼,但是最終肯定是基於 JDBC API
的,而JDBC API
是怎麼處理結果集映射的?
翻一翻 ResultSet
類可以看到一共有兩種方法獲取結果:by index
和 by name
,仔細看看執行的 SQL,person0_.last_name as col_0_0_
last_name 自動生成瞭一個別名叫col_0_0_
,而投影類中能獲得的信息隻有字段名last_name
而沒有別名col_0_0_
,所以 by name
的路走不通;
那麼by index
呢,很明顯也不行,我這裡的示例隻有一個字段,假如有兩個字段,那麼SQL 中的字段的順序和投影類中的字段的順序就無法保證一致,從而就無法根據 index 來獲取想要的對應的結果。
然後就是驗證環節瞭,假如是因為名字映射不上導致的結果為 null,那我就給你一個能對應的名字:
測試代碼
@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"); }
執行的 SQL
select person0_.last_name as col_0_0_ from person person0_ where person0_.last_name=?
雖然執行的 SQL 上還是用瞭自動生成的別名,但是斷言卻通過瞭,猜測是 JPA 在解析 Query 的時候存儲瞭手動聲明的別名信息。
最後總結一下,如果要在 @Query 中使用投影,必須要主動聲明要查詢的字段,並且主動寫明字段的別名才行。
最後的最後,再吐槽一下 JPA,文檔中提到投影除瞭基於接口之外,還可以基於類來實現,然鵝當你想在 @Query 中使用基於類的投影時,💥~。
到此這篇關於Spring Data JPA 在 @Query 中使用投影的方法的文章就介紹到這瞭,更多相關Spring Data JPA 投影內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 詳解使用Spring Data repository進行數據層的訪問問題
- Java各種比較對象的方式的對比總結
- JPA自定義對象接收查詢結果集操作
- jpa介紹以及在spring boot中使用詳解
- SpringData如何通過@Query註解支持JPA語句和原生SQL語句