HIBERNATE SERIES - CUỐN SỔ

Entity Vẫn Trong HashSet, Nhưng contains() Không Tìm Thấy

IDENTITY - XANH = TẦNG PHÓNG TOĐọc từ contract của cấu trúc băm tới lifecycle cấp ID và ranh giới proxy.

Trước persist

id == null; object được đặt vào bucket tính từ hash hiện tại.

Bucket không tự chuyển đồNếu field tham gia hashCode() đổi khi object còn trong Set, lookup tính bucket mới nhưng object vẫn nằm ở bucket cũ.

Sau persist

ID được cấp, hash đổi; size() == 1 và iteration thấy object nhưng contains() có thể false.

Mức 1 - HashSet dùng HashMap

HashMap.putVal() / getNode()

Cùng key phải đi lại đúng bucket

  1. HashSet.add(entity)Ủy quyền cho backing HashMap.put(entity, PRESENT).
  2. hash(entity)Gọi entity.hashCode(), trộn bit rồi chọn bucket theo capacity.
  3. putVal(...)Đặt node vào bucket ở thời điểm add; Set không theo dõi field thay đổi.
  4. getNode(key)contains() tính lại hash hiện tại và chỉ tìm trong bucket tương ứng.
  5. Contract ổn định hashTrong một execution, nếu dữ liệu dùng cho equals không đổi thì hash phải nhất quán.
Mức 2 - lifecycle cấp danh tính

Transient -> Managed

Generated ID có thể xuất hiện sau khi object đã vào Set

  1. Construct entityDatabase-generated identifier thường đang null.
  2. Add vào HashSetBucket được chọn trước khi persistence lifecycle cấp ID.
  3. persist() / flushStrategy sinh ID quyết định ID có mặt lúc nào.
  4. Mìn 1 - Lombok @Data gộp id vào hashhashCode() tính trên mọi field, gồm cả id vừa được cấp: mã số đổi khi vật còn nằm trong tủ.
  5. Mìn 2 - equals gọi getter trên otherĐể so hết field, @Data phải đánh thức other. Nếu đó là proxy: một câu SELECT, hoặc LazyInitializationException khi sổ đã đóng.
  6. contains(entity) sai bucketKhông phải object biến mất; đường lookup đã đổi.
Mức 3 - chọn equality boundary

equals() + hashCode()

Không có một template đúng cho mọi entity model

Có business key bất biến

  • Dùng key có nghĩa nghiệp vụKey phải unique, non-null sớm và không đổi suốt lúc object ở hash collection.
  • Không dùng field mutableTên hiển thị, trạng thái hay generated ID muộn không phù hợp nếu chúng thay đổi.

Chỉ có surrogate ID

  • equals theo id, hashCode hằng sốChỉ bằng nhau khi id non-null; Tag.class.hashCode() không bao giờ đổi qua save.
  • Kiểm lớp bằng instanceofKhông getClass() (proxy là subclass), cũng không Hibernate.getClass() - nó khởi tạo proxy ngay trong equals.
  • Hoặc tránh entity trong hash keyDTO/value object bất biến thường có contract đơn giản hơn.

Phép kiểm: log id, hashCode() và contains() trước add rồi sau save; remove và add lại để chứng minh bucket cũ là nguyên nhân, không phải Hibernate xóa object.

tren mot proxy CHUA khoi tao:
                            trong session   ngoai session
Hibernate.getClass(p)       1 cau SELECT    nem LazyInit...
Hibernate.getClassLazy(p)   khong SQL       nem LazyInit...
o instanceof Tag            khong gi ca     chay binh thuong

Giữ identity ổn định

  • Ưu tiên value/business key bất biếnChọn field tồn tại trước persist và không thay đổi.
  • Viết equality có chủ đíchTest transient, managed, detached, proxy và hai instance cùng DB identity.
  • Đừng để @Data quyết định domain equalityNó gộp id vào hash và đánh thức proxy khi so - hai quả mìn khác nhau từ một annotation.

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

  • Hằng số hash hợp với mọi hash collectionNó dồn mọi phần tử vào một ngăn: vừa vặn với collection cỡ entity, không dành cho Set hàng vạn phần tử.
  • getClass() luôn proxy-safeProxy là subclass nên runtime class khác; bytecode enhancement lại có đặc tính khác nữa.
  • ID bằng nhau là đủ khi ID nullHai transient entity cùng null không mặc nhiên là cùng một thực thể.