HIBERNATE SERIES - CUỐN SỔ
merge(): Chép Vào Bản Managed, Không Nhận Lại Bản Cũ
ENTITY STATE - XANH = TẦNG PHÓNG TOĐọc từ trái sang phải. Dấu
* đánh dấu các bước bài viết khai thác. Điểm chốt của cả trang nằm ở cuối cột 3: object đầu vào và object trả về là hai vật khác nhau.Triệu chứng ồn
em.persist(detached) ném EntityExistsException. Thủ tục khai sinh áp lên một kẻ đã có ID.
Quản theo danh tính, không theo referenceMỗi identity chỉ được một instance trong sổ. Object trên tay bạn và entity trong mắt Hibernate chỉ trùng nhau đúng lúc nó managed.
Triệu chứng im
Sửa field trước merge() thì sống; sửa sau, trên bản cũ, thì bay màu. DB lưu đúng một nửa, không exception nào.
Mức 1 - API đi vào event
SessionImpl.merge()
Đầu vào được bọc thành MergeEvent
merge(object)JPA/Hibernate contract trả về persistent instance chứa state đã chép.fireMerge(new MergeEvent(...))Bọc đầu vào thành một event rồi bắn qua listener group.MergeEventListener.onMerge()NơiMergeContextra đời: bản đồ detached -> managed dùng chung cho cả graph. Object đầu vào không thành managed tại chỗ.event.getResult()Kết quả do listener đặt mới là giá trịmerge()trả về.MergeContext.clear()Dọn bản đồ sao chép sau toàn bộ graph, kể cả khi có lỗi.
Mức 2 - phân loại lifecycle
DefaultMergeEventListener.doMerge()
Nhánh xử lý phụ thuộc state, không chỉ phụ thuộc ID
copiedAlready.containsKey(entity)Ngắt vòng và tránh xử lý lặp trong graph hai chiều.getEntityState(...)Phân loạiPERSISTENT,TRANSIENT,DETACHEDhoặcDELETED.entityIsDetached(...)Nhánh bài viết: lấy identifier, tìm bản persistent tương ứng.entityIsPersistent(...)Vẫn cascade và copy để xử lý graph, nhưng instance đã thuộc Session.entityIsTransient(...)Tạo một copy mới rồi lưu theo semantics của merge.
Mức 3 - detached thành managed copy
entityIsDetached()
Load, ánh xạ, cascade, copy và trả đúng instance
- Load bằng merge fetch profileTìm instance cùng entity identity trong persistence context hoặc database.
copyCache.put(entity, result, true)MergeContextgiữ ánh xạ detached -> managed và cờ đang xử lý.targetEntity(...)Unproxy bản managed và kiểm tra version/type trước khi chép.cascadeOnMerge(...)CascadeMERGEđi qua graph; cascadePERSISTlà semantics khác.copyValues(source, target, ...)Chép property từ object detached sang managed target.event.setResult(result)Managed result được trả về; source không được nhập lại Session.
Phép kiểm: gọi Order managed = em.merge(detached), kiểm tra detached == managed là false và em.contains() cho từng object. Sửa một field trên mỗi bản sau merge, flush rồi đối chiếu field nào tới DB.
Giữ một nguồn state
- Ưu tiên DTO + reload managed entityTrong transaction POST, load theo ID rồi map các field được phép sửa.
- Nếu merge, dùng ngay giá trị trả vềCoi object đầu vào hết vai sau lời gọi.
- Rà cascade theo lifecycle conKhông dùng
PERSISTcho graph có thể chứa entity detached.
Khi KHÔNG được kết luận
- ID khác null luôn đồng nghĩa detachedState còn phụ thuộc persistence context và unsaved-value strategy.
merge()gắn lại chính object cũContract trả một managed copy; object cũ vẫn detached.save()luôn chọn merge đúng cho cả graphSpring Data chọn ở root; cascade của association vẫn có thể gọi semantics khác.