# 六角鼠年鐵人賽 Week 28 - Spring Boot - Spring Data & JPA QueryDSL ==大家好,我是 "為了拿到金角獎盃而努力著" 的文毅青年 - Kai== ## :::info 要怎麼收穫,先怎麼栽 ::: ## 主題 上篇說完了 **JPQL** 之後,其實在 Spring Data & JPA 還提供了另外一種優美的方式去建構 SQL,那就是 **QueryDSL**。 ## QueryDSL 是 JPA 的擴展套件,繼承了 JPQL 物件導向的優點,擁有比 JPA 處理更複雜 SQL 語句組件的功能。 在專案進行 Compile 後,QueryDSL 透過與 Lombok 連動針對 Entity 的物件生成 Q + "Entity Class Name" 的新物件,以上次的 Employee.java 來說,就會在 Compile 後新建 QEmployee.class 物件,這個物件就是在執行 QueryDSL 相關功能時,針對 Entity 物件提供 SQL 組件的處理。 ## Q Class Function 這些透過 QueryDSL 和 Lombok 產出的 Q Class 帶有許多組建 SQL 的方法 | 主架構 Function | Info | | ---- | ---- | | select (Q Object) |放入指定的 Q 欄位| | from (Q Object) |放入指定的 Entity,通常用來做多表聯動處理| | where (Q Object) |放入指定的 Q 欄位的條件句,例如: eq() 或 in()| | groupBy (Q Object) |放入指定的 Q 欄位| | orderBy (Q Object) |放入指定的 Q 欄位的 desc() 或 asc()| | 常見的欄位 Function | Info | | ---- | ---- | | eq | 判定相符的功能,會組建出 Q column = Integer or String | | in | 判定與內容其中之一相符的功能,同 SQL 中的 IN () | | like | 判定與內容有部分相符,同 SQL 中的 LIKE | | isNull | 判定 Null 的功能 | | isNotNull | 判定 Not Null 的功能 | | count | 計算該欄位的資料筆數 | | countDistinct | 計算該欄位的不重複資料筆數 | | sum | 總和該欄位的值,同 SQL 中的 SUM | | between | 判定內容包覆在條件中,同 SQL 的 BETWEEN | 當然 QueryDSL 繼承了 JDK 1.8 Stream 的特性 - 急性與惰性。 上述所有的設定皆為**惰性**,因此必須要有一個急性方法驅動才會執行。 下列就是執行時會使用到的方法 | 執行 Function | Info | | ---- | ---- | | fetch | 執行組建完成的 Query,也是最基本的查詢法 | | fetchOne |執行後會取出一筆資料的查詢法| | fetchAll |執行後會取出所有結果的查詢法與基本 fetch 不同的是可以針對 JOIN 的部分保全資料的完整性| | fetchFirst |執行後會取出第一筆的查詢法| | fetchJoin |執行後可以接續聯動其他 Query 的查詢法| | fetchResults |配合分頁功能的查詢法| ## RepositoryImpl Class 原先由 JPA 處理的 Repository 方式非常簡單易懂,但 QueryDSL 原先設計的用意就是為了讓開發人員可以自由的組建 SQL 語句。 因此在開發的時候,由 **RepositoryImpl** Class 來進行自定義的 SQL 語句處理,該物件與 Repository 並無關係存在,僅是用取名的方式確立其屬於操作同一 Entity 的一種習慣,也方便讓其他開發人員快速了解有兩個 Class 在操作同一個 Entity。 會需要在其中使用到的特殊物件為 **EntityManager** 和 **JPAQuery**。 EntityManager 會在 RunTime 得到所有的 Entity 物件集合,給 JPAQuery 用來確立其處理的 Entity 有哪些。 JPAQuery 用來組件 SQL,其包含主架構 Function、執行 Function 和部分功能 Function。 ## 範例架構 ![](https://i.imgur.com/SOWggHk.png) ## 範例 **build.gradle** ``` implementation "com.querydsl:querydsl-jpa" // querydsl implementation "com.querydsl:querydsl-apt" // querydsl annotationProcessor( "com.querydsl:querydsl-apt:4.1.4:jpa", "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final", "javax.annotation:javax.annotation-api", "org.projectlombok:lombok:1.18.6" ) ``` **Employee2.java** ```java= package kai.com.jpa.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import java.sql.Date; @Entity public class Employee2 { public Employee2(){} public Employee2(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; } } ``` **EmployeeRepository2.java** ```java= package kai.com.jpa.repository; import kai.com.jpa.entity.Employee2; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.repository.CrudRepository; public interface EmployeeRepository2 extends CrudRepository<Employee2, Long>, QuerydslPredicateExecutor<Employee2> { } ``` **EmployeeRepository2Impl.java** ```java= package kai.com.jpa.queryDSLRepositoryImpl; import com.querydsl.jpa.impl.JPAQuery; import kai.com.jpa.entity.Employee2; import kai.com.jpa.entity.QEmployee2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.persistence.EntityManager; import java.util.List; @Component public class EmployeeRepository2Impl { @Autowired EntityManager entityManager; public List<Employee2> findByName(String name){ QEmployee2 emp = QEmployee2.employee2; JPAQuery query = new JPAQuery(entityManager); query.from(emp) .where(emp.emp_name.eq(name)) .orderBy(emp.emp_id.desc()); return query.fetch(); } } ``` **JPATestController.java** ```java= package kai.com.jpa.controller; import kai.com.jpa.entity.Employee; import kai.com.jpa.entity.Employee2; import kai.com.jpa.entity.QEmployee; import kai.com.jpa.queryDSLRepositoryImpl.EmployeeRepository2Impl; import kai.com.jpa.repository.EmployeeRepository; import kai.com.jpa.repository.EmployeeRepository2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.sql.Date; import java.util.List; import java.util.Optional; @RestController @RequestMapping("/getEmp") public class JPATestController { @Autowired EmployeeRepository employeeRepository; @Autowired EmployeeRepository2Impl employeeRepository2Impl; @Autowired EmployeeRepository2 employeeRepository2; @GetMapping("/build") public String buildTableAndData(){ /*** 建立測試資料 ***/ Employee emp = new Employee(); emp.setEmp_name("Michael"); emp.setEmp_team("IT"); emp.setEmp_birthDate(new Date(19920101)); employeeRepository.save(emp); emp = new Employee(); emp.setEmp_name("Cherry"); emp.setEmp_team("Salse"); emp.setEmp_birthDate(new Date(19900101)); employeeRepository.save(emp); emp = new Employee(); emp.setEmp_name("Stanley"); emp.setEmp_team("Product"); emp.setEmp_birthDate(new Date(19960101)); employeeRepository.save(emp); Employee2 emp2 = new Employee2(); emp2.setEmp_name("Michael"); emp2.setEmp_team("IT"); emp2.setEmp_birthDate(new Date(19920101)); employeeRepository2.save(emp2); emp2 = new Employee2(); emp2.setEmp_name("Cherry"); emp2.setEmp_team("Salse"); emp2.setEmp_birthDate(new Date(19900101)); employeeRepository2.save(emp2); emp2 = new Employee2(); emp2.setEmp_name("Stanley"); emp2.setEmp_team("IT"); emp2.setEmp_birthDate(new Date(19960101)); employeeRepository2.save(emp2); return "Done build."; } @GetMapping() public List<Employee> getAll(){ return employeeRepository.findAll(); } @GetMapping("/{number1}/{number2}") public List<Employee> getByRange(@PathVariable("number1") Long number1, @PathVariable("number2") Long number2){ return employeeRepository.findByPriorityBetween(number1,number2); } @GetMapping("/{id}") public Optional<Employee> getById(@PathVariable("id") Long Id){ return employeeRepository.findById(Id); } @DeleteMapping("/{id}") public String deleteById(@PathVariable("id") Long Id){ employeeRepository.deleteById(Id); if(!employeeRepository.findById(Id).isPresent()) return "Delete Successfully."; return "Delete failed."; } @GetMapping("exclude/{id}") public List<Employee> excludeById(@PathVariable("id") Long Id){ return employeeRepository.findByPriorityExclude(Id); } @GetMapping("/emp2/{name}") public List<Employee2> getByName(@PathVariable("name") String name){ return employeeRepository2Impl.findByName(name); } } ``` ## 結語 :::danger 這一篇文章大致說明了 QueryDSL 的用法,基本的會寫之後,相信複雜的語法也不會是問題~ Kai 下一篇會來分享使用 JPQL 和 QueryDSL 做分頁的功能,也是這套建比較常見的用途~ [六角鼠年鐵人賽 Week 29 - Spring Boot - Spring Data & JPA Paging](/fUMP8BCiRgC6LGLqgJ957g) ::: 首頁 [Kai 個人技術 Hackmd](/2G-RoB0QTrKzkftH2uLueA) ###### tags: `Spring Boot`,`Spring Data`,`w3HexSchool`