99 Ngày Spring — Ngày 21: JPA & Hibernate tổng quan
Giai đoạn 3 bắt đầu: dữ liệu của API cần một nơi ở thật thay cho danh sách trong bộ nhớ. Nhưng giữa đối tượng Java và bảng quan hệ là một khoảng cách khái niệm — kế thừa, tham chiếu, collection không có bản dịch trực tiếp sang hàng và cột. ORM là cây cầu bắc qua khoảng cách đó, và hôm nay ta vẽ bản đồ tổng quan trước khi đi sâu: JPA là đặc tả, Hibernate là cài đặt, Spring Data JPA là tầng tiện ích — cùng hai khái niệm vận hành cốt lõi: persistence context và dirty checking.

Vấn đề: hai thế giới không khớp nhau
// Hai thế giới không khớp nhau — "impedance mismatch":
//
// Java (đối tượng) PostgreSQL (bảng quan hệ)
// ------------------ --------------------------
// Post có List<Tag> bảng nối post_tags (N-N)
// Post tham chiếu Author cột author_id (khóa ngoại)
// kế thừa, đa hình không có khái niệm tương đương
// đồ thị đối tượng các hàng và phép JOIN
// JDBC thủ công: tự viết SQL + tự chuyển đổi hai chiều:
try (PreparedStatement ps = conn.prepareStatement(
"SELECT id, title, slug FROM posts WHERE id = ?")) {
ps.setLong(1, id);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
Post post = new Post(); // lặp lại khối này
post.setId(rs.getLong("id")); // cho MỌI entity,
post.setTitle(rs.getString("title")); // MỌI truy vấn —
post.setSlug(rs.getString("slug")); // và im lặng khi lệch schema
}
}
// ORM (Object-Relational Mapping): khai báo ánh xạ MỘT lần,
// framework đảm nhận SQL + chuyển đổi hai chiều.Khoảng cách đối tượng – quan hệ có tên riêng: impedance mismatch — đồ thị đối tượng với tham chiếu và kế thừa phải ánh xạ sang thế giới phẳng của hàng, cột và khóa ngoại.
JDBC thủ công không sai — nó là nền móng mọi thứ bên trên — nhưng khối code chuyển đổi lặp lại cho từng entity, từng truy vấn, và trôi lệch schema chỉ lộ ra lúc runtime; JDBC vẫn có chỗ đứng riêng ở Ngày 35 (
JdbcClient).
JPA, Hibernate, Spring Data JPA — ba tầng, ba vai
// Ba tầng dễ nhầm — vai trò thật của từng cái tên:
//
// JPA (Jakarta Persistence API)
// └─ ĐẶC TẢ (specification): bộ interface + annotation chuẩn
// @Entity, @Id, EntityManager... — KHÔNG có code thực thi
//
// Hibernate
// └─ CÀI ĐẶT (implementation) phổ biến nhất của JPA:
// sinh SQL, quản lý session, caching, dirty checking...
//
// Spring Data JPA
// └─ TẦNG TIỆN ÍCH của Spring đứng TRÊN JPA:
// JpaRepository, derived query, phân trang (Ngày 18 đã gặp Pageable)
//
// Code của bạn ──> Spring Data JPA ──> JPA (đặc tả) ──> Hibernate ──> JDBC ──> PostgreSQL
// Lợi ích của kiến trúc đặc tả/cài đặt — đúng bài interface (Java Ngày 17):
// code nghiệp vụ phụ thuộc hợp đồng (JPA), không phụ thuộc cài đặt (Hibernate).Câu trả lời phỏng vấn kinh điển nằm gọn trong sơ đồ: JPA là đặc tả (interface + annotation, không có code thực thi), Hibernate là cài đặt được Spring Boot chọn mặc định, Spring Data JPA là tầng tiện ích sinh repository từ interface.
Kiến trúc này là bài interface (Java Ngày 17) ở quy mô hệ sinh thái: code của bạn viết theo hợp đồng
jakarta.persistence— về lý thuyết đổi được cài đặt (EclipseLink...) mà không sửa code nghiệp vụ.Ba cái tên xuất hiện ở ba chỗ khác nhau trong code: annotation
@Entityđến từ JPA, log SQL in ra từ Hibernate, cònJpaRepositoryđến từ Spring Data — nhận diện đúng giúp đọc tài liệu và stack trace nhanh hơn.
EntityManager, persistence context & dirty checking
// EntityManager — cửa ngõ của JPA, quản lý PERSISTENCE CONTEXT:
// "vùng làm việc" chứa các entity đang được theo dõi (managed).
Post post = entityManager.find(Post.class, 42L); // SELECT — entity thành MANAGED
post.setTitle("Tiêu đề mới"); // CHỈ gán giá trị trong Java...
// ...không cần gọi save/update nào cả:
// DIRTY CHECKING — khi transaction commit, JPA so sánh trạng thái
// entity managed với ảnh chụp ban đầu và TỰ SINH câu UPDATE.
// Persistence context đồng thời là FIRST-LEVEL CACHE:
Post p1 = entityManager.find(Post.class, 42L); // truy vấn DB
Post p2 = entityManager.find(Post.class, 42L); // KHÔNG truy vấn lại —
p1 == p2; // true: cùng một đối tượng
// Spring Data JPA che EntityManager sau JpaRepository (Ngày 23) —
// nhưng dirty checking và first-level cache vẫn vận hành bên dưới:
// hiểu chúng là chìa khóa giải thích N+1, lazy loading (Ngày 27–28).Persistence context là khái niệm trung tâm của cả giai đoạn: entity managed được theo dõi tự động — thay đổi trong Java trở thành
UPDATElúc commit mà không cần lời gọi save tường minh (dirty checking).First-level cache bảo đảm trong một persistence context, mỗi bản ghi chỉ có một đối tượng đại diện — nền tảng cho tính nhất quán, và là mảnh ghép để hiểu N+1 (Ngày 27) lẫn
@Transactional(Ngày 28).Spring Data JPA (từ Ngày 23) che
EntityManagerđi cho tiện — nhưng mọi hành vi "kỳ lạ" của JPA sau này đều giải thích được bằng hai khái niệm của mục này.
Trong Spring Boot: starter & ddl-auto
# 1. Dependency — một starter kéo đủ JPA + Hibernate + HikariCP:
# spring-boot-starter-data-jpa
# 2. application.yml — kết nối PostgreSQL:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/blogdb
username: dev
password: secret
jpa:
hibernate:
ddl-auto: validate # ← lựa chọn quan trọng nhất ở đây
open-in-view: false # tắt anti-pattern OSIV ngay từ đầu
# Các giá trị ddl-auto:
# none không đụng schema
# validate đối chiếu entity với schema — lệch là fail ngay khi khởi động
# update tự thêm cột/bảng còn thiếu — TIỆN Ở DEV, cấm ở production
# create xóa tạo lại schema mỗi lần chạy — MẤT DỮ LIỆU
#
# Nguyên tắc: dev có thể dùng update để đi nhanh;
# production LUÔN validate + migration có kiểm soát (Flyway — Ngày 34).Một starter duy nhất kéo đủ bộ ba JPA + Hibernate + HikariCP (connection pool) — auto-configuration (Ngày 10) dựng
DataSourcevàEntityManagerFactorytừ đúng các property trên.ddl-autolà lựa chọn vận hành quan trọng nhất của trang config:updatetiện ở dev nhưng cấm ở production — schema production chỉ thay đổi qua migration có kiểm soát (Flyway — Ngày 34); blog này cũng vận hành đúng nguyên tắc đó với Prisma.open-in-view: falseđáng đặt ngay từ đầu dự án: OSIV giữ kết nối DB suốt vòng đời request để "che" lỗi lazy loading — che triệu chứng thay vì chữa nguyên nhân (phân tích ở Ngày 27).
Bài tập nhỏ
Thêm
spring-boot-starter-data-jpa+ driver PostgreSQL vào project bài tập — cấu hình datasource và xác nhận ứng dụng khởi động, đọc các dòng log của HikariCP và Hibernate.Bật
spring.jpa.show-sql: true— chuẩn bị quan sát SQL Hibernate sinh ra khi có entity đầu tiên (Ngày 22).Đặt
ddl-auto: validatekhi DB đang trống — đọc lỗi khởi động khi có entity chưa có bảng, và giải thích vì sao fail-fast này là hành vi mong muốn.Vẽ lại sơ đồ 5 tầng từ code của bạn xuống PostgreSQL không nhìn bài — tự kiểm tra vai trò từng tầng.
Kết luận
Bản đồ đã trải ra trước khi đi vào chi tiết: ORM bắc cầu qua impedance mismatch, JPA là đặc tả — Hibernate là cài đặt — Spring Data JPA là tầng tiện ích, persistence context với dirty checking và first-level cache là cơ chế vận hành cốt lõi, còn ddl-auto phân định dev với production. Ngày 22 ta viết entity đầu tiên: @Entity, @Id và các chiến lược sinh khóa chính — nơi record của Java Ngày 20 chính thức không được dùng, và bạn đã biết vì sao. Hẹn gặp lại!
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


