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 的版本)。
背景鋪墊完畢,接下來(lái)開始正文。
最近在寫需求的時(shí)候用到了投影來(lái)減少數(shù)據(jù)庫(kù)查詢的字段,結(jié)果發(fā)現(xiàn)官方文檔中挖了個(gè)坑= =。官方文檔中以及另一篇示例文章中,全程使用了方法名派生的查詢方式,而投影的文檔中卻全程沒(méi)有提到示例的內(nèi)容僅在方法名派生的查詢方式下才有效。
那么,方法名派生的查詢方式好用嗎?對(duì)于簡(jiǎn)單的只有兩三個(gè)字段的查詢來(lái)說(shuō),確實(shí)方便好用,但條件一多,問(wèn)題就來(lái)了,如果有五六個(gè)字段要過(guò)濾,那方法名簡(jiǎn)直長(zhǎng)的不能看,并且很多查詢默認(rèn)值都需要通過(guò)參數(shù)傳進(jìn)來(lái)而不是直接內(nèi)置到 SQL 中。
在這種時(shí)候我更偏好使用自定義查詢的方式,直接面向 SQL 編程,比看巨長(zhǎng)的方法名要容易的多。
當(dāng)我在這次需求中把投影和自定義查詢一結(jié)合,這坑它就來(lái)了...
上面提過(guò),使用投影是為了減少數(shù)據(jù)庫(kù)查詢的字段。而直接運(yùn)行示例代碼的時(shí)候也確實(shí)看到了這個(gè)效果:
測(cè)試代碼
@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)我換成自定義查詢的方式時(shí),效果就變成了這樣:
測(cè)試代碼
@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í)在是讓人摸不著頭腦)。
后來(lái)有同事提醒說(shuō)是因?yàn)槲覍懥?code>select p導(dǎo)致的,我就嘗試寫明要查詢的字段(但還是無(wú)法理解為什么在這種情況下投影直接不生效):
測(cè)試代碼
@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 上來(lái)看,這樣寫已經(jīng)是實(shí)現(xiàn)了我想要的效果,可是實(shí)際上真正使用這個(gè)代碼的時(shí)候,坑就又來(lái)了:
測(cè)試代碼
@Test
public void whenUsingOpenProjections_thenViewWithRequiredPropertiesIsReturned2() {
PersonView personView = personRepository.findByLastNameByQuery("Doe");
assertThat(personView.getLastName()).isEqualTo("Doe");
}加了一行斷言來(lái)模擬使用的場(chǎng)景
執(zhí)行結(jié)果
org.opentest4j.AssertionFailedError:
expected: "Doe"
but was: null
Expected :"Doe"
Actual :null
直接黑人問(wèn)號(hào)臉。
分析了一下,執(zhí)行的 SQL 沒(méi)有問(wèn)題,投影類也沒(méi)有問(wèn)題,那問(wèn)題就是出在結(jié)果集映射的時(shí)候了。雖然沒(méi)看過(guò) JPA 的代碼,但是最終肯定是基于 JDBC API 的,而JDBC API是怎么處理結(jié)果集映射的?
翻一翻 ResultSet 類可以看到一共有兩種方法獲取結(jié)果:by index 和 by name,仔細(xì)看看執(zhí)行的 SQL,person0_.last_name as col_0_0_ last_name 自動(dòng)生成了一個(gè)別名叫col_0_0_,而投影類中能獲得的信息只有字段名last_name而沒(méi)有別名col_0_0_,所以 by name 的路走不通;
那么by index呢,很明顯也不行,我這里的示例只有一個(gè)字段,假如有兩個(gè)字段,那么SQL 中的字段的順序和投影類中的字段的順序就無(wú)法保證一致,從而就無(wú)法根據(jù) index 來(lái)獲取想要的對(duì)應(yīng)的結(jié)果。
然后就是驗(yàn)證環(huán)節(jié)了,假如是因?yàn)槊钟成洳簧蠈?dǎo)致的結(jié)果為 null,那我就給你一個(gè)能對(duì)應(yīng)的名字:
測(cè)試代碼
@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 上還是用了自動(dòng)生成的別名,但是斷言卻通過(guò)了,猜測(cè)是 JPA 在解析 Query 的時(shí)候存儲(chǔ)了手動(dòng)聲明的別名信息。
最后總結(jié)一下,如果要在 @Query 中使用投影,必須要主動(dòng)聲明要查詢的字段,并且主動(dòng)寫明字段的別名才行。
最后的最后,再吐槽一下 JPA,文檔中提到投影除了基于接口之外,還可以基于類來(lái)實(shí)現(xiàn),然鵝當(dāng)你想在 @Query 中使用基于類的投影時(shí),??~。
到此這篇關(guān)于Spring Data JPA 在 @Query 中使用投影的方法的文章就介紹到這了,更多相關(guān)Spring Data JPA 投影內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java設(shè)計(jì)模式之中介者模式(Mediator Pattern)簡(jiǎn)介
這篇文章主要介紹了Java設(shè)計(jì)模式之中介者模式(Mediator Pattern),需要的朋友可以參考下2014-07-07
Java 鎖的知識(shí)總結(jié)及實(shí)例代碼
這篇文章主要介紹了Java 鎖的知識(shí)總結(jié)及實(shí)例代碼,需要的朋友可以參考下2016-09-09
劍指Offer之Java算法習(xí)題精講鏈表與二叉樹專項(xiàng)訓(xùn)練
跟著思路走,之后從簡(jiǎn)單題入手,反復(fù)去看,做過(guò)之后可能會(huì)忘記,之后再做一次,記不住就反復(fù)做,反復(fù)尋求思路和規(guī)律,慢慢積累就會(huì)發(fā)現(xiàn)質(zhì)的變化2022-03-03
舉例講解Java的Spring框架中AOP程序設(shè)計(jì)方式的使用
這篇文章主要介紹了Java的Spring框架中AOP程序設(shè)計(jì)方式的使用講解,文中舉的AOP下拋出異常的例子非常實(shí)用,需要的朋友可以參考下2016-04-04
IDEA搭建純注解版本SpringMVC的web開發(fā)環(huán)境全過(guò)程并分析啟動(dòng)原理
本文詳細(xì)介紹了如何使用注解開發(fā)搭建Spring Web環(huán)境,包括創(chuàng)建Maven工程、配置web環(huán)境、設(shè)置pom.xml、創(chuàng)建配置類和控制器等步驟,同時(shí),文章還探討了注解開發(fā)中如何創(chuàng)建IOC容器和添加DispatcherServlet組件,并通過(guò)Servlet 3.0規(guī)范2024-11-11
Spring security認(rèn)證兩類用戶代碼實(shí)例
這篇文章主要介紹了Spring security認(rèn)證兩類用戶代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
Mybatis-Plus自動(dòng)生成的數(shù)據(jù)庫(kù)id過(guò)長(zhǎng)的解決
這篇文章主要介紹了Mybatis-Plus自動(dòng)生成的數(shù)據(jù)庫(kù)id過(guò)長(zhǎng)的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
java動(dòng)態(tài)構(gòu)建數(shù)據(jù)庫(kù)復(fù)雜查詢教程
這篇文章主要介紹了java動(dòng)態(tài)構(gòu)建數(shù)據(jù)庫(kù)復(fù)雜查詢的實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-11-11

