Spring REST API & Data JPA
30/46
Bài 30 / 46~12 phútRepository & QueriesLộ trình · chặng 16/21Miễn phí… lượt xem

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:

InterfaceVai trò kiến trúcPhương thức cung cấp
RepositoryMarker InterfaceGiao diện rỗng giúp Spring Data quét và nhận diện component.
CrudRepositoryThao tác CRUD cơ bảnsave, findById, existsById, findAll, count, deleteById.
PagingAndSortingRepositoryPhân trang & sắp xếpfindAll(Sort), findAll(Pageable).
JpaRepositoryJPA mở rộng chuyên sâuflush, 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 đó:

  1. Lúc khởi động ứng dụng, RepositoryFactoryBean tạo ra một JDK Dynamic Proxy.
  2. Proxy điều hướng các method CRUD mặc định tới SimpleJpaRepository.
  3. Đố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:

Java
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:

Tên method findByStatusAndOwnerId đi xuống ba tầng: PartTree tách token, JPQL viết bằng tên entity và tên field Java, cuối cùng SQL Postgres viết bằng tên bảng tasks và tên cột owner_id — chỉ tầng cuối biết tới tên cột

  1. 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).
  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.
  3. Hibernate biên dịch sang SQL: Dựa vào Dialect cơ sở dữ liệu và quy tắc đặt tên (SpringPhysicalNamingStrategy) để sinh SQL thuần: SELECT ... FROM tasks WHERE status = ? AND owner_id = ?.

Hai cột đối chiếu hai pha thời gian: cột khởi động quét interface, sinh proxy động, parse tên method thành JPQL rồi compile ra TypedQuery nằm sẵn trong proxy; cột mỗi lời gọi thì proxy uỷ quyền xuống SimpleJpaRepository, bind tham số vào TypedQuery có sẵn và chạy SQL mà không đọc lại tên method

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 đương LIKE %?%), StartingWith, Between, In, IsNull.
  • Giới hạn & Sắp xếp: Top3By..., OrderByCreatedAtDesc.
Java
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ạpSố lượng điều kiệnGiải pháp khuyến nghị
Đơn giản1 – 3 điều kiệnDerived Query: Cú pháp tự nhiên, ngắn gọn, dễ đọc.
Trung bình4 – 5 điều kiệnCân nhắc: Tên method bắt đầu dài dòng và khó bao quát.
Phức tạpTrê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ợpSUM, AVG, MAX, MINBắt buộc dùng @Query: Derived query không hỗ trợ tính toán tổng hợp.
Java
// 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ì?

  1. Lỗi sai tên thuộc tính Entity: Khai báo findByUserId trong khi Entity chỉ có thuộc tính ownerId. Ứng dụng sẽ ném lỗi PropertyReferenceException và dừng ngay khi khởi động.
  2. Nguy cơ Out-Of-Memory khi dùng findAll(): Gọi repo.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.
  3. Dùng Optional cho 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ỗi IncorrectResultSizeDataAccessException tại runtime. Chỉ dùng Optional khi trường tìm kiếm có ràng buộc UNIQUE.

Tự kiểm tra kiến thức

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Tạ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?
  2. Q2
    Derived Query trong Spring Data JPA KHÔNG hỗ trợ thao tác nào sau đây?
  3. 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?
  4. 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?
  5. 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.

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

Bài tiếp theo

@Query, @Modifying và Projection: Tùy Biến Truy Vấn Dữ Liệu