Spring Data JPA 允許在 Repository 介面定義方法,框架自動解析方法名生成 JPQL/SQL。規則:find|read|get 開頭 + By + 屬性名 + 關鍵字組合。
| 關鍵字 | SQL 對應 | 範例 |
|---|---|---|
| And / Or | AND / OR | findByNameAndAge |
| Is / Equals | = | findByNameIs |
| Between | BETWEEN | findByAgeBetween |
| LessThan / LessThanEqual | < / <= | findByAgeLessThan |
| GreaterThan / GreaterThanEqual | > / >= | findByAgeGreaterThanEqual |
| After / Before | > / < (時間) | findByCreateTimeAfter |
| IsNull / IsNotNull | IS NULL / IS NOT NULL | findByEmailIsNull |
| Like / NotLike | LIKE / NOT LIKE | findByNameLike (需自帶 %) |
| StartingWith / EndingWith | LIKE 'xxx%' / LIKE '%xxx' | findByNameStartingWith |
| Containing | LIKE '%xxx%' | findByNameContaining |
| OrderBy...Asc/Desc | ORDER BY | findByAgeOrderByNameDesc |
| Not | <> | findByNameNot |
| In / NotIn | IN / NOT IN | findByIdIn |
| True / False | = true / = false | findByActiveTrue |
| IgnoreCase | LOWER() 比較 | findByNameIgnoreCase |
// 假設實體:AccountInfo { User user; Address address; }
// 方法:findByUserAddressZip()
// 解析步驟:
1. 去掉前綴 findBy → "UserAddressZip"
2. 判斷 userAddressZip 是否為 AccountInfo 直接屬性 → 否
3. 從右截取首個大寫開頭單詞 "Zip" → 剩餘 "UserAddress"
4. 判斷 userAddress 是否為屬性 → 否
5. 繼續截取 "Address" → 剩餘 "User"
6. 判斷 user 為屬性 ✓ → 類型 User
7. 在 User 中找 addressZip → 找到 ✓
8. 最終解析:AccountInfo.user.addressZip
⚠️ 歧義問題:若同時存在 `user` 和 `userAddress` 屬性,會產生歧義。強烈建議用底線分隔層級:`findByUser_Address_Zip()`
List<User> findByNameAndAge(String name, Integer age); // 呼叫:repo.findByNameAndAge(null, 18) // 生成 SQL:WHERE name IS NULL AND age = 18 // ⚠️ 不是忽略條件,而是精確匹配 NULL 值!
動態查詢請用 `@Query` 或 `Specification` / `QueryByExampleExecutor`。
// 靜態排序
List<User> findByAgeOrderByNameDesc(Integer age);
// 動態排序/分頁
Page<User> findByAge(Integer age, Pageable pageable);
List<User> findByAge(Integer age, Sort sort);
// 呼叫
repo.findByAge(18, PageRequest.of(0, 10, Sort.by("name").descending()));
// 實體關聯:Order { User user; }
// 方法:findByUserName(String name) → 查詢 user.name = ? 的訂單
// 更複雜關聯建議改用 @Query
public interface UserRepository extends JpaRepository<User, Long> {
// 基礎查詢
List<User> findByName(String name);
User findByEmail(String email);
// 組合條件
List<User> findByNameAndAgeGreaterThan(String name, int age);
List<User> findByNameOrEmail(String name, String email);
// 模糊/範圍
List<User> findByNameContaining(String keyword);
List<User> findByAgeBetween(int min, int max);
List<User> findByCreateTimeAfter(LocalDateTime time);
// 排序分頁
List<User> findByStatusOrderByCreateTimeDesc(Integer status);
Page<User> findByStatus(Integer status, Pageable pageable);
// 集合條件
List<User> findByIdIn(List<Long> ids);
// 忽略大小寫
User findByEmailIgnoreCase(String email);
}
當方法名過長、涉及複雜 JOIN、子查詢、原生 SQL 時,改用 `@Query`:
@Query("select u from User u where u.name like %:keyword% and u.age > :minAge")
List<User> search(@Param("keyword") String keyword, @Param("minAge") int minAge);
@Query(value = "select * from user where name = ?1", nativeQuery = true)
User findByNameNative(String name);
方法命名查詢適合單表、條件固定、語意清晰的簡單場景。複雜動態查詢請擁抱 `Specification`、`Querydsl` 或 `@Query`,別讓方法名變成「文檔」。