Spring Boot/Module 04 — Tổng kết & cheat sheet
~15 phútSpring Data JPAMiễn phí lượt xem

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

ConceptKhi nào dùngPitfall thường gặp
@EntityClass map sang DB tableClass final → Hibernate không proxy được → lazy load fail
@Id @GeneratedValue(IDENTITY)PK auto-increment đơn giảnKhông batch insert được — mỗi INSERT cần DB trả ID trước
@Id @GeneratedValue(SEQUENCE)PK high-throughput, batchPhải khai @SequenceGenerator explicit hoặc Hibernate dùng sequence mặc định
@Column(nullable = false)Đánh dấu NOT NULLChỉ hint schema gen — Flyway migration mới enforce DB-level NOT NULL
@Enumerated(EnumType.STRING)Map enum → VARCHARORDINAL (default!) gây corrupt khi reorder — luôn dùng STRING
@EmbeddableValue object inline vào entityKhông có lifecycle riêng — không phải entity, không có @Id
@PrePersist / @PostLoadAudit timestamp, default valueKhông dùng cho business logic phức tạp — chỉ simple field init
JpaRepositoryMọi CRUD + page + sortfindAll() không LIMIT — load toàn bộ bảng nếu không dùng Pageable
Derived query1-3 điều kiện đơn giảnMethod name dài hơn 5 keyword → dùng @Query cho readable
@Query JPQLQuery phức tạp, JOIN, GROUP BYJPQL 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 + @TransactionalBulk UPDATE / DELETEQuên @ModifyingInvalidDataAccessApiUsageException
@ManyToOne(fetch=LAZY)Parent reference từ childDefault EAGER — phải explicit fetch=LAZY để tránh join tự động
@OneToMany(mappedBy=...)Collection con từ parentQuên mappedBy → Hibernate sinh join table trung gian không mong muốn
JOIN FETCH trong @QueryFix N+1 cho collectionKhông dùng được với Pageable + collection — HHH90003004 warning
@EntityGraphFix N+1 linh hoạt, không sửa queryOverride 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
@TransactionalMethod có write DB operationSelf-call bypass AOP proxy — không có effect khi gọi this.method()
@Transactional(readOnly=true)Read-only service methodHibernate bỏ dirty check — không dùng cho method có write
REQUIRES_NEW propagationSub-operation độc lập (audit log)Suspend tx cha — nếu tx cha rollback, REQUIRES_NEW đã committed rồi
rollbackFor = Exception.classRollback cả checked exceptionDefault không rollback checked — phải explicit khi cần
V<n>__<desc>.sqlSchema migration mỗi thay đổiHai dấu gạch dưới __ bắt buộc — một dấu gạch là description separator
baseline-on-migrate: trueMigrate legacy DB vào FlywayKhông dùng cho DB mới — chỉ khi DB đã có schema trước Flyway
Page<T>List với total count cho UI2 SQL query mỗi request — COUNT có thể chậm với WHERE phức tạp
Slice<T>Infinite scroll mobileKhông có totalElements — không biết tổng số record
Window (keyset)Realtime feed, log viewerSpring Data 3.1+ — không portable với mọi DB

Glossary module

