Module 04 — Spring Data JPA: tổng quan
Học gì, vì sao học, học xong làm được gì. JPA spec, Hibernate ORM, Spring Data Repository abstraction, N+1 problem, transaction propagation, Flyway migration, pagination — nền tảng persistence production-grade.
TL;DR: Module 04 bóc tầng Persistence Layer của Spring Boot — từ JPA spec định nghĩa chuẩn ORM, Hibernate implementation sinh SQL và quản lý persistence context, đến Spring Data JPA tự sinh repository implementation từ interface không cần viết một dòng SQL boilerplate. Module cover entity mapping annotation-driven, N+1 problem (bug hiệu năng nguy hiểm nhất JPA), transaction propagation và self-invocation trap, Flyway schema migration versioned cho production, và pagination chiến lược Page vs Slice vs keyset cursor. Capstone TaskFlow v2 migrate in-memory HashMap sang PostgreSQL persistence thực sự.
Vì sao module này tồn tại
Thứ Tư tuần trước, team nhận alert từ monitoring: "API /api/projects response time tăng từ 50ms lên 2100ms sau khi thêm feature hiển thị số task của mỗi project."
Bạn mở Datadog. Thấy pattern rõ: mỗi request /api/projects bắn 101 SQL query thay vì 1. Log:
SELECT * FROM projects LIMIT 20 -- 1 query
SELECT * FROM tasks WHERE project_id = 1 -- N=100 queries
SELECT * FROM tasks WHERE project_id = 2
...
SELECT * FROM tasks WHERE project_id = 100
Root cause: code Java như sau:
List<Project> projects = projectRepo.findAll();
return projects.stream()
.map(p -> new ProjectDto(p.getId(), p.getName(), p.getTasks().size()))
.toList();
p.getTasks() trigger lazy load — Hibernate bắn 1 SQL cho mỗi project. 100 projects = 100 extra queries. Đây là N+1 problem — bug hiệu năng phổ biến nhất JPA, ẩn trong code trông hoàn toàn bình thường.
Incident thứ 2: team deploy tính năng đổi tên column project_name thành name. Dev dùng ddl-auto=update. Kết quả: Hibernate tạo column name mới, giữ nguyên project_name cũ, copy data không chạy — dữ liệu cũ mất visibility, 2 giờ rollback khẩn cấp.
Incident thứ 3: @Transactional service method gọi private method khác cùng class — private method throw IOException (checked). Transaction không rollback. Corrupted state.
Module 04 xây đúng những thứ đó: persistence layer đúng cách từ ngày đầu — không phải retrofit sau khi có bug production.
Sau module này bạn sẽ
- Explain 3 layer abstraction JPA spec / Hibernate / Spring Data JPA và ORM impedance mismatch — biết chọn đúng layer để debug khi query sai
- Implement entity mapping đúng chuẩn:
@Entitylifecycle,@Idstrategy, naming convention,@Embeddable, lifecycle callback, equals/hashCode pattern - Design repository query ở đúng tier: built-in method, derived query,
@QueryJPQL/native, Specification — và expose kết quả qua projection DTO - Diagnose N+1 problem bằng Hibernate statistics, chọn đúng fix trong 4 giải pháp: JOIN FETCH,
@EntityGraph, batch size, projection DTO - Implement
@Transactionalđúng propagation và rollback rule — tránh self-invocation bypass và checked-exception silent commit - Design schema migration workflow với Flyway: naming convention, baseline existing DB, rollback strategy forward-only, multi-environment pattern
- Choose đúng pagination strategy cho từng use case:
PagevsSlicevs keyset cursor, validate và whitelist sort column để tránh DoS
Lộ trình module
Bài 01 đặt nền tảng khái niệm: 3 layer abstraction — JPA spec (jakarta.persistence), Hibernate implementation, Spring Data JPA abstraction. JPA là chuẩn Java định nghĩa @Entity, EntityManager, JPQL — không có code chạy, chỉ là interface. Hibernate là implementation thực tế chiếm hơn 90% thị phần — sinh SQL, quản lý lazy loading proxy, duy trì persistence context. Spring Data JPA nằm trên Hibernate, tự động sinh repository implementation từ interface lúc runtime. Sau bài này, "tại sao findByStatus hoạt động mà không cần viết SQL" không còn là magic.
Bài 02 bóc entity mapping chi tiết: @Entity yêu cầu tối thiểu (no-arg constructor, không final, @Id), vì sao Java record không dùng làm entity được, chiến lược @GeneratedValue — IDENTITY cho đơn giản, SEQUENCE cho batch high-throughput, UUID v7 cho distributed system. @Column customize, naming strategy SnakeCaseStrategy tự động chuyển camelCase sang snake_case, @Enumerated(EnumType.STRING) bắt buộc, @Embeddable cho value object, lifecycle callback @PrePersist/@PostLoad, và equals/hashCode pattern đúng cho entity (Lombok @Data không dùng được).
Bài 03 bóc repository abstraction: 3 tier method — built-in (JpaRepository kế thừa), derived query từ tên method (findByStatusAndCreatedAtAfter → JPQL sinh tự động), @Query cho JPQL hoặc native SQL phức tạp. Projection DTO để chỉ select field cần thay vì load full entity. @Modifying + @Transactional cho UPDATE/DELETE. Specification cho dynamic query runtime.
Bài 04 là bài quan trọng nhất module cho production performance: relationships @OneToMany/@ManyToOne/@ManyToMany, fetch type LAZY vs EAGER, và N+1 problem — nguyên nhân, detection, và 4 cách fix (JOIN FETCH, @EntityGraph, batch size, projection). Sai 1 chỗ N+1 → app chậm 100x ở scale. Bài này phải đọc kỹ nhất.
Bài 05 bóc @Transactional cơ chế AOP proxy, 7 propagation (REQUIRED mặc định, REQUIRES_NEW cho independent tx, NESTED cho savepoint), 5 isolation level, rollback rule (RuntimeException rollback, checked exception không), readOnly = true optimization, và self-invocation bypass — pitfall cổ điển nhất Spring, phát hiện muộn thường gây data corruption production.
Bài 06 bóc Flyway schema migration: vì sao ddl-auto=update không an toàn production, naming convention V<version>__<desc>.sql, flyway_schema_history table audit, rollback strategy forward-only (bug = new forward migration), baseline existing DB, multi-environment pattern với subfolder per env.
Bài 07 bóc pagination và sorting: Pageable auto-bind từ query string, SQL OFFSET/LIMIT generated, performance vấn đề deep page (page 1000+ chậm tuyến tính), Slice bỏ COUNT cho infinite scroll, keyset pagination (Spring Data 3.1+) O(log N) constant, whitelist sort column để tránh DoS query injection.
Bài 08 mini-challenge capstone TaskFlow v2: migrate TaskFlow v1 từ in-memory ConcurrentHashMap sang PostgreSQL persistence — thêm JPA entity, Flyway migration, JpaRepository, fix N+1, @Transactional đúng chỗ, pagination endpoint.
Bài 09 tổng kết: cheat sheet, glossary, pitfall tổng hợp, self-assessment 7 outcomes.
Yêu cầu trước khi bắt đầu
- Spring Core (Module 01): AOP proxy mechanism — bài 05 transactions dựa hoàn toàn vào hiểu biết AOP proxy. Module 01 bài 04 là prerequisite cứng cho bài 05.
- Spring Boot Foundations (Module 02):
DataSourceAutoConfiguration, externalized config (spring.datasource.*),@ConditionalOnMissingBean. Boot tự setupEntityManagerFactory,TransactionManagerqua autoconfig. - REST API Spring MVC (Module 03):
@RestController,Pageableintegration với controller,@Validtrên request DTO — bài 07 assume bạn biết Spring Data Web integration với MVC. - SQL nền tảng: biết
SELECT,JOIN,WHERE,ORDER BY,LIMIT/OFFSET. Flyway migration viết SQL thuần. JPQL là query entity — cần đọc được SQL để debug query generated. - Maven + JDK 21 + Docker: PostgreSQL local qua
docker compose up -d postgres. Module này cần DB thật — không có in-memory H2.
Time budget
| Bài | Chủ đề | Phút |
|---|---|---|
| 00 | Tổng quan (đang đọc) | 10 |
| 01 | JPA & Hibernate — 3 layer abstraction, ORM mismatch, persistence context | 24 |
| 02 | Entity mapping — @Entity, @Id, naming strategy, lifecycle | 24 |
| 03 | Repository abstraction — JpaRepository, derived query, @Query, projection | 24 |
| 04 | Relationships — @OneToMany, fetch type, N+1 problem + 4 fix | 26 |
| 05 | Transactions — @Transactional, propagation, self-call bypass, rollback | 26 |
| 06 | Flyway migration — schema versioning, naming, baseline, CI/CD | 22 |
| 07 | Pagination & sorting — Pageable, Page/Slice/keyset, DTO projection | 22 |
| 08 | Mini-challenge: TaskFlow v2 — migrate sang PostgreSQL (capstone) | 35 |
| 09 | Tổng kết & cheat sheet | 15 |
| Tổng | ~3.8h đọc + 0.5h lab + buffer |
Khuyến nghị: chia làm 3 ngồi:
- Ngồi 1 (~1.5h): bài 00 → 02 (orient + 3 layer abstraction + entity mapping — nền tảng khái niệm).
- Ngồi 2 (~1.5h): bài 03 → 05 (repository + relationships + transactions — 3 bài cốt lõi production, N+1 và self-call trap nằm ở đây).
- Ngồi 3 (~1.5h): bài 06 (Flyway) + bài 07 (pagination) + bài 08 (lab). Lab cần ngồi liên tục — bật
spring.jpa.show-sql=truevà quan sát SQL thật trong từng bước.
Cách học module này hiệu quả
-
Bật
spring.jpa.show-sql=truevàformat_sql=truengay từ bài 01. Quan sát SQL thật Hibernate sinh ra cho mỗi method call — đây là cách duy nhất để biết mình có N+1 không, query có đúng không. Đừng assume Hibernate sinh SQL đúng. -
Bài 04 (Relationships + N+1) — đọc 2 lần. Đây là bài quan trọng nhất module. N+1 là bug phổ biến nhất JPA và thường không bị phát hiện đến khi có load. Sau bài đầu, đọc lại section "N+1 problem" và implement fix trong code thật.
-
Bài 05 (Transactions) — luôn trace AOP proxy trong đầu. Mỗi khi thấy
@Transactional, hỏi: "method này được gọi từ đâu? Qua proxy hay self-call?" Nếu self-call → transaction không có effect. 80% bug transaction production là self-invocation. -
Bài 06 (Flyway) — không dùng
ddl-auto=updatengay cả trong dev. Thay vào đó dùngddl-auto=validate+ Flyway từ ngày đầu. Tập quen workflow: thêm entity field → viết migration script → chạyflyway migrate. Thói quen này tránh "quên viết migration khi deploy production". -
Bài 08 lab — bật Hibernate statistics để đo N+1. Add
spring.jpa.properties.hibernate.generate_statistics=truevà quan sátQueryStatistics. Số query per request phải là 1-3, không phải 101. Nếu số cao — trace lại relationship, thêm JOIN FETCH.
Module 04 là module kỹ thuật nhất trong chuỗi Spring. Kiên nhẫn với bài 04 và 05 — đầu tư thời gian ở đây trả lại 10x khi debug production.
Bài này có giúp bạn hiểu bản chất không?