Module 04 — Tổng kết & cheat sheet
Recap, cheat sheet 1 trang, glossary JPA/Hibernate, pitfall tổng hợp, self-assessment outcomes. 1 trang để bookmark khi debug N+1, transaction không rollback, hay Flyway fail startup.
TL;DR: Module 04 đã xây đủ Persistence Layer production-grade: JPA spec định nghĩa chuẩn ORM, Hibernate implementation sinh SQL và quản lý persistence context, Spring Data JPA tự sinh repository từ interface. Entity mapping annotation-driven, N+1 problem với 4 cách fix, @Transactional propagation + self-invocation trap, Flyway schema versioning forward-only, và pagination chiến lược Page/Slice/keyset cursor. Capstone TaskFlow v2 migrate HashMap sang PostgreSQL persistence thực sự. Đây là trang bookmark — quay lại khi debug LazyInitializationException bất ngờ, transaction không rollback, hay Flyway checksum mismatch fail startup.
Đã đi qua những gì
Hành trình bắt đầu từ câu hỏi: vì sao không dùng JDBC thuần? Bài 01 trả lời bằng 40+ dòng boilerplate JDBC cho 2 method — và giới thiệu 3 layer abstraction giải quyết: JPA spec (jakarta.persistence) định nghĩa chuẩn interface — @Entity, EntityManager, JPQL; Hibernate implementation thực tế sinh SQL, quản lý lazy loading CGLIB proxy, duy trì persistence context là first-level cache + dirty tracking; Spring Data JPA nằm trên cùng tự sinh repository implementation từ interface lúc runtime qua method name parsing.
Bài 02 chỉ ra entity là Java class với 5 requirement: @Entity, no-arg constructor, không final, @Id, mutable field. Java record bị loại vì final và immutable. Ba chiến lược ID: IDENTITY đơn giản, SEQUENCE batch-friendly, UUID v7 distributed. @Enumerated(EnumType.STRING) bắt buộc — ORDINAL gây data corruption khi reorder enum. Equals/hashCode pattern: based on ID null-safe, hashCode constant per class.
Bài 03 bóc 3 tier query: built-in inherit (findById, save, deleteById), derived query từ tên method (findByStatusAndCreatedAtAfter → JPQL tự sinh), @Query cho phức tạp. Projection DTO via JPQL SELECT new ... chỉ load field cần — tốt hơn load full entity. @Modifying bắt buộc cho UPDATE/DELETE, kết hợp @Transactional.
Bài 04 là trung tâm performance: 4 association type, owning vs inverse side, fetch LAZY default cho @OneToMany/@ManyToMany, EAGER default cho @ManyToOne phải override về LAZY. N+1 problem: lazy load trong loop sinh 1+N query — detection qua spring.jpa.show-sql=true hoặc Hibernate statistics. Bốn fix theo thứ tự ưu tiên: JOIN FETCH trong @Query, @EntityGraph, @BatchSize, projection DTO. Cascade ALL + orphanRemoval cho parent-child ownership.
Bài 05 bóc @Transactional qua AOP proxy: proxy chèn begin/commit/rollback quanh method. Self-invocation bypass proxy — this.method() trong cùng class đi thẳng vào object thật bỏ qua proxy. Rollback mặc định chỉ với RuntimeException/Error — checked exception commit bình thường. Bảy propagation: REQUIRED join/create, REQUIRES_NEW suspend tx cha tạo tx độc lập, NESTED savepoint trong tx cha. readOnly = true bỏ dirty checking, tăng hiệu năng read.
Bài 06 chốt schema lifecycle: Flyway version mỗi thay đổi thành V<version>__<desc>.sql, apply đúng 1 lần, ghi flyway_schema_history audit. Concurrent-safe qua DB lock. Forward-only rollback: bug = new migration script. Baseline existing DB với baseline-on-migrate: true. Không sửa migration đã apply — checksum mismatch fail startup.
Bài 07 bóc pagination: Pageable auto-bind từ query string, Page<T> 2 query (data + COUNT), Slice<T> 1 query (bỏ COUNT), keyset Window O(log N) cursor-based. OFFSET deep page chậm tuyến tính. Whitelist sort column + giới hạn max-page-size để tránh DoS.
Bài 08 mini-challenge — TaskFlow v2 migrate từ ConcurrentHashMap sang PostgreSQL: Flyway migration script, JPA entity, JpaRepository, JOIN FETCH fix N+1, @Transactional đúng chỗ, Pageable endpoint.
Cheat sheet
| Concept | Khi nào dùng | Pitfall thường gặp |
|---|---|---|
@Entity | Class map sang DB table | Class final → Hibernate không proxy được → lazy load fail |
@Id @GeneratedValue(IDENTITY) | PK auto-increment đơn giản | Không batch insert được — mỗi INSERT cần DB trả ID trước |
@Id @GeneratedValue(SEQUENCE) | PK high-throughput, batch | Phải khai @SequenceGenerator explicit hoặc Hibernate dùng sequence mặc định |
@Column(nullable = false) | Đánh dấu NOT NULL | Chỉ hint schema gen — Flyway migration mới enforce DB-level NOT NULL |
@Enumerated(EnumType.STRING) | Map enum → VARCHAR | ORDINAL (default!) gây corrupt khi reorder — luôn dùng STRING |
@Embeddable | Value object inline vào entity | Không có lifecycle riêng — không phải entity, không có @Id |
@PrePersist / @PostLoad | Audit timestamp, default value | Không dùng cho business logic phức tạp — chỉ simple field init |
JpaRepository | Mọi CRUD + page + sort | findAll() không LIMIT — load toàn bộ bảng nếu không dùng Pageable |
| Derived query | 1-3 điều kiện đơn giản | Method name dài hơn 5 keyword → dùng @Query cho readable |
@Query JPQL | Query phức tạp, JOIN, GROUP BY | JPQL query entity không table — tên field Java, không tên column SQL |
@Query(nativeQuery=true) | DB-specific feature (JSON, full-text) | Lock vendor — không portable sang DB khác |
@Modifying + @Transactional | Bulk UPDATE / DELETE | Quên @Modifying → InvalidDataAccessApiUsageException |
@ManyToOne(fetch=LAZY) | Parent reference từ child | Default EAGER — phải explicit fetch=LAZY để tránh join tự động |
@OneToMany(mappedBy=...) | Collection con từ parent | Quên mappedBy → Hibernate sinh join table trung gian không mong muốn |
JOIN FETCH trong @Query | Fix N+1 cho collection | Không dùng được với Pageable + collection — HHH90003004 warning |
@EntityGraph | Fix N+1 linh hoạt, không sửa query | Override fetch plan cho từng method riêng — verbose với nhiều sub-graph |
@BatchSize(size=25) | Fix N+1 cho nhiều query nhỏ | Không loại bỏ N+1, chỉ batch — vẫn N/25 queries thay vì N |
@Transactional | Method có write DB operation | Self-call bypass AOP proxy — không có effect khi gọi this.method() |
@Transactional(readOnly=true) | Read-only service method | Hibernate bỏ dirty check — không dùng cho method có write |
REQUIRES_NEW propagation | Sub-operation độc lập (audit log) | Suspend tx cha — nếu tx cha rollback, REQUIRES_NEW đã committed rồi |
rollbackFor = Exception.class | Rollback cả checked exception | Default không rollback checked — phải explicit khi cần |
V<n>__<desc>.sql | Schema migration mỗi thay đổi | Hai dấu gạch dưới __ bắt buộc — một dấu gạch là description separator |
baseline-on-migrate: true | Migrate legacy DB vào Flyway | Không dùng cho DB mới — chỉ khi DB đã có schema trước Flyway |
Page<T> | List với total count cho UI | 2 SQL query mỗi request — COUNT có thể chậm với WHERE phức tạp |
Slice<T> | Infinite scroll mobile | Không có totalElements — không biết tổng số record |
Window (keyset) | Realtime feed, log viewer | Spring Data 3.1+ — không portable với mọi DB |
Glossary module
| Thuật ngữ | Định nghĩa 1 câu | Nguồn |
|---|---|---|
| JPA (Jakarta Persistence API) | Chuẩn Java (JSR 338) định nghĩa interface ORM — @Entity, EntityManager, JPQL — không có code chạy, chỉ spec | Bài 01 |
| Hibernate ORM | Implementation JPA phổ biến nhất (hơn 90% thị phần) — sinh SQL, quản lý session, lazy loading proxy CGLIB | Bài 01 |
| ORM (Object-Relational Mapping) | Pattern map giữa Java object model và SQL relational model tự động, bỏ boilerplate JDBC | Bài 01 |
| Impedance mismatch | 5 bất đồng giữa ORM và DB: inheritance, identity, association, navigation, granularity — ORM giải quyết nhưng không hoàn hảo | Bài 01 |
| EntityManager | JPA core API quản lý lifecycle entity: persist, find, merge, remove, createQuery — Spring Data wrap phía trên | Bài 01 |
| Persistence context | First-level cache + dirty tracking theo scope transaction — Hibernate track mọi managed entity, flush thay đổi khi commit | Bài 01 |
| Entity lifecycle | 4 trạng thái: Transient (chưa có ID, ngoài context), Managed (trong context, track), Detached (có ID, ngoài context), Removed (xoá pending) | Bài 01 |
| JPQL (Jakarta Persistence Query Language) | Query language JPA — query entity và field Java thay vì table/column SQL, Hibernate compile thành SQL dialect | Bài 01 |
| Dirty checking | Hibernate so sánh entity snapshot lúc load với state hiện tại khi flush — tự sinh UPDATE nếu có thay đổi, không cần gọi save | Bài 01 |
@GeneratedValue(IDENTITY) | DB auto-increment (BIGSERIAL Postgres) — đơn giản nhưng không batch insert được vì cần ID ngay sau INSERT | Bài 02 |
@GeneratedValue(SEQUENCE) | Hibernate pre-allocate ID từ DB sequence theo block — cho phép batch insert không cần round-trip DB | Bài 02 |
@Embeddable | Value object được store inline trong table của entity chủ — không có lifecycle riêng, không có @Id, không có bảng riêng | Bài 02 |
| Naming strategy | Quy tắc Hibernate map tên Java field sang tên column SQL — Boot default SpringPhysicalNamingStrategy: camelCase → snake_case | Bài 02 |
| Owning side | Side trong relationship chứa foreign key (@JoinColumn) — Hibernate chỉ nhìn owning side để sinh SQL INSERT/UPDATE | Bài 04 |
mappedBy | Attribute trên inverse side chỉ về field owning side — tránh Hibernate tạo join table trùng | Bài 04 |
| Fetch type LAZY | Collection/association chỉ load từ DB khi accessed lần đầu — mặc định @OneToMany/@ManyToMany, phải explicit cho @ManyToOne | Bài 04 |
| N+1 problem | Anti-pattern: 1 query load N entity, rồi N query lazy-load association của từng entity — tổng 1+N queries thay vì 1 JOIN | Bài 04 |
| JOIN FETCH | JPQL keyword force eager load association trong 1 query — SELECT p FROM Project p JOIN FETCH p.tasks | Bài 04 |
@EntityGraph | Annotation định nghĩa fetch plan per-method — override LAZY thành EAGER cho specific repository method | Bài 04 |
@Transactional | Annotation declarative transaction — Spring tạo AOP proxy wrap method với begin/commit/rollback | Bài 05 |
| AOP proxy | CGLIB subclass Spring tạo cho bean có @Transactional — mọi call từ bên ngoài qua proxy, self-call bypass | Bài 05 |
| Self-invocation | Method trong class gọi method khác cùng class (this.method()) — bypass AOP proxy, @Transactional không có effect | Bài 05 |
| Propagation REQUIRED | Default @Transactional — tham gia transaction hiện có nếu có, tạo mới nếu không | Bài 05 |
| Propagation REQUIRES_NEW | Luôn tạo transaction mới độc lập, suspend transaction cha — dùng cho audit log, notification | Bài 05 |
readOnly = true | Hint Hibernate skip dirty checking và flush — tăng hiệu năng read endpoint, không dùng cho method write | Bài 05 |
| Flyway | Schema migration tool — version mỗi thay đổi thành SQL script, apply đúng 1 lần, audit qua flyway_schema_history | Bài 06 |
flyway_schema_history | Bảng Flyway tự tạo tracking migrations đã apply: version, checksum, timestamp, success/fail | Bài 06 |
| Baseline | Flyway technique để bỏ qua lịch sử migration cũ của DB đã có sẵn — dùng khi migrate legacy DB vào Flyway | Bài 06 |
Pageable | Interface Spring Data wrap page index + size + sort — auto-bind từ query string qua PageableHandlerMethodArgumentResolver | Bài 07 |
Page<T> | Wrapper result với content + total count + metadata (totalPages, hasNext) — 2 SQL queries | Bài 07 |
Slice<T> | Wrapper result chỉ có content + hasNext (không total count) — 1 SQL query, dùng cho infinite scroll | Bài 07 |
| Keyset pagination | Cursor-based pagination dùng ID/timestamp làm điểm neo thay vì OFFSET — O(log N) constant, stable với insert | Bài 07 |
| Projection DTO | Query chỉ select field cần (SELECT new ... JPQL) thay vì load full entity — giảm data transfer + mapping overhead | Bài 03 |
Pitfall tổng hợp
12 pitfall lớn nhất gom từ section "Pitfall tổng hợp" các bài.
1. N+1 problem ẩn trong code bình thường
// SAI: 101 queries cho 100 projects
@Transactional(readOnly = true)
public List<ProjectDto> listProjects() {
List<Project> projects = projectRepo.findAll(); // 1 query
return projects.stream()
.map(p -> new ProjectDto(p.getName(), p.getTasks().size())) // N lazy queries
.toList();
}
// DUNG: 1 query voi JOIN FETCH
@Query("SELECT p FROM Project p LEFT JOIN FETCH p.tasks")
List<Project> findAllWithTasks();
Lý do: p.getTasks() trigger lazy load cho từng entity riêng. Detection: bật spring.jpa.show-sql=true hoặc hibernate.generate_statistics=true — đếm số query per request.
2. Self-invocation bypass @Transactional
// SAI: createBatch goi this.create — bypass proxy, tx khong chay
@Service
public class ProjectService {
public void createBatch(List<Request> reqs) {
reqs.forEach(req -> this.create(req)); // self-call!
}
@Transactional
public Project create(Request req) { ... }
}
// DUNG: inject self bean hoac tach class
@Service
public class ProjectService {
@Autowired
private ProjectService self; // inject proxy
public void createBatch(List<Request> reqs) {
reqs.forEach(req -> self.create(req)); // qua proxy
}
}
Lý do: Spring tạo CGLIB proxy ngoài object. Call this.method() đi thẳng vào object thật bỏ qua proxy — @Transactional không wrap.
3. Checked exception không rollback
// SAI: IOException la checked — tx commit du throw
@Transactional
public void importFile(MultipartFile file) throws IOException {
Project p = new Project(file.getOriginalFilename());
projectRepo.save(p);
parseAndProcess(file); // throw IOException
// tx COMMIT du IOException! Project duoc luu
}
// DUNG: rollbackFor explicit
@Transactional(rollbackFor = Exception.class)
public void importFile(MultipartFile file) throws IOException { ... }
Lý do: @Transactional mặc định chỉ rollback RuntimeException và Error. Checked exception bị bỏ qua — transaction commit, data được lưu dù method fail.
4. @Enumerated ORDINAL gây data corruption
// SAI: ORDINAL (default) — number trong DB
@Enumerated // default = ORDINAL
private TaskStatus status;
// TaskStatus.OPEN = 0, CLOSED = 1, CANCELLED = 2
// INSERT: status = 0 (OPEN)
// Sau: add PENDING truoc OPEN
// enum TaskStatus { PENDING, OPEN, CLOSED, CANCELLED }
// OPEN gio = 1, CLOSED = 2 — data cu bi shifted!
// DUNG: STRING luon
@Enumerated(EnumType.STRING)
private TaskStatus status;
// DB luu "OPEN", "CLOSED" — reorder enum OK
Lý do: ORDINAL lưu vị trí index của enum constant. Thêm constant ở giữa = shift tất cả value sau — data cũ đọc sai. STRING lưu tên constant — thay đổi thứ tự không ảnh hưởng.
5. @ManyToOne EAGER default gây N+1 ẩn
// SAI: @ManyToOne mac dinh EAGER — moi load Task la load Project
@ManyToOne // EAGER by default!
@JoinColumn(name = "project_id")
private Project project;
// SELECT * FROM tasks WHERE id = 1
// SELECT * FROM projects WHERE id = 42 -- automatic, khong thay trong code
// DUNG: override LAZY
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "project_id")
private Project project;
Lý do: @ManyToOne và @OneToOne mặc định EAGER — mọi lần load entity con đều kéo theo entity cha. List 100 tasks = 100 project query nếu mỗi task có EAGER project.
6. LazyInitializationException ngoài transaction
// SAI: load entity trong tx, dung ngoai tx
@Transactional
public Project findProject(Long id) {
return projectRepo.findById(id).orElseThrow();
// tx end khi method return — entity becomes DETACHED
}
// Controller:
Project project = service.findProject(1L);
int taskCount = project.getTasks().size(); // LazyInitializationException!
// Session da dong, khong the lazy load
// DUNG: load data can thiet trong tx
@Transactional
public ProjectDto findProjectWithTasks(Long id) {
Project p = projectRepo.findById(id).orElseThrow();
return new ProjectDto(p.getName(), p.getTasks().size()); // trong tx
}
Lý do: Persistence context đóng khi transaction end. Lazy load ngoài transaction = "no session" → LazyInitializationException. Solution: load trong transaction, hoặc dùng JOIN FETCH.
7. Sửa file migration đã apply
# SAI: edit V2__add_column.sql sau khi da apply
V2__add_column.sql (da apply + checksum = 12345)
# Edit noi dung file
# Flyway tiep theo:
# ERROR: Checksum mismatch for migration V2__add_column.sql
# Expected: 12345 — Found: 67890
# App khong start!
# DUNG: tao migration moi
V3__fix_add_column.sql # migration moi cho thay doi them
Lý do: Flyway lưu checksum của mỗi script trong flyway_schema_history. Sửa file = checksum mismatch = Flyway từ chối apply = app không start. Rule cứng: migration đã apply là immutable.
8. cascade = CascadeType.ALL từ child lên parent
// SAI: cascade tu Task len Project — xoa Task xoa ca Project
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "project_id")
private Project project;
// taskRepo.delete(task);
// -> DELETE FROM tasks WHERE id = ?
// -> DELETE FROM projects WHERE id = ? -- ngoai y muon!
// DUNG: cascade chi tu parent xuong child
@OneToMany(mappedBy = "project", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Task> tasks = new ArrayList<>();
Lý do: Cascade từ child (Task) lên parent (Project) = xoá task kéo theo xoá project. Chỉ cascade từ parent xuống child — delete project xoá tất cả task của project.
9. equals/hashCode dùng Lombok @Data trên entity
// SAI: @Data sinh equals/hashCode dua tren tat ca field
@Entity
@Data // NGUY HIEM cho entity!
public class Project {
@Id Long id;
String name;
@OneToMany List<Task> tasks;
// equals so sanh ca tasks -> trigger lazy load khi cho vao Set!
// hashCode thay doi khi id thay doi -> mat item trong HashSet
}
// DUNG: manual equals/hashCode
@Override
public boolean equals(Object o) {
if (!(o instanceof Project p)) return false;
return id != null && id.equals(p.id);
}
@Override
public int hashCode() { return getClass().hashCode(); }
Lý do: @Data dùng mọi field cho equals/hashCode — trigger lazy load collection khi compare, và hashCode thay đổi khi id thay đổi từ null (Transient) sang value (Managed) → mất entity trong HashSet/HashMap.
10. REQUIRES_NEW trong self-call
// SAI: self-call bypass proxy — REQUIRES_NEW khong tac dung
@Service
public class AuditService {
@Transactional
public void doWork() {
// ... business logic
this.logAudit("work done"); // self-call!
}
@Transactional(propagation = REQUIRES_NEW)
public void logAudit(String msg) {
auditRepo.save(new AuditLog(msg));
// Van chay trong tx cua doWork(), KHONG phai tx moi!
}
}
Lý do: Self-call bypass proxy — REQUIRES_NEW không tạo transaction mới. logAudit tham gia transaction của doWork. Khi doWork rollback, logAudit cũng rollback — audit log bị mất. Fix: inject self bean hoặc tách AuditService thành class riêng.
11. ddl-auto=update production
# SAI: hibernate quan ly schema production
spring:
jpa:
hibernate:
ddl-auto: update
# DUNG: validate + Flyway
spring:
jpa:
hibernate:
ddl-auto: validate
flyway:
enabled: true
Lý do: ddl-auto=update không drop column, không rename (= drop + add → data loss), race condition khi multi-pod startup, không audit trail. Flyway đảm bảo migration atomic, versioned, audited, concurrent-safe.
12. OFFSET deep pagination không scale
// SAI: page 1000 + size 20 = OFFSET 20000 — chậm tuyến tính
GET /projects?page=1000&size=20
// SQL: SELECT * FROM projects ORDER BY id LIMIT 20 OFFSET 20000
// DB phai skip 20000 row truoc khi tra 20 row!
// DUNG cho realtime feed: keyset cursor
GET /projects?after=20001&size=20
// SQL: SELECT * FROM projects WHERE id > 20001 ORDER BY id LIMIT 20
// B-tree index seek O(log N) — khong phu thuoc depth
Lý do: OFFSET N yêu cầu DB đọc và bỏ N row — chậm tuyến tính với N. Page 1000 với size 20 = OFFSET 20.000 = đọc 20.020 row chỉ để trả 20. Dùng keyset cursor cho data stream lớn.
Self-assessment outcomes
Tick được hết các ô sau, bạn sẵn sàng Module 05 (Spring Security). Nếu chưa: re-read bài tương ứng trước khi tiếp tục.
- Explain 3 layer abstraction JPA spec / Hibernate / Spring Data JPA và ORM impedance mismatch — biết chọn đúng layer để debug khi query sai.
- Nếu chưa: re-read bài 01 section 2-5 ("3 layer", "ORM impedance mismatch", "EntityManager", "persistence context"). Vẽ sơ đồ 3 layer từ trí nhớ không nhìn tài liệu. Giải thích tại sao cần Hibernate trên JPA, tại sao cần Spring Data trên Hibernate.
- Implement entity mapping đúng chuẩn:
@Entitylifecycle,@Idstrategy, naming convention,@Embeddable, lifecycle callback, equals/hashCode pattern.- Nếu chưa: re-read bài 02 section 1-9 + SelfCheck Q1-Q3. Viết entity
Ordervới@Id(SEQUENCE),@Embeddable Address,@Enumerated(STRING),@PrePersistset timestamp, equals/hashCode đúng pattern — từ scratch không copy.
- Nếu chưa: re-read bài 02 section 1-9 + SelfCheck Q1-Q3. Viết entity
- Design repository query ở đúng tier: built-in method, derived query,
@QueryJPQL/native, Specification — và expose kết quả qua projection DTO.- Nếu chưa: re-read bài 03 section 1-5 + SelfCheck Q1. Viết 5 query cho
OrderRepository: 1 built-in, 1 derived, 1 JPQL@Query, 1 native, 1 projection DTO. Verify generated SQL bằngshow-sql=true.
- Nếu chưa: re-read bài 03 section 1-5 + SelfCheck Q1. Viết 5 query cho
- 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.- Nếu chưa: re-read bài 04 section 5 ("N+1 problem") + SelfCheck Q1. Reproduce N+1 trong code thật, bật
hibernate.generate_statistics=true, đếm query count. Apply JOIN FETCH fix, verify query count giảm xuống 1-2.
- Nếu chưa: re-read bài 04 section 5 ("N+1 problem") + SelfCheck Q1. Reproduce N+1 trong code thật, bật
- Implement
@Transactionalđúng propagation và rollback rule — tránh self-invocation bypass và checked-exception silent commit.- Nếu chưa: re-read bài 05 section 2-7 ("AOP proxy", "rollback rules", "propagation", "self-invocation"). Viết test reproduce self-invocation bypass: method A không transactional gọi method B
@Transactionalcùng class — verify B không trong transaction bằngTransactionSynchronizationManager.isActualTransactionActive().
- Nếu chưa: re-read bài 05 section 2-7 ("AOP proxy", "rollback rules", "propagation", "self-invocation"). Viết test reproduce self-invocation bypass: method A không transactional gọi method B
- Design schema migration workflow với Flyway: naming convention, baseline existing DB, rollback strategy forward-only, multi-environment pattern.
- Nếu chưa: re-read bài 06 section 2-5 + SelfCheck Q1. Tạo 3 migration script từ scratch:
V1__init.sql,V2__add_column.sql,V3__add_index.sql. Chạyflyway migratetrên DB local, verifyflyway_schema_history. Thử sửaV2và chạy lại — xác nhận checksum error.
- Nếu chưa: re-read bài 06 section 2-5 + SelfCheck Q1. Tạo 3 migration script từ scratch:
- Choose đúng pagination strategy cho từng use case:
PagevsSlicevs keyset cursor, validate và whitelist sort column để tránh DoS.- Nếu chưa: re-read bài 07 section 3-8 + SelfCheck Q1. Implement endpoint 3 loại:
Pagecho admin table,Slicecho mobile infinite scroll, keysetWindowcho activity feed. Thêm@PageableDefaultvà whitelist sort fields. Verify SQL generated bằngshow-sql=true.
- Nếu chưa: re-read bài 07 section 3-8 + SelfCheck Q1. Implement endpoint 3 loại:
What's next — Module 05: Spring Security cơ bản
Module 04 đã có persistence layer đầy đủ: entity, repository, transaction, schema migration, pagination. Module 05 thêm security layer: Spring Security filter chain, authentication (who are you?) và authorization (what can you do?), JWT Bearer token, password hashing BCrypt, và role-based access control.
TaskFlow v2 sẽ được thêm: user registration/login endpoint, JWT token issue + validate, @PreAuthorize("hasRole('ADMIN')") bảo vệ write endpoint, và anonymous read cho public project list.
→ Đi tới Module 05: Spring Security cơ bản
Tài liệu mở rộng
Sách:
- Java Persistence with Hibernate — Christian Bauer, Gavin King, Gary Gregory (Manning, 3rd ed. 2023). Cuốn reference toàn diện nhất cho JPA/Hibernate — cover mọi concept bài 01-05 với depth. Chapter 7-9 cho N+1 và performance.
- Pro JPA 2 in Spring — Mike Keith, Merrick Schincariol (Apress). Tập trung Spring + JPA integration — pagination, repository abstraction, transaction management.
Spec / Reference:
- Jakarta Persistence 3.2 Spec — chuẩn JPA chính thức. Section 3 (Entity), Section 4 (Query Language JPQL), Section 7 (EntityManager API).
- Hibernate ORM Reference — guide Hibernate 6.x, cover stateless session, batch processing, statistics.
- Spring Data JPA Reference — repository abstraction, query creation, Specification, auditing.
- Flyway Documentation — naming convention, CLI, Boot integration, repair, baseline.
Video / Talk:
- Vlad Mihalcea — Hibernate Performance — tác giả "High-Performance Java Persistence". Deep dive N+1, connection pool, batch. Essential cho JPA performance.
- Spring Tips: Spring Data JPA — Josh Long demo repository, Specification, auditing patterns.
Source code:
SimpleJpaRepository— implementation mặc địnhJpaRepository— đọc để hiểufindAll,save,deleteByIdhoạt động thế nào.JpaRepositoryFactory— factory tạo proxy repository từ interface lúc runtime.- Flyway GitHub — source
MigrationExecutor,SchemaHistoryFactory— hiểu cơ chế checksum + locking.
Blog:
- Vlad Mihalcea Blog — 400+ bài JPA/Hibernate performance, N+1, connection pool, batch. Bookmark.
- Baeldung — Spring Data JPA — tutorial thực hành từng feature.
- Thoughts on Java (Thorben Janssen) — JPA best practices, pitfall, performance tips từ JPA trainer.
Chúc mừng — bạn đã hoàn thành Module 04. Persistence layer đã sẵn sàng: entity mapped, repository query, N+1 fix, transaction đúng chỗ, schema migration versioned, pagination scale. Nghỉ 1 ngày cho concept lắng xuống — đặc biệt bài 04 (N+1) và bài 05 (self-invocation). Rồi vào Module 05 — thêm security layer cho TaskFlow.
Bài này có giúp bạn hiểu bản chất không?