JPA SERIES - QUAN HỆ

Hai Field Java, Một FK: Chỉ Owning Side Cầm Bút

SOURCE OF TRUTH - XANH = TẦNG PHÓNG TOmappedBy khai báo collection là inverse side; nó không biến hai field Java thành một field tự đồng bộ.

Chỉ sửa inverse side

order.getItems().add(item); collection đổi nhưng item.order vẫn null.

Một source of truth cho FKVới FK order_id ở bảng con, field OrderItem.order là owning side quyết định giá trị cột.

Đồng bộ cả hai phía

items.add(item) và item.setOrder(this); mô hình RAM nhất quán với FK cần ghi.

Mức 1 - boot mapping

CollectionBinder.bind()

mappedBy trở thành metadata inverse

  1. Đọc @OneToMany(mappedBy="order")Binder lưu tên property ở entity đích.
  2. isUnownedCollection()Trả true khi mappedBy != null.
  3. collection.setInverse(true)Collection được đánh dấu không sở hữu association mapping.
  4. bindStarToManySecondPass()Resolve property order và kiểm type của phía được trỏ tới.
  5. Không được khai join column ở inverse sideMetadata từ chối các khai báo mâu thuẫn với mappedBy.
Mức 2 - flush hai state độc lập

DefaultFlushEntityEventListener

Hibernate đọc field của child khi quyết định FK

  1. Collection cha dirtyAdd vào wrapper làm collection state thay đổi và cascade có thể persist child.
  2. AbstractCollectionPersister.isInverse()Persister biết collection không sở hữu row update cho association.
  3. Flush entity conpersister.getValues(item) đọc field item.order.
  4. item.order == nullGiá trị owning side này được bind vào cột FK của child.
  5. INSERT ... order_id = NULLCascade đã tạo row con nhưng không bịa quan hệ từ inverse collection.
Mức 3 - giữ model nhất quán

Helper Method Trên Aggregate

Một thao tác nghiệp vụ cập nhật cả hai lời khai trong RAM

  1. order.addItem(item)Đặt mutation vào một API thay vì rải hai lệnh ở caller.
  2. items.add(item)Giữ phía đọc/traverse của aggregate đúng ngay trong RAM.
  3. item.setOrder(this)Đặt owning side để FK được ghi đúng lúc flush.
  4. removeItem(item)Gỡ collection và clear owning reference theo cùng invariant.
  5. Cascade khác quyền sở hữu FKcascade truyền operation; orphanRemoval quyết định delete; cả hai không đổi owning side.

Phép kiểm: trong cùng transaction, log item.getOrder(), bật SQL và thử ba case: chỉ add collection, chỉ set child, dùng helper hai phía. Sau flush, đối chiếu object graph và cột order_id.

Giữ invariant tại một chỗ

  • Dùng helper add/remove hai phíaCaller không thể quên owning reference.
  • Viết test cả RAM lẫn DBAssert hai field trước flush và FK sau reload.
  • Chọn aggregate API rõ nghĩaKhông expose collection mutable nếu invariant quan hệ quan trọng.

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

  • Inverse side hoàn toàn vô hình với HibernateNó còn semantics cascade, dirty collection và orphan removal; chỉ không cầm quyền ghi FK này.
  • cascade = ALL tự set hai phíaCascade truyền lifecycle operation, không sửa object reference hộ ứng dụng.
  • Owning side luôn là phía chaOwning side do mapping của FK/join table quyết định, không theo tên parent/child.