HIBERNATE SERIES - CUỐN SỔ

Không Gọi save(), Vì Sao DB Vẫn UPDATE?

PERSISTENCE CONTEXT - XANH = TẦNG PHÓNG TO Đọc từ trái sang phải. Màu đậm dần nghĩa là gần cơ chế hơn. Dấu * đánh dấu điểm bài viết khai thác.

Managed - tự phát UPDATE

Load entity trong transaction, sửa field rồi commit; không gọi save() mà SQL vẫn xuất hiện.

Cùng một gốcPersistence context giữ entity và loaded snapshot. Flush so trạng thái hiện tại với snapshot rồi xếp action cần thiết.

Detached - thay đổi bốc hơi

Sửa cùng object sau khi Session đóng; không còn entry và snapshot để dirty-check nên DB đứng yên.

Mức 1 - thời điểm flush

SessionImpl.flush()

Flush tường minh, trước completion hoặc trước query cần đồng bộ

  1. flushBeforeTransactionCompletion()Transaction completion gọi managed flush nếu transaction có thể flush.
  2. mustFlushBeforeCompletion()Bỏ qua khi flush mode là MANUAL hoặc transaction không ở trạng thái phù hợp.
  3. managedFlush() hoặc flush()Cả hai đi vào doFlush() và phát FlushEvent.
  4. autoFlushIfRequired(querySpaces)Đường query chỉ auto-flush khi transaction đang chạy và action có thể ảnh hưởng query-space.
  5. FlushMode.MANUALKhông có lời hứa auto-flush trước commit; phải đọc đúng flush mode của Session.
Mức 2 - duyệt cuốn sổ

DefaultFlushEventListener.onFlush()

Biến trạng thái trong persistence context thành execution queue

  1. flushEverythingToExecutions()Gọi Interceptor.preFlush(...) - method của interceptor, không phải của listener này - rồi xử lý entity và collection.
  2. flushEntities()Lấy snapshot an toàn của các EntityEntry đang managed.
  3. FlushEntityEventMỗi entry hợp lệ được gửi tới nhóm FlushEntityEventListener. Đây là cửa vào cột kế.
  4. flushCollections()Collection dirty-check có pipeline riêng, không đồng nhất với field scalar.
  5. performExecutions()Thực thi các action đã xếp theo thứ tự khóa ngoại, insert, update và delete.
  6. postFlush()Cập nhật lại bookkeeping sau khi action queue được thực thi.
Mức 3 - so current với snapshot

DefaultFlushEntityEventListener.onFlushEntity()

Điểm SQL UPDATE được quyết định

  1. entry.requiresDirtyCheck(entity)Loại sớm entity read-only, immutable hoặc chắc chắn không dirty.
  2. persister.getValues(entity)Đọc trạng thái hiện tại của object đang managed.
  3. entry.getLoadedState()Loaded snapshot là mốc so thông thường; bytecode enhancement có thể cung cấp dấu dirty khác.
  4. dirtyCheck(event)Nơi quyết định, đọc được cả ngữ cảnh; nó gọi performDirtyCheck(event). Đặt breakpoint ở đây, đừng đặt ở interface.
  5. persister.findDirty(values, loadedState, ...)Trả về index các property khác snapshot; fallback có thể dùng database snapshot.
  6. scheduleUpdate(event)Nếu cần update, thêm EntityUpdateAction vào action queue.
  7. SQL chạy sau dirty-checksave() không phải tác giả của update trên entity đã managed.

Phép kiểm: trong một transaction, load entity, sửa một field và không gọi save(). Bật SQL và breakpoint tại onFlushEntity(); xem loadedState, dirtyProperties và action queue. Lặp lại sau khi đóng EntityManager để xác nhận object detached không còn bị theo dõi.

Dùng đúng unit of work

  • Sửa managed entity trong transactionĐể dirty checking tạo update; bỏ lời gọi save() chỉ mang tính nghi thức.
  • Kiểm soát thời điểm cần SQLDùng flush tường minh khi thật sự cần DB đồng bộ giữa method, rồi đo tác động.
  • Giảm chi phí khi đọcDùng read-only đúng boundary đã kiểm chứng, không tự suy ra từ tên method.

Khi KHÔNG được kết luận

  • Flush luôn đồng nghĩa commitFlush đồng bộ state xuống DB trong transaction; rollback sau đó vẫn có thể hủy kết quả.
  • Mọi query đều buộc flushVới AUTO, Hibernate xét transaction và query-space; query không liên quan có thể không flush.
  • Câu UPDATE chỉ chứa cột đã đổifindDirty quyết có update hay không; SQL sinh ra mặc định viết lại cả hàng. Muốn hẹp lại phải khai @DynamicUpdate.
  • Không có snapshot thì không thể dirty-checkEnhancement, interceptor hoặc database snapshot là các đường khác trong source.