Part 03 · Spring Framework & Spring Boot · 3.1.07

Spring Data JPA và persistence context

Repository proxy giảm boilerplate, nhưng entity lifecycle, flush, fetching, locking và transaction vẫn tuân theo JPA và database semantics.


Repository architecture

Spring Data tạo proxy implementation từ repository interface, query derivation, declared query và repository fragments. Interface nên phản ánh aggregate và use case thay vì expose CRUD vô hạn, vì generic persistence operations có thể phá invariant hoặc làm application layer phụ thuộc schema.

Entity lifecycle và identity map

StateÝ nghĩa
New/transientChưa thuộc persistence context và chưa có persistent identity.
ManagedĐược EntityManager theo dõi; thay đổi sẽ dirty-check khi flush.
DetachedTừng persistent nhưng không còn được context hiện tại theo dõi.
RemovedĐược đánh dấu xóa khi flush/commit.

Identity map trả cùng entity instance cho cùng ID trong một persistence context. clear() hoặc detach() làm state không còn tracked. Flush đồng bộ thay đổi thành SQL nhưng chưa đồng nghĩa transaction đã commit.

save(), persist và merge

save() chọn persist hoặc merge dựa trên new detection. Merge trả về managed copy; object truyền vào vẫn có thể detached, vì vậy phải dùng object được trả về nếu cần tiếp tục thao tác. Không gọi save cho mọi managed entity chỉ vì thói quen: dirty checking đã theo dõi thay đổi. ID generation strategy cũng ảnh hưởng batching và thời điểm INSERT.

Query và pagination

Derived query phù hợp điều kiện đơn giản; JPQL, query annotation, Specification, projection hoặc custom repository phục vụ trường hợp phức tạp. Native query giảm portability. Parameter binding chống SQL injection cho values, nhưng dynamic sort, field hoặc expression vẫn phải allow-list. Pagination thường chạy thêm count query, có thể rất đắt với join hoặc filter phức tạp.

Fetching và N+1

LAZY/EAGER là mapping defaults, không phải fetch plan tối ưu cho mọi use case. Phát hiện N+1 bằng SQL logs, Hibernate statistics hoặc APM; giải theo query bằng fetch join, entity graph, projection hoặc batch fetching.

Pagination caveat: collection fetch join có thể tạo duplicate rows và khiến pagination chạy trong memory hoặc cho kết quả không như mong đợi. Thường cần query IDs trước rồi fetch graph ở bước hai.

Locking và version

@Version hỗ trợ optimistic locking để phát hiện lost update khi entity được update. Pessimistic lock giữ database lock trong transaction thật và cần timeout, deadlock handling cùng thứ tự lock nhất quán. Repository @Lock không có giá trị bảo vệ nếu lời gọi không nằm trong transaction phù hợp.

Auditing và domain events

Auditing điền created/modified/by nhưng principal và time source phải rõ, có thể test được. Repository domain events được publish quanh repository save; chúng không tự là durable integration event và không bảo đảm broker handoff sau crash. Dùng transactional outbox cho event cần giao ra ngoài process.

Entity equality và toString()

Generated database ID chưa tồn tại trước persist, nên thiết kế equals/hashCode phải giữ kết quả ổn định khi entity đi vào Set/Map và qua các lifecycle states. Không đưa mutable associations vào equality hoặc toString(): chúng có thể trigger lazy loading, recursion và collection corruption.

Open EntityManager in View

OSIV kéo persistence context qua web request, cho phép lazy loading khi serialization hoặc render view nhưng che query, N+1 và transaction boundary. API thường nên fetch DTO hoặc projection trong service transaction rồi tắt hoặc kiểm soát OSIV có chủ đích.

Debugging model: xác định entity state → persistence context nào đang quản lý → transaction đã flush/commit chưa → query plan/fetch plan nào chạy → database lock và isolation thực tế ra sao.
Nguồn tham khảo