# Spring boot解析: chapter5 JPA, SQL db

Figure. 高層次抽象, 以JPA為例

Figure. Repository interfaces: 藍色為spring data核心interface
### :microscope: Specification 動態條件查詢
---
> JPA提供Criteria查詢, Spring Data JPA則對Criteria進行封裝
- api interface: SpecificationExecutor
- long count(Specification<T> spec);
- Optional<T> findOne(Specification<T> spec)
- List<T> findAll(Specification<T> spec)
- List<T> findAll(Specification<T> spec, Sort sort)
- Page<T> findAll(Specification<T> spec, Pageable pageable)
- Class specification 組合動態查詢條件
- 定義一個方法需覆寫: Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
- root 物件本身
- query 最終語法
- criteriaBuilder: 生成各種查詢語法(equal() ==, ge() >=, gt() >)
```java
Specification<查詢目標類> specification = (root, query, criteriabuilder) -> {
List<Predicate> predicates = new ArrayList<>();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate start = LocalDate.parse(transReq.getStartDate(), formatter);
LocalDate end = LocalDate.parse(transReq.getEndDate(), formatter);
predicates.add(criteriabuilder.between(root.get("executeDate"), start, end));
// select t from 目標類 where t.apid = ?1
predicates.add(criteriabuilder.equal(root.get("apid"), apid));
if (StringUtils.hasText(transReq.getiContactId())) {
predicates.add(criteriabuilder.equal(root.get("eformId"), transReq.getiContactId()));
}
if (StringUtils.hasText(transReq.getChangeTypeId())) {
predicates.add(criteriabuilder.equal(root.get("changeTypeId"), transReq.getChangeTypeId()));
}
if (StringUtils.hasText(transReq.getIsActive())) {
predicates.add(criteriabuilder.equal(root.get("isActive"), transReq.getIsActive()));
}
// where: 根據特定的predicate產生特定的specification, 參數為陣列
// 轉成陣列: predicates.toArray(new Predicate[predicates.size()])
// 最後透過getRestriction()取得對應的predicate
return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
};
```
Repository 需繼承JpaSpecificationExecutor
```java
public interface SreEformChangeinfoRepository
extends JpaRepository<SreEformChangeinfo, String>, JpaSpecificationExecutor<SreEformChangeinfo> {
// 通常會再寫靜態方法來組合查詢條件, 如前面的Specification的部分
}
```
Parameter binding
---
預設以位置(position-based) binding,這種方法容易導致錯誤,@Query做name binding的方式
(1) `@Param`註解,例如:

(2) 在Java 8以上的版本,javac compile flag 能傳入 `-parameter`,gradle可傳入參數
TODO: 傳入flag,但測試API仍未解決

(3) Eclipse對應的設定方法:store information about method parameters

-parameters @Param的差別
---
1. **-parameters Compiler Option**
編譯Java source code時使用,若Java class包含方法參數,由於效能考量及減少編譯程式的size,參數名稱通常不會從編譯好的bytecode取得,這個參數告訴compiler要包含parameter names在compiled bytecode,包含參數名稱有利於依賴反射的tools及frameworks,像是在web frameworks HTTP request binding的parameters
`javac -parameters MyClass.java`
2. **@param Javadoc Tag (Spring data JPA)**
用來標記Java方法及constructor的參數,map方法參數到自定義sql query的placeholder(外部取用),不涉略編譯過程,而 @param 主要是用於生成文檔,提供對方法參數的說明,顯示指定名稱。
:point_right: 兩者的用途不同,使用-parameters可omit @Param,但就要另加注意命名與sql query的變數名稱一致
```java
@Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Long> {
@Query("SELECT e FROM MyEntity e WHERE e.someProperty = :paramValue")
List<MyEntity> findBySomeProperty(@Param("paramValue") String testValue);
}
```
參考
- [Spring Data JPA 何时必须使用@Param注解](https://blog.csdn.net/mryang125/article/details/100604645)
- [Using Named Parameters](https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html)
- [Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters](https://stackoverflow.com/questions/66398543/use-param-for-query-method-parameters-or-when-on-java-8-use-the-javac-flag-p)
- [如何省略springboot项目中的@Param注解](https://blog.csdn.net/qqgg0216/article/details/134132118?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EYuanLiJiHua%7EPosition-2-134132118-blog-87954102.235%5Ev40%5Epc_relevant_3m_sort_dl_base1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EYuanLiJiHua%7EPosition-2-134132118-blog-87954102.235%5Ev40%5Epc_relevant_3m_sort_dl_base1&utm_relevant_index=5)