# [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(); } } ```