# 六角鼠年鐵人賽 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。
## 範例架構

## 範例
**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`