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/transient | Chưa thuộc persistence context và chưa có persistent identity. |
| Managed | Được EntityManager theo dõi; thay đổi sẽ dirty-check khi flush. |
| Detached | Từ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.
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.