HIBERNATE SERIES - CUỐN SỔ
Lazy Loading: Tờ Giấy Hẹn Phải Quay Đúng Session
Session đã đóng
order.getItems().size() chạm wrapper chưa initialized và ném LazyInitializationException.
OSIV còn mở Session
Cùng getter chạy được ở web layer, nhưng có thể phát SQL ngoài service transaction và tạo N+1.
AbstractPersistentCollection
Đọc collection chưa initialized
read()hoặcreadSize()PersistentBagchuyển thao tác đọc vào base wrapper.initialize(false)Bao tác vụ load trongwithTemporarySessionIfNeeded().session.initializeCollection(this, false)Gửi initialize event vào Session đang sở hữu wrapper.throwLazyInitializationExceptionIfNotConnected()Ném khi không còn Session, Session đóng hoặc mất kết nối. Chuỗi của nhánh này:failed to lazily initialize a collection of role: ...- không có ngoặc vuông.ENABLE_LAZY_LOAD_NO_TRANSCó thể mở temporary Session nếu bật, nhưng đổi boundary và chi phí query.
AbstractLazyInitializer.initialize()
Proxy to-one có pipeline riêng
- Getter cần targetInterceptor của proxy gọi lazy initializer khi property không thể trả bằng identifier.
isConnectedToSession()Kiểm tra proxy còn liên hệ với persistence context phù hợp.session.immediateLoad(entityName, id)Load target bằng Session nếu Session mở và kết nối.checkTargetState(session)Xử lý target null và semantics unwrap.LazyInitializationExceptionChuỗi của nhánh này có ngoặc vuông:could not initialize proxy [Entity#id] - no Session, và phân biệt tiếpthe owning Session was closed/is disconnected.
OpenEntityManagerInViewInterceptor
Cả hai nhánh trên đều dừng ở cùng một câu hỏi: Session còn dùng được không?
preHandle()Nếu chưa có holder, tạo EntityManager và bind vào thread.EntityManagerFactoryAccessor.createEntityManager()Kế thừa từ lớp cha, không khai trên chính interceptor. Persistence context sống xuyên suốt request web đồng bộ.- Service transaction kết thúcTransaction đóng nhưng suất trong pool không được trả; nó ở lại tới khi EntityManager của OSIV đóng.
- View/Jackson chạm lazySession còn mở nên query có thể chạy ngoài transaction của service.
- Một
@Transactionalkhác chạy sauEntity sửa ngoài transaction vẫn managed, nên commit của transaction này flush hộ: cú set tưởng rơi vào khoảng không lại được ghi. Không ai gọisave(). afterCompletion()Unbind holder rồi đóng EntityManager ở cuối request - đây mới là lúc suất được trả.
Phép kiểm: tắt spring.jpa.open-in-view, gọi đúng endpoint và đặt breakpoint tại hai hàm initialize(). Ghi lại thread, transaction active, Session open, SQL count và metric pool tại chính thời điểm getter lazy chạy.
Thiết kế fetch theo use case
- Load đủ dữ liệu trong transactionDùng projection, entity graph hoặc query fetch phù hợp endpoint.
- Dựng DTO trước khi rời serviceKhông mang proxy và collection wrapper qua boundary web.
- Đếm query, không đoánOSIV che exception nhưng không xóa chi phí lazy load.
Khi KHÔNG được kết luận
- OSIV ghim connection từ đầu requestMở sổ tốn 0 suất - Hibernate lấy connection muộn nhất có thể. Suất bị lấy từ câu SQL đầu tiên, rồi giữ tới khi đóng sổ, tức hết request.
- Tắt OSIV là tự hết N+1Nó làm query muộn nổ sớm; query trong transaction vẫn có thể N+1.
- OSIV cứu được mọi cú chạm lazy
@Async/@Scheduledchạy ở thread khác: túi khác, không sổ nào - nổ y hệt kể cả khi OSIV bật (afterConcurrentHandlingStarted()). - Mọi lazy value đều là Hibernate proxyTo-one proxy và persistent collection dùng class, event và đường initialize khác nhau.
Hai phép đo trên sổ mở kiểu OSIV. Trái: cái giá về connection - hai trật tự (chờ trước khi chạm lazy, và chạm lazy trước khi chờ) cho kết quả giống hệt nhau. Phải: mặt tối - entity sửa ngoài transaction vẫn managed, nên một @Transactional khác trong cùng request flush hộ.
mo so, chua chay gi -> 0 suat transaction cua service dong -> 1 suat <- KHONG nha cho 300ms -> 1 suat cham lazy -> 1 suat DONG so -> 0 suat
con managed sau khi tx dong ? true note trong DB ngay sau cu set -> chua doi sau MOT transaction khac -> DA DOI