JpaRepository và Derived Queries: Truy Vấn Không Cần Viết Code
Tìm hiểu cây phân cấp JpaRepository, cơ chế phân tích tên method lúc startup thành JPQL và SQL, cùng giới hạn thực tế của derived queries.
TL;DR: JpaRepository kế thừa toàn diện từ CrudRepository và PagingAndSortingRepository. Spring Data JPA sử dụng lớp PartTreeJpaQuery để phân tích tên method lúc startup thành câu lệnh JPQL, sau đó Hibernate dịch sang SQL tương ứng với dialect. Bạn hoàn toàn không cần viết một dòng SQL nào. Khi số lượng điều kiện lọc vượt quá 4 hoặc cần tính toán hàm tổng hợp (aggregate), đó là lúc nên chuyển sang @Query.
Cây phân cấp JpaRepository gồm những tầng nào?
Khi một interface kế thừa JpaRepository<Task, Long>, nó thừa hưởng toàn bộ sức mạnh từ cây phả hệ Spring Data:
| Interface | Vai trò kiến trúc | Phương thức cung cấp |
|---|---|---|
Repository | Marker Interface | Giao diện rỗng giúp Spring Data quét và nhận diện component. |
CrudRepository | Thao tác CRUD cơ bản | save, findById, existsById, findAll, count, deleteById. |
PagingAndSortingRepository | Phân trang & sắp xếp | findAll(Sort), findAll(Pageable). |
JpaRepository | JPA mở rộng chuyên sâu | flush, saveAndFlush, deleteInBatch, getReferenceById. |
Trong 99% các tình huống thực tế, ta luôn khai báo kế thừa JpaRepository để sở hữu đầy đủ tiện ích mà không làm phát sinh thêm chi phí overhead.
Cơ chế sinh Proxy tự động lúc Startup
Spring Data không yêu cầu lập trình viên viết code triển khai (implementation class). Thay vào đó:
- Lúc khởi động ứng dụng,
RepositoryFactoryBeantạo ra một JDK Dynamic Proxy. - Proxy điều hướng các method CRUD mặc định tới
SimpleJpaRepository. - Đối với các method tùy biến, proxy kết nối tới cây cú pháp
PartTreeJpaQueryđã được biên dịch sẵn.
Cơ chế biến đổi tên phương thức thành câu lệnh SQL ra sao?
Hãy xem xét phương thức sau:
List<Task> findByStatusAndOwnerId(TaskStatus status, Long ownerId);
Spring Data không đối chiếu chuỗi string đơn thuần ở runtime mà thực hiện quy trình chuẩn hóa gồm 3 bước:

- Phân tích cú pháp (PartTree): Bóc tách
findBy(hành động SELECT),Status(thuộc tính 1),And(toán tử logic),OwnerId(thuộc tính 2). - Sinh mã JPQL (QueryUtils): Dựng câu truy vấn JPQL trên Entity:
SELECT t FROM Task t WHERE t.status = :status AND t.ownerId = :ownerId. - Hibernate biên dịch sang SQL: Dựa vào
Dialectcơ sở dữ liệu và quy tắc đặt tên (SpringPhysicalNamingStrategy) để sinh SQL thuần:SELECT ... FROM tasks WHERE status = ? AND owner_id = ?.

Cú pháp ngữ pháp (Method Grammar) chuẩn
[verb][Distinct]?[Top N]?By[Property][Keyword][And/Or][Property][Keyword]...[OrderBy...]
Một số tiền tố và từ khóa thông dụng:
- Hành động:
find...By,count...By(trả vềlong),exists...By(trả vềboolean),delete...By. - So sánh:
Equals,Not,Like,Containing(tương đươngLIKE %?%),StartingWith,Between,In,IsNull. - Giới hạn & Sắp xếp:
Top3By...,OrderByCreatedAtDesc.
public interface TaskRepository extends JpaRepository<Task, Long> {
Optional<Task> findByTitle(String title);
List<Task> findByStatusIn(Collection<TaskStatus> statuses);
List<Task> findByTitleContainingIgnoreCase(String keyword);
List<Task> findTop5ByProjectIdOrderByCreatedAtDesc(Long projectId);
long countByStatus(TaskStatus status);
boolean existsByTitleAndProjectId(String title, Long projectId);
}
Khi nào Derived Query đủ dùng và khi nào nên chuyển sang @Query?
| Mức độ phức tạp | Số lượng điều kiện | Giải pháp khuyến nghị |
|---|---|---|
| Đơn giản | 1 – 3 điều kiện | Derived Query: Cú pháp tự nhiên, ngắn gọn, dễ đọc. |
| Trung bình | 4 – 5 điều kiện | Cân nhắc: Tên method bắt đầu dài dòng và khó bao quát. |
| Phức tạp | Trên 5 điều kiện, JOIN phức tạp | @Query: Viết truy vấn JPQL tường minh, dễ format. |
| Hàm tổng hợp | SUM, AVG, MAX, MIN | Bắt buộc dùng @Query: Derived query không hỗ trợ tính toán tổng hợp. |
// KHONG NEN: Ten method qua dai gay roi mat
List<Task> findByStatusAndProjectIdAndAssigneeIsNotNullAndDueDateBeforeAndPriorityGreaterThan(
TaskStatus s, Long pId, Instant d, int minP);
// NEN: Chuyen sang @Query JPQL ro rang
@Query("""
SELECT t FROM Task t
WHERE t.status = :status
AND t.projectId = :projectId
AND t.assignee IS NOT NULL
AND t.dueDate < :deadline
AND t.priority >= :minPriority
""")
List<Task> findUrgentTasks(
@Param("status") TaskStatus status,
@Param("projectId") Long projectId,
@Param("deadline") Instant deadline,
@Param("minPriority") int minPriority);
Những cạm bẫy phổ biến cần tránh là gì?
- Lỗi sai tên thuộc tính Entity: Khai báo
findByUserIdtrong khi Entity chỉ có thuộc tínhownerId. Ứng dụng sẽ ném lỗiPropertyReferenceExceptionvà dừng ngay khi khởi động. - Nguy cơ Out-Of-Memory khi dùng
findAll(): Gọirepo.findAll()trên bảng có hàng triệu dòng dữ liệu sẽ tải toàn bộ vào bộ nhớ Heap. Luôn luôn truyền tham sốPageableđể phân trang. - Dùng
Optionalcho truy vấn có thể trả về nhiều kết quả: Nếu điều kiện truy vấn khớp từ 2 dòng trở lên, Spring Data sẽ ném lỗiIncorrectResultSizeDataAccessExceptiontại runtime. Chỉ dùngOptionalkhi trường tìm kiếm có ràng buộcUNIQUE.
Tự kiểm tra kiến thức
- Q1Tại sao Spring Data JPA lại phân tích tên phương thức (Method Name Parsing) ngay tại thời điểm Startup thay vì Runtime?
- Q2Derived Query trong Spring Data JPA KHÔNG hỗ trợ thao tác nào sau đây?
- Q3Điều gì sẽ xảy ra nếu khai báo phương thức 'Optional<Task> findByStatus(TaskStatus status)' khi trong database có 5 bản ghi cùng status?
- Q4Để giới hạn chỉ lấy 5 bản ghi mới nhất theo ngày tạo trong Derived Query, cú pháp nào sau đây là chính xác?
- Q5Để tìm kiếm chuỗi ký tự theo dạng 'LIKE %keyword%' không phân biệt chữ hoa chữ thường, ta sử dụng từ khóa nào?
Bài tiếp theo: @Query, Modifying & Projection: Truy Vấn Tùy Biến Nâng Cao
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Repository & Queries — tổng quan
Bài tiếp@Query, @Modifying và Projection: Tùy Biến Truy Vấn Dữ Liệu
Lộ trình Junior Java Backend Developer
Chặng 16/21 · Spring Data JPA — bài 9/20 của chặng
Thảo luận & góp ý
Hỏi đáp về bài này