Thuật ngữĐịnh nghĩa 1 câuNguồ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ỉ specBài 01
Hibernate ORMImplementation JPA phổ biến nhất (hơn 90% thị phần) — sinh SQL, quản lý session, lazy loading proxy CGLIBBài 01
ORM (Object-Relational Mapping)Pattern map giữa Java object model và SQL relational model tự động, bỏ boilerplate JDBCBài 01
Impedance mismatch5 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ảoBài 01
EntityManagerJPA core API quản lý lifecycle entity: persist, find, merge, remove, createQuery — Spring Data wrap phía trênBài 01
Persistence contextFirst-level cache + dirty tracking theo scope transaction — Hibernate track mọi managed entity, flush thay đổi khi commitBài 01
Entity lifecycle4 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 dialectBài 01
Dirty checkingHibernate 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 saveBà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 INSERTBài 02
@GeneratedValue(SEQUENCE)Hibernate pre-allocate ID từ DB sequence theo block — cho phép batch insert không cần round-trip DBBài 02
@EmbeddableValue 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êngBài 02
Naming strategyQuy tắc Hibernate map tên Java field sang tên column SQL — Boot default SpringPhysicalNamingStrategy: camelCase → snake_caseBài 02
Owning sideSide trong relationship chứa foreign key (@JoinColumn) — Hibernate chỉ nhìn owning side để sinh SQL INSERT/UPDATEBài 04
mappedByAttribute trên inverse side chỉ về field owning side — tránh Hibernate tạo join table trùngBài 04
Fetch type LAZYCollection/association chỉ load từ DB khi accessed lần đầu — mặc định @OneToMany/@ManyToMany, phải explicit cho @ManyToOneBài 04
N+1 problemAnti-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 JOINBài 04
JOIN FETCHJPQL keyword force eager load association trong 1 query — SELECT p FROM Project p JOIN FETCH p.tasksBài 04
@EntityGraphAnnotation định nghĩa fetch plan per-method — override LAZY thành EAGER cho specific repository methodBài 04
@TransactionalAnnotation declarative transaction — Spring tạo AOP proxy wrap method với begin/commit/rollbackBài 05
AOP proxyCGLIB subclass Spring tạo cho bean có @Transactional — mọi call từ bên ngoài qua proxy, self-call bypassBài 05
Self-invocationMethod trong class gọi method khác cùng class (this.method()) — bypass AOP proxy, @Transactional không có effectBài 05
Propagation REQUIREDDefault @Transactional — tham gia transaction hiện có nếu có, tạo mới nếu khôngBài 05
Propagation REQUIRES_NEWLuôn tạo transaction mới độc lập, suspend transaction cha — dùng cho audit log, notificationBài 05
readOnly = trueHint Hibernate skip dirty checking và flush — tăng hiệu năng read endpoint, không dùng cho method writeBài 05
FlywaySchema migration tool — version mỗi thay đổi thành SQL script, apply đúng 1 lần, audit qua flyway_schema_historyBài 06
flyway_schema_historyBảng Flyway tự tạo tracking migrations đã apply: version, checksum, timestamp, success/failBài 06
BaselineFlyway technique để bỏ qua lịch sử migration cũ của DB đã có sẵn — dùng khi migrate legacy DB vào FlywayBài 06
PageableInterface Spring Data wrap page index + size + sort — auto-bind từ query string qua PageableHandlerMethodArgumentResolverBài 07
Page<T>Wrapper result với content + total count + metadata (totalPages, hasNext) — 2 SQL queriesBài 07
Slice<T>Wrapper result chỉ có content + hasNext (không total count) — 1 SQL query, dùng cho infinite scrollBài 07
Keyset paginationCursor-based pagination dùng ID/timestamp làm điểm neo thay vì OFFSET — O(log N) constant, stable với insertBài 07
Projection DTOQuery chỉ select field cần (SELECT new ... JPQL) thay vì load full entity — giảm data transfer + mapping overheadBà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 RuntimeExceptionError. 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@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: @Entity lifecycle, @Id strategy, 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 Order với @Id(SEQUENCE), @Embeddable Address, @Enumerated(STRING), @PrePersist set timestamp, equals/hashCode đúng pattern — từ scratch không copy.
  • Design repository query ở đúng tier: built-in method, derived query, @Query JPQL/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ằng show-sql=true.
  • 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.
  • 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 @Transactional cùng class — verify B không trong transaction bằng TransactionSynchronizationManager.isActualTransactionActive().
  • 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ạy flyway migrate trên DB local, verify flyway_schema_history. Thử sửa V2 và chạy lại — xác nhận checksum error.
  • Choose đúng pagination strategy cho từng use case: Page vs Slice vs 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: Page cho admin table, Slice cho mobile infinite scroll, keyset Window cho activity feed. Thêm @PageableDefault và whitelist sort fields. Verify SQL generated bằng show-sql=true.

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:

Video / Talk:

Source code:

  • SimpleJpaRepository — implementation mặc định JpaRepository — đọc để hiểu findAll, save, deleteById hoạ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:

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?