# [Spring Data JPA] 自定義 Repository
###### tags: `Spring` `Java` `Spring Data JPA` `Spring Boot`
[TOC]
## JPA的生命週期
JpaRepository(介面) 的子介面(如: UserRepository)
在建立實作物件時會
1. 找有 impl 的類別(如: UserRepositoryImpl)
* 可藉由設定 `@EnableJpaRepositories` 修改屬性
* basePackages--> 掃描 Repositories 所在的 package 及子 package,可多個
* repositoryImplementationPostfix--> 字尾設定
```=Java!
//單個
@EnableJpaRepositories(basePackages = "com.dao2",
repositoryImplementationPostfix = "xyz",)
//多個
@EnableJpaRepositories(basePackages = {"com.dao2", "com.winnie"},
repositoryImplementationPostfix = "xyz",)
```
* Spring 的 xml
```=xml!
<jpa:repository base-packages="com.dao2"
repository-impl-postfix="xyz" />
```
3. 如果有,會和JPA自動實作的方法合併
## 自訂JPA方法
```!
[動作][要操作的資料表][Distinct]By[條件][排序],如: findMemberDistinctByNicknameLikeAndCreatorOrderByIdDesc
```
### 命名規則 - 動作
* 基本查詢
`find` 、 `read` 、 `get` 、 `query` 、 `search`
* 其他查詢
`exists` 是否存在(回傳型態boolean)、 `count` 數量(回傳型態int)、 `stream` (回傳型態Stream)
* 刪除
`delete` 、 `remove`
### 命名規則 - 條件
[欄位][運算子][連接字]...
* 欄位
資料表的欄位名稱,必須使用大駝峰式命名
* 運算子、連接字
| keyword | Repository | SQL | Sample |
|-|-|-|-|
|`And`|And|WHERE name = ?1 AND country =?2|findByNameAndCountry|
|`Or`|Or|WHERE name = ?1 OR country =?2|findByNameOrCountry|
|`=` 、 `is`|Equals、Is(預設,可略)|where id= ?|findById、findByIdEquals|
|`!=` 、 `is not`| isNotNull、NotNull | where name is not null | findByNameNotNull|
|`is null`|IsNull|where name is null|findByNameIsNull|
|`True`|True|where show = true|findByShowTrue|
|`False`|False|where show = false|findByShowFalse|
|`>`|GreaterThan、After|where age > ?、where date > ?|findByAgeGreaterThan、findByDateAfter|
|`>=`|GreaterThanEqual、GreaterThanEquals|where id > = ?|findByIdGreaterThanEquals|
|`<`|LessThan、Before|WHERE age < ?、where date < ?|findByAgeLessThan、findByDateBefore|
|`<=`|LessThanEqual、LessThanEquals|where id <= ?|findByIdLessThanEquals|
|`between` 、 `not between`|Between、NotBetween|WHERE age <= ?1 AND age >=?2、where age between ? and ?|findByAgeBetween|
|`in`|In|where id in (?)|`findByIdIn(Collection<?> c)`|
|`not in`|NotIn|where name <> ?|findByNameNot|
|`like ?`|Like|WHERE name LIKE ?1|findByNameLike|
|`like ?%`|StartingWith|where name like '?%'|findByNameStartingWith|
|`like %?`|EndingWith|where name like '%?'|findByNameEndingWith|
|`like %?%`|Containing|where name like '%?%'|findByNameContaining|
|`not like`|notlike|where name not like ?|findByNameNotLike|
|`order by`|OrderBy|where id = ? order by Name desc|findByIdOrderByNameDesc|
| |top|top|findTop100|
| |IgnoreCase|where UPPER(name)=UPPER(?)|findByNameIgnoreCase|
參考資料: [Spring Boot學習筆記(三)Spring Data Jpa 快速上手(二) jpa命名規則 與 Jpql、Sql](https://www.twblogs.net/a/5ebbb3f686ec4d7c568bb167)
### 命名規則 - 排序
* 區分大小寫
* 固定字為 `OrderBy`
* 遞增--> `Asc`,遞減--> `Desc`
* 可有多個
### 特殊用法 [Pageable](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Pageable.html)、[Sort](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Sort.html)
以findByName方法為例
```=java
Page<User> findByName(String name, Pageable pageable);
List<User> findByName(String name, Sort sort);
```
```=java
public void findUser() {
int page = 0;
int size = 10;
Sort sort = new Sort(Direction.DESC, "age");
Pageable pageable = new PageRequest(page, size, sort);
Page<User> page = userDao.findAll(pageable);
Page<User> page = userDao.findByCountry(country, pageable);
}
```
## 自訂 JPQL
### 一般查詢 @Query
@Query(value = "xxxxx")
JPA在解析 Repository 中的方法時,會先檢查開發者是否有**自定義 Query** (即 `@Query`)。
若有,檢查 Query 字串是否符合規則;
若無,則檢查方法名稱是否符合規則。
:::warning
**注意 1** @Query 內的字串預設為JPQL,如要改用SQL,須加上 nativeQuery=true,如: @Query(value = "xxxxx", nativeQuery = true)。
**注意 2** 不同資料庫可能支援的語句不同,若有差異則需要指定格式。
:::
```=Java!
@Query(value="select * from user where name like %?1",
nativeQuery=true)
public List<User> findByName(String name);
```
### 新增/刪除/修改 @Modifying
**查詢以外必須加上@Modifying**
```Java!
@Modifying
@Query(value = "update USER set USERNAME = :username where ID = :id",
nativeQuery = true)
public int updatePassById(String username, Integer id);
```
## 自訂實作類別
若 使用JPA自動實作的方法(命名過長或是語意無法表達)與 `@Query` 皆無法滿足需求,則需另外自訂介面供該介面繼承,
* OrderRepository.java
```=Java!
public interface OrderRepository extends JpaRepository<Order, Integer>, OrderOperation {
List<Order> findByMemberId(Integer memberId);
@Query(value = "select * "
+ "from ORDER o join ORDER_DETAIL od on o.ID = od.ORDER_ID "
+ "where o.STATUS = true",
nativeQuery = true)
List<Order> findByStatusTrue();
}
```
* OrderOperation.java
```=Java!
public interface OrderOperation {
List<Order> findByContainsBook();
}
```
* OrderOperationImpl.java
覆寫的方式可自行選擇使用 JPQL/HQL、Criteria API或Native SQL
```=Java!
public class OrderRepositoryImpl implements OrderOperation {
@PersistenceContext
private Session session;
@Override
public List<Order> getOrder() {
String sql = new StringBuilder()
.append("select o.*, od.* ")
.append("from ORDER o join ORDER_DETAIL od on o.ID = od.ORDER_ID ")
.append("join BOOK b on od.PRODUCT_ID = b.PRODUCT_ID ")
.toString();
return session.createNativeQuery(sql, Order.class).getResultList();
}
}
```
:::danger
**注意 1** 必須與實作的介面名稱相同,否則將拋出找不到該Repository的錯誤
**注意 2** SQLQuery已遭棄用
:::
```=Java!
public class OrderRepositoryImpl implements OrderOperation {
@PersistenceContext
EntityManager entityManager
@Override
public List<Order> getOrder() {
Query q = entityManager.createNativeQuery("select * from message");
//將取出的資料轉為 Map,SQLQuery 雖還可使用,但已遭棄用,尚在尋找替代方案
q.unwrap(org.hibernate.SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
List<Map<String, Object>> list = q.getResultList();
}
}
```
* OrderServiceImpl.java
```=Java!
@Service
public class OrderServiceImpl implements OrderService {
// 注入時,一樣使用自定義的Repository型態
@Autowired
private OrderRepository repository;
// 略..
@Override
public List<Member> findAll() {
return repository.findAll();
}
}
```