Backend

99 Ngày Spring — Ngày 21: JPA & Hibernate tổng quan

SSite Admin
17 tháng 08, 2026 7 phút đọc 4 lượt xem
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 Javabả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 contextdirty checking.

Sketchnote Ngày 21: JPA & Hibernate tổng quan — impedance mismatch, ba tầng JPA/Hibernate/Spring Data, EntityManager và ddl-auto

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òn JpaRepository đế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 UPDATE lú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.

Cây cầu lớn vắt qua eo biển — ORM là cây cầu nối thế giới đối tượng Java với thế giới bảng quan hệ

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 DataSourceEntityManagerFactory từ đúng các property trên.

  • ddl-auto là lựa chọn vận hành quan trọng nhất của trang config: update tiệ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: validate khi 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!

S

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.

Bài viết liên quan

99 Ngày Java — Ngày 21: StringBuilder & xử lý chuỗi nâng cao

Ngày 21 của 99 Ngày Java: vì sao nối chuỗi bằng + trong vòng lặp có chi phí bình phương, StringBuilder với capacity và bộ API thường dùng, String pool — lý do == đôi khi tình cờ đúng nhưng equals mới là quy tắc, cùng String.format và text block cho chuỗi nhiều dòng dễ đọc.

17 thg 8, 20266 phút2
99 Ngày Java — Ngày 20: Record & lớp bất biến

Ngày 20 của 99 Ngày Java: record — lớp dữ liệu trong một dòng khai báo với equals/hashCode/toString tự sinh, compact constructor cho validation, so sánh với Lombok và JavaBean, value semantics cùng record pattern của Java 21 — và ranh giới sử dụng: DTO dùng record, entity dùng class.

16 thg 8, 20266 phút12
99 Ngày Spring — Ngày 20: CORS

Ngày 20 của 99 Ngày Spring: same-origin policy — luật của trình duyệt, preflight OPTIONS và vì sao một lần gọi sinh hai request, cấu hình @CrossOrigin và global theo profile, cùng các quy tắc an toàn: không mở * ở production, đọc lỗi CORS ở DevTools, và cái bẫy khi thêm Spring Security.

16 thg 8, 20266 phút16