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

EntityManager và JPQL: Tầng Thực Thi Lõi Trong JPA

Khám phá cơ chế EntityManager, First-Level Cache, Dirty Checking, vòng đời Entity và cơ chế Spring Data JPA tự động sinh proxy thực thi JPQL.

TL;DR: EntityManager là API trung tâm của JPA với bốn method cốt lõi persist/find/merge/remove. Phía sau là persistence context (first-level cache + dirty tracking) hoạt động theo transaction. JPQL query trên entity class thay vì database table. Spring Data JPA loại bỏ boilerplate bằng cách sinh proxy implementation tại runtime từ method name. Cấu hình ddl-auto: validate là bắt buộc cho môi trường production để bảo vệ schema.

EntityManager quản lý vòng đời Entity như thế nào?

EntityManager là giao diện cốt lõi của JPA dùng để quản lý vòng đời entity, đóng vai trò như cầu nối giữa Java object model và cơ sở dữ liệu. Mỗi EntityManager instance gắn liền với một persistence context riêng biệt.

Bốn method thao tác entity nền tảng:

Java
@PersistenceContext
private EntityManager em;

// 1. persist: Chuyen Transient -> Managed, luu vao persistence context
Project project = new Project("Mobile App", ProjectStatus.PLANNING);
em.persist(project);

// 2. find: SELECT theo khoa chinh, dua vao Managed state
Project loaded = em.find(Project.class, 42L);

// 3. merge: Sao chep trang thai tu Detached sang Managed
loaded.setStatus(ProjectStatus.ACTIVE);
Project managed = em.merge(loaded);

// 4. remove: Danh dau Managed entity de DELETE luc commit
em.remove(managed);

Persistence Context và First-Level Cache

Persistence context đóng vai trò là first-level cache (L1 cache) trong phạm vi của một transaction:

  • Identity Guarantee: Trong cùng một transaction, mọi lời gọi truy vấn cùng một ID luôn trả về cùng một object instance trong bộ nhớ heap (p1 == p2 trả về true).
  • Dirty Checking: Khi tải entity, Hibernate lưu lại một bản chụp snapshot. Khi transaction commit, Hibernate so sánh trạng thái hiện tại với snapshot ban đầu và tự động sinh câu lệnh SQL UPDATE.
Java
@Transactional
public void updateProjectName(Long id, String newName) {
    Project p = repo.findById(id).orElseThrow();
    p.setName(newName); // Thay doi field tren Managed entity
    // Ket thuc method -> Transaction commit -> Hibernate dirty check -> Phat SQL UPDATE tu dong
}

Bốn trạng thái trong vòng đời Entity

Trạng tháiĐặc điểm kỹ thuật
TransientĐối tượng Java thuần vừa khởi tạo bằng new, chưa có định danh ID và chưa được EntityManager quản lý.
ManagedNằm trong persistence context, có ID database, được theo dõi bởi cơ chế dirty checking.
DetachedTừng được quản lý nhưng transaction đã kết thúc hoặc bị tách qua detach(), thay đổi không tự sync.
RemovedĐã được đánh dấu xóa qua remove(), câu lệnh SQL DELETE sẽ kích hoạt khi flush.
Entity bị thay đổi ở trạng thái Detached

Khi một method Service không có @Transactional, transaction của findById sẽ đóng ngay lập tức. Đối tượng trả về rơi vào trạng thái Detached, khiến các setter tiếp theo hoàn toàn không được đồng bộ xuống database.

Khi nào Hibernate thực sự phát lệnh SQL Flush?

Mọi thao tác thay đổi dữ liệu đều được gom lại (deferred write) và chỉ đồng bộ xuống database tại 3 thời điểm:

  1. Ngay trước khi transaction commit.
  2. Trước khi thực thi một truy vấn JPQL/SQL liên quan (chế độ FlushMode.AUTO mặc định) để tránh đọc thiếu dữ liệu vừa ghi.
  3. Khi lập trình viên gọi em.flush() thủ công.

Trục thời gian một transaction: new Project rồi em.persist rồi setter đều chưa sinh câu SQL nào, thay đổi mới nằm trong persistence context; câu JPQL kế tiếp buộc Hibernate flush trước để không đọc thiếu dòng vừa persist nên INSERT phát ở đó, và tx commit là lần flush cuối

Tại sao JPQL lại truy vấn trên Entity thay vì Table?

JPQL (Jakarta Persistence Query Language) là ngôn ngữ truy vấn hướng đối tượng của JPA. Cú pháp tương đồng SQL nhưng thao tác trực tiếp trên Java entity class và property:

Java
List<Project> result = em.createQuery(
    "SELECT p FROM Project p WHERE p.status = :status AND p.createdAt > :since",
    Project.class
).setParameter("status", ProjectStatus.ACTIVE)
 .setParameter("since", cutoff)
 .getResultList();

Hibernate phân tích JPQL và biên dịch thành SQL tương ứng với database dialect:

SQL
-- SQL thuc te duoc Hibernate sinh ra (PostgreSQL Dialect)
SELECT p.id, p.name, p.status, p.created_at
FROM projects p
WHERE p.status = 'ACTIVE' AND p.created_at > ?

Ưu điểm vượt trội của JPQL so với Raw SQL

  • Tính khả chuyển (Database Portability): JPQL không phụ thuộc cú pháp database riêng biệt. Chuyển đổi giữa PostgreSQL, MySQL hay Oracle chỉ cần thay đổi dialect trong cấu hình.
  • Điều hướng quan hệ tự nhiên: Hỗ trợ duyệt quan hệ đối tượng liền mạch như SELECT p FROM Project p WHERE p.customer.region = :region, Hibernate tự động sinh câu lệnh JOIN tương ứng.
  • Phát hiện lỗi sớm: Khi tên property trong JPQL bị sai, Hibernate hoặc Spring Data sẽ báo lỗi ngay khi khởi động ứng dụng thay vì chờ đến runtime.

Spring Data JPA sinh mã Repository như thế nào?

Spring Data JPA loại bỏ hoàn toàn mã lặp boilerplate của EntityManager. Bạn chỉ cần khai báo interface:

Java
public interface ProjectRepository extends JpaRepository<Project, Long> {
    List<Project> findByStatus(ProjectStatus status);
    Optional<Project> findByNameIgnoreCase(String name);
}

Từ interface ProjectRepository, lúc startup RepositoryFactoryBean sinh một JDK dynamic proxy đăng ký như Spring bean; bên trong proxy có hai đường, method built-in đi xuống SimpleJpaRepository còn method derived được PartTreeJpaQuery đọc tên rồi dựng JPQL template cache lại, cả hai cùng đổ vào EntityManager

Cơ chế Dynamic Proxy và Method Parsing lúc Startup

Tại thời điểm ứng dụng khởi động:

  1. JpaRepositoriesAutoConfiguration quét và tìm kiếm tất cả các interface kế thừa JpaRepository.
  2. RepositoryFactoryBean tạo một JDK Dynamic Proxy triển khai interface tương ứng.
  3. Proxy điều hướng method theo hai cơ chế:
    • Method có sẵn (save, findById, delete): Chuyển tiếp tới lớp triển khai mặc định SimpleJpaRepository.
    • Derived Query Methods: Lớp PartTreeJpaQuery phân tích tên method theo quy tắc ngữ pháp lúc startup, tạo sẵn JPQL template và lưu cache.
  4. Proxy được đăng ký vào Spring ApplicationContext dưới dạng một Spring Bean hoàn chỉnh.

Quy tắc phân tích tên phương thức (Method Name Grammar)

Ngữ pháp phân tích có cấu trúc tổng quát: [find|count|exists]By[Property][Keyword][And|Or][Property].

Tên phương thứcJPQL tương ứng
findByStatus(Status s)WHERE p.status = ?1
findByStatusIn(List<Status> s)WHERE p.status IN ?1
findByNameContainingIgnoreCase(String n)WHERE LOWER(p.name) LIKE LOWER(?)
findByCreatedAtBetween(Instant a, Instant b)WHERE p.createdAt BETWEEN ?1 AND ?2
existsByName(String n)SELECT CASE WHEN COUNT(p) > 0 THEN TRUE ELSE FALSE END WHERE p.name = ?1

Nếu viết sai tên thuộc tính (ví dụ findByStatuz), Spring Data sẽ ném ngay ngoại lệ PropertyReferenceException tại thời điểm startup, áp dụng triệt để nguyên lý Fail-Fast.

Làm thế nào để quản lý Schema database an toàn với ddl-auto?

Thuộc tính spring.jpa.hibernate.ddl-auto điều khiển hành vi tương tác của Hibernate đối với cấu trúc schema:

Giá trịHành viMôi trường áp dụng
noneKhông can thiệp, không kiểm tra schema.Database do team DBA hoặc migration tool quản trị.
validateĐối chiếu entity với schema hiện tại, ném lỗi nếu sai lệch.Production (bắt buộc)
updateTự động thêm cột hoặc bảng mới, không bao giờ xóa.Chỉ dùng cho Dev local.
create-dropTạo mới toàn bộ khi khởi động và drop sạch khi shutdown.Môi trường Unit / Integration Test.

Cạm bẫy khi dùng ddl-auto=update trên Production

  1. Xung đột đa tiến trình (Multi-pod Race Condition): Khi triển khai nhiều pod cùng khởi động, các pod đồng thời chạy ALTER TABLE gây ra lỗi khóa deadlock hoặc fail khởi động.
  2. Không có khả năng Rollback: Hibernate chỉ thêm cột, không xóa hay đổi tên cột cũ. Cột cũ bị bỏ hoang gây sai lệch schema.
  3. Mất kiểm soát lịch sử thay đổi: Không có file migration lưu trữ trong Git, không thể audit hoặc đồng bộ môi trường.
YAML
# application-prod.yml
spring:
  jpa:
    hibernate:
      ddl-auto: validate # Chi validate tinh khop, khong can thiep schema

Những cạm bẫy phổ biến cần tránh là gì?

  1. Thay đổi Entity ngoài Transaction: Gọi setter khi phương thức không có @Transactional. Entity đã ở trạng thái Detached nên Dirty Checking không kích hoạt, thay đổi bị mất âm thầm.
  2. Tự viết em.createQuery khi không cần thiết: Viết thủ công các câu truy vấn đơn giản thay vì tận dụng derived query method hoặc @Query của Spring Data JPA.
  3. Dùng ddl-auto: update trên môi trường Staging: Khiến môi trường staging khác biệt so với Production, che giấu các lỗi migration script tiềm ẩn.

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

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Lời gọi em.persist(project) có thực hiện câu lệnh SQL INSERT ngay lập tức không?
  2. Q2
    Đặc tính Identity Guarantee của First-Level Cache trong JPA mang lại điều gì?
  3. Q3
    Điểm khác biệt cốt lõi nhất giữa truy vấn JPQL và truy vấn SQL thuần là gì?
  4. Q4
    Điều gì xảy ra khi khai báo một method repository sai tên thuộc tính như findByStatuz lúc khởi động Spring Boot?
  5. Q5
    Tại sao cấu hình spring.jpa.hibernate.ddl-auto=update lại bị nghiêm cấm trên môi trường Production?

Bài tiếp theo: @Entity & @Id strategies: Thiết kế Khóa Chính

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 3/20 của chặng

Ôn phỏng vấn

Bài này trả lời được các câu phỏng vấn sau — tự trả lời thử trước khi mở đáp án.

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

@Entity và @Id: POJO Contract và Chiến Lược Khóa Chính