# 六角鼠年鐵人賽 Week 27 - Spring Boot - Spring Data & JPA JPQL ==大家好,我是 "為了拿到金角獎盃而努力著" 的文毅青年 - Kai== ## 諸葛孔明 :::info 非淡泊無以明志,非寧靜無以致遠。 ::: ## 主題 有鑑於 @Query Annotation 的部分可能還是有些模糊的地方,因此特別抽出這一塊來說明。 希望讓讀者可以更了解究竟這些寫在 Repository 中的 Query 可以如何變化,讓開發人員能夠更得心應手地寫出符合需求的 SQL commands。 Kai 在上週提及了 Spring Data & JPA 是透過一種 JPQL 的方式處理 SQL 指令。 其實不全然是 JPQL,還有 Native SQL 的部分也包含在 JPA 當中。 JPA 提供了兩種方式讓開發者做彈性化的處理~ ## JPQL 和 Native SQL JPQL 的全名是 **Java Persistence Query Language**,是一種與使用 DB 無關的的物件導向 SQL,實作的方法都已被封裝在 JPA 中,使用者必須遵從 JPQL 規定的格式進行值 (SQL 指令) 的設定,以及帶入參數、條件、集合、排序處理等方法的使用,方能讓 JPA 組成正確的 SQL 指令並執行。 而 JPA 則會根據使用 DB 的類型,決定組成 SQL 指令時候的保留字或 SQL 組成規範,這讓開發人員不需要針對不同的 DB 做太大幅度的調整,盡量讓 JPA 幫忙處理瑣碎的事情即可,因此大大增加了開發的彈性。 兩者最大的差異在於,JPQL 使用的是 Entity 的物件替代了原本的 Table,而 Native SQL 則是使用了原本的 Table。 因此在執行時,JPQL 針對的部分是 Entity 的部分,而 Native SQL 則會將參數、條件帶入後就直接送到 SQL 執行。 :::danger 許多人在寫 JPQL 的指令時常會遇到以下錯誤訊息˙ > Validation failed for query for method public abstract java.util.List 這是因為在寫入的 @Query 中,並沒有正確的使用到 Entity 的表名稱與欄位名稱,因此雖然可以 Compile 過,但在執行時卻因為 Bean 開始帶入建置的關係無法找到對應的 Class 而出錯。 以上週的範例舉實際例子: ```java= // 錯誤 @Query("SELECT * FROM EMPLOYEE as e WHERE e.EMP_ID <> ?1") List<Employee> findByPriorityExclude(Long number1); // 正確 @Query("SELECT * FROM Employee as e WHERE e.emp_id <> ?1") List<Employee> findByPriorityExclude(Long number1); ``` 這兩段 SQL 指令基本上無論在任何 DB 來看都是沒有任何問題的 (除了 ClickHouse 這種非常重視大小寫名稱的之外) 但因為 Kai 的 Entity 中,資料表名稱為 Empolyee 以及 欄位名稱為 emp_id,後續便會因為 **大小寫** 的關係促使 JPA 無法找到對應的 Entity 而報錯。 ::: 最後與 Native SQL 比較來看,雖然 JPQL 避免了針對不同 DB 的修改,相反的,在基於 Entity 的規範下,JPQL 僅支援標準的子查詢指令,因此無法像 Native SQL 一次性的使用多階層、多 Table 的子查詢 SQL 指令,意即無法在 JPQL 中做太多複雜的變化。 ## 應用方式 JPQL 和 Native SQL 的使用法非常簡單,用上述的例子繼續舉例,以下分別是 JPQL 和 Native SQL 對於同一段 SQL 指令的寫法。 ```java= // JPQL @Query("SELECT * FROM Employee as e WHERE e.emp_id <> ?1") List<Employee> findByPriorityExclude(Long number1); // Native SQL @Query(nativeQuery = true, value = "SELECT * FROM Employee as e WHERE e.emp_id <> ?1") List<Employee> findByPriorityExclude(Long number1); ``` 關鍵字就是 nativeQuery 這個參數必須要被設置為 true,而且是每一個 Native SQL 的 @Query 都要這樣設置,並沒有支援一次設定全部使用 Native SQL 的方式,但可以透過另外一種方式實現。 那就是在 Entity 中直接使用 @NamedNativeQueries + @NamedNativeQuery 的方式 ```java= @NamedNativeQueries({ @NamedNativeQuery( name = "Employee.findByPriorityExclude", query = "SELECT * FROM Employee as e WHERE e.emp_id <> ?1", resultClass=Employee.class ), @NamedNativeQuery( ... ), }) ``` 完整 Entity Class ```java= package kai.com.jpa.entity; import javax.persistence.*; import java.sql.Date; @Entity @NamedNativeQueries({ @NamedNativeQuery( name = "Employee.findByPriorityExclude", query = "SELECT * FROM Employee as e WHERE e.emp_id <> ?1", resultClass=Employee.class ) }) public class Employee { public Employee (){} public Employee(String name, String team, Date birthDate){ this.emp_name = name; this.emp_team = team; this.emp_birthDate = birthDate; } @Id @GeneratedValue(strategy= GenerationType.AUTO) private Long emp_id; private String emp_name; private String emp_team; private Date emp_birthDate; public Long getEmp_id() { return emp_id; } public void setEmp_id(Long emp_id) { this.emp_id = emp_id; } public String getEmp_name() { return emp_name; } public void setEmp_name(String emp_name) { this.emp_name = emp_name; } public String getEmp_team() { return emp_team; } public void setEmp_team(String emp_team) { this.emp_team = emp_team; } public Date getEmp_birthDate() { return emp_birthDate; } public void setEmp_birthDate(Date emp_birthdate) { this.emp_birthDate = emp_birthdate; } } ``` 而後我們在 Repository 的設定就不需要再使用 @Query 而可以直接是一個方法,這個方法名稱會直接對應到剛剛設置的 @NamedNativeQuery 的 name 參數去,從而達到相同的效果 Repository Class ```java= //@Query("SELECT * FROM Employee as e WHERE e.emp_id <> ?1") List<Employee> findByPriorityExclude(Long number1); ``` ## 置入參數的方式 置入參數的方式有兩種,一種是常見的、類似 Stored Procedure 方式的 Named Parameters,另外一種是 JPQL 特有的 Positional Parameters ```java= // JPQL Named Parameters 方式 1 @Query("FROM Employee as e WHERE e.emp_id <> :number1") List<Employee> findByPriorityExclude(Long number1); // JPQL Named Parameters 方式 2 @Query("FROM Employee as e WHERE e.emp_id <> :getEmpId") List<Employee> findByPriorityExclude(@Param("getEmpId") Long number1); // JPQL Positional Parameters 方式 @Query("FROM Employee as e WHERE e.emp_id <> ?1") List<Employee> findByPriorityExclude(Long number1); ``` **Positional Parameters** - 依照傳入的參數順序,將其置入 SQL 中 - 簡單易懂 **Named Parameters** - 依照對應名稱的參數,將其置入 SQL 中 - 若沒有經過良好整理,容易錯亂 ## 結語 :::danger 其他還有如 Sorting 和 Paging 等應用方式,基本上與上述的例子大同小異,就不多作介紹~ ~~以上差不多就結束了基本的 Spring Data & JPA 的單元部分了~~ ~~下一期可以考慮學習 Activity Flow 或是 進入 Spring Cloud 部分(這部分尷尬的是現在諸多被 K8S 取代掉了,學習價值不高... 但為了充實所有的基本知識,應該還是會放在主力目標!)~~ 計畫趕不上變化... 才發現 Spring Data & JPA還漏了部分想要分享的內容,見諒,詳細會分享在第 28 篇 [六角鼠年鐵人賽 Week 28 - Spring Boot - Spring Data & JPA QueryDSL](/mC-2-op_Qe-tVLoJ2x-d1w) ::: 首頁 [Kai 個人技術 Hackmd](/2G-RoB0QTrKzkftH2uLueA) ###### tags: `Spring Boot`,`Spring Data`,`w3HexSchool`
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up