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
HashSet.add(entity)Ủy quyền cho backingHashMap.put(entity, PRESENT).hash(entity)Gọientity.hashCode(), trộn bit rồi chọn bucket theo capacity.putVal(...)Đặt node vào bucket ở thời điểm add; Set không theo dõi field thay đổi.getNode(key)contains()tính lại hash hiện tại và chỉ tìm trong bucket tương ứng.- 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
- Construct entityDatabase-generated identifier thường đang
null. - Add vào
HashSetBucket được chọn trước khi persistence lifecycle cấp ID. persist()/ flushStrategy sinh ID quyết định ID có mặt lúc nào.- Mìn 1 - Lombok
@Datagộpidvà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ủ. - Mìn 2 -
equalsgọi getter trênotherĐể so hết field,@Dataphải đánh thứcother. Nếu đó là proxy: một câu SELECT, hoặcLazyInitializationExceptionkhi sổ đã đóng. 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
equalstheo id,hashCodehằ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ônggetClass()(proxy là subclass), cũng khôngHibernate.getClass()- nó khởi tạo proxy ngay trongequals. - 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 thuongGiữ 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 để
@Dataquyế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ể.