Cách triển khai bằng và mã băm khi sử dụng JPA và Hibernate


103

Bằng và mã băm của lớp mô hình nên được triển khai như thế nào trong Hibernate? Cạm bẫy thường gặp là gì? Việc triển khai mặc định có đủ tốt cho hầu hết các trường hợp không? Có bất kỳ ý nghĩa nào để sử dụng khóa kinh doanh?

Đối với tôi, dường như khá khó để làm cho nó hoạt động đúng trong mọi tình huống, khi tính đến tính năng tìm nạp lười biếng, tạo id, proxy, v.v.


Xem thêm stackoverflow.com/a/39827962/548473 (triển khai spring-data-
jpa

Câu trả lời:


74

Hibernate có một mô tả dài và đẹp về thời điểm / cách ghi đè equals()/ hashCode()trong tài liệu

Ý chính của nó là bạn chỉ cần lo lắng về nó nếu thực thể của bạn sẽ là một phần của một Sethoặc nếu bạn sắp tách / gắn các thể hiện của nó. Sau này không phải là phổ biến. Trước đây thường được xử lý tốt nhất thông qua:

  1. Dựa trên equals()/ hashCode()trên một khóa nghiệp vụ - ví dụ: một tổ hợp các thuộc tính duy nhất sẽ không thay đổi trong suốt thời gian tồn tại của đối tượng (hoặc ít nhất là phiên).
  2. Nếu điều trên là không thể, hãy căn cứ equals()/ hashCode()dựa trên khóa chính NẾU nó được đặt và nhận dạng đối tượng / System.identityHashCode()nếu không. Phần quan trọng ở đây là bạn cần tải lại Bộ của mình sau khi thực thể mới đã được thêm vào nó và tồn tại; nếu không, bạn có thể gặp phải hành vi lạ (cuối cùng dẫn đến lỗi và / hoặc hỏng dữ liệu) bởi vì thực thể của bạn có thể được phân bổ vào một nhóm không khớp với hiện tại của nó hashCode().

1
Khi bạn nói "tải lại" @ ChssPly76 nghĩa là bạn đang làm gì refresh()? Làm thế nào để thực thể của bạn, thực thể tuân theo Sethợp đồng kết thúc trong nhóm sai (giả sử bạn có triển khai mã băm đủ tốt).
non sequitor 28/10/09

4
Làm mới bộ sưu tập hoặc tải lại toàn bộ thực thể (chủ sở hữu), có. Đối với trường hợp nhóm sai: a) bạn thêm thực thể mới để đặt, id của nó chưa được đặt, vì vậy bạn đang sử dụng Mã định danh để đặt đối tượng của bạn vào nhóm số 1. b) thực thể của bạn (trong tập hợp) vẫn tồn tại, nó hiện có một id và do đó bạn đang sử dụng hashCode () dựa trên id đó. Nó khác với ở trên và sẽ đặt thực thể của bạn trong nhóm số 2. Bây giờ, giả sử bạn giữ một tham chiếu đến thực thể này ở nơi khác, hãy thử gọi Set.contains(entity)và bạn sẽ nhận lại được false. Cũng vậy với get () / đặt () / etc ...
ChssPly76

Làm cho tinh thần nhưng không bao giờ sử dụng identityHashCode bản thân mình mặc dù tôi thấy nó được sử dụng trong nguồn Hibernate như trong ResultTransformers của họ
không sequitor

1
Khi sử dụng Hibernate, bạn cũng có thể gặp phải vấn đề này mà tôi vẫn chưa tìm ra giải pháp.
Giovanni Botta,

@ ChssPly76 Do các quy tắc nghiệp vụ xác định xem hai đối tượng có bằng nhau hay không, tôi sẽ cần căn cứ các phương thức bằng / mã băm của mình trên các thuộc tính có thể thay đổi trong vòng đời của đối tượng. Đó thực sự là một vấn đề lớn? Nếu vậy làm thế nào để tôi vượt qua nó?
ubiquibacon

39

Tôi không nghĩ rằng câu trả lời được chấp nhận là chính xác.

Để trả lời câu hỏi ban đầu:

Việc triển khai mặc định có đủ tốt cho hầu hết các trường hợp không?

Câu trả lời là có, trong hầu hết các trường hợp là như vậy.

Bạn chỉ cần ghi đè equals()hashcode()nếu thực thể sẽ được sử dụng trong một ANDSet (rất phổ biến) thực thể sẽ được tách ra khỏi và sau đó được gắn lại vào phiên ngủ đông (đây là cách sử dụng ngủ đông không phổ biến).

Câu trả lời được chấp nhận chỉ ra rằng các phương thức cần được ghi đè nếu một trong hai điều kiện là đúng.


Điều này phù hợp với quan sát của tôi, thời gian để tìm hiểu tại sao .
Vlastimil Ovčáčík

"Bạn chỉ cần ghi đè bằng () và mã băm () nếu thực thể sẽ được sử dụng trong Tập hợp" là hoàn toàn đủ nếu một số trường xác định một đối tượng và vì vậy bạn không muốn dựa vào Object.equals () để xác định các đối tượng.
davidxxx 11/1117

17

Cách triển khai equals/ tốt nhất hashCodelà khi bạn sử dụng một khóa doanh nghiệp duy nhất .

Khóa nghiệp vụ phải nhất quán trên tất cả các chuyển đổi trạng thái thực thể (tạm thời, đính kèm, tách rời, loại bỏ), đó là lý do tại sao bạn không thể dựa vào id để bình đẳng.

Một tùy chọn khác là chuyển sang sử dụng số nhận dạng UUID , được gán bởi logic ứng dụng. Bằng cách này, bạn có thể sử dụng UUID cho equals/ hashCodevì id được chỉ định trước khi thực thể bị xóa.

Bạn thậm chí có thể sử dụng mã định danh thực thể cho equalshashCode, nhưng điều đó yêu cầu bạn phải luôn trả về cùng một hashCodegiá trị để bạn đảm bảo rằng giá trị Mã băm thực thể nhất quán qua tất cả các chuyển đổi trạng thái thực thể. Kiểm tra bài đăng này để biết thêm về chủ đề này .


+1 cho cách tiếp cận uuid. Đặt điều đó vào một BaseEntityvà không bao giờ nghĩ lại về vấn đề đó. Phải mất một chút không gian ở phía db nhưng giá mà bạn phải trả tốt hơn cho sự thoải mái :)
Martin Frey

12

Khi một thực thể được tải thông qua tải chậm, nó không phải là một thể hiện của kiểu cơ sở, mà là một kiểu con được tạo động do javassist tạo ra, do đó, việc kiểm tra cùng một loại lớp sẽ không thành công, vì vậy không sử dụng:

if (getClass() != that.getClass()) return false;

thay vào đó sử dụng:

if (!(otherObject instanceof Unit)) return false;

đây cũng là một phương pháp hay, như đã giải thích về Triển khai bằng trong Thực tiễn Java .

vì lý do tương tự, việc truy cập trực tiếp vào các trường, có thể không hoạt động và trả về null, thay vì giá trị cơ bản, vì vậy không sử dụng so sánh trên các thuộc tính, mà hãy sử dụng getters, vì chúng có thể kích hoạt để tải các giá trị cơ bản.


1
Điều này hoạt động nếu bạn đang so sánh các đối tượng của các lớp cụ thể, không hoạt động trong tình huống của tôi. Tôi đã so sánh các đối tượng của lớp siêu, trong trường hợp này mã này làm việc cho tôi:. Obj1.getClass () isinstance (obj2)
Tad

6

Phải, thật khó. Trong dự án của tôi, cả hai mã bằng và mã băm đều dựa vào id của đối tượng. Vấn đề của giải pháp này là cả hai đều không hoạt động nếu đối tượng vẫn chưa được duy trì, vì id được tạo bởi cơ sở dữ liệu. Trong trường hợp của tôi, điều đó có thể chấp nhận được vì trong hầu hết các trường hợp, các đối tượng vẫn tồn tại ngay lập tức. Ngoài ra, nó hoạt động tuyệt vời và dễ thực hiện.


Những gì tôi nghĩ chúng tôi đã làm là sử dụng nhận dạng đối tượng trong trường hợp id chưa được tạo ra
Kathy Van Đá

2
vấn đề ở đây là nếu bạn giữ nguyên đối tượng, mã băm của bạn sẽ thay đổi. Điều đó có thể gây ra những kết quả bất lợi lớn nếu đối tượng đã là một phần của cấu trúc dữ liệu dựa trên băm. Vì vậy, nếu bạn kết thúc bằng cách sử dụng nhận dạng đối tượng, tốt hơn bạn nên tiếp tục sử dụng id obj cho đến khi đối tượng được giải phóng hoàn toàn (hoặc xóa đối tượng khỏi bất kỳ cấu trúc dựa trên băm nào, duy trì, sau đó thêm lại vào). Cá nhân tôi nghĩ rằng tốt nhất là không sử dụng id và dựa trên băm dựa trên các thuộc tính bất biến của đối tượng.
Ngày Kevin

1

Trong tài liệu của Hibernate 5.2, nó nói rằng bạn có thể không muốn triển khai hashCode và equals - tùy thuộc vào tình huống của bạn.

https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#mapping-model-pojo-equalshashcode

Nói chung, hai đối tượng được tải từ cùng một phiên sẽ bằng nhau nếu chúng bằng nhau trong cơ sở dữ liệu (không triển khai mã băm và bằng).

Nó sẽ phức tạp nếu bạn đang sử dụng hai hoặc nhiều phiên. Trong trường hợp này, sự bằng nhau của hai đối tượng phụ thuộc vào việc triển khai phương thức bằng của bạn.

Hơn nữa, bạn sẽ gặp rắc rối nếu phương thức bằng của bạn đang so sánh các ID chỉ được tạo trong khi duy trì một đối tượng lần đầu tiên. Chúng có thể chưa ở đó khi các dấu bằng được gọi.


0

Có một bài viết rất hay ở đây: https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persists-classes-equalshashcode.html

Trích dẫn một dòng quan trọng từ bài báo:

Chúng tôi khuyên bạn nên triển khai equals () và hashCode () bằng cách sử dụng Business key bình đẳng. Bình đẳng khóa nghiệp vụ có nghĩa là phương thức equals () chỉ so sánh các thuộc tính tạo thành khóa nghiệp vụ, một khóa sẽ xác định phiên bản của chúng ta trong thế giới thực (khóa ứng viên tự nhiên):

Nói một cách đơn giản

public class Cat {

...
public boolean equals(Object other) {
    //Basic test / class cast
    return this.catId==other.catId;
}

public int hashCode() {
    int result;

    return 3*this.catId; //any primenumber 
}

}

0

Nếu bạn tình cờ ghi đè equals, hãy đảm bảo bạn thực hiện các hợp đồng của nó: -

  • ĐỐI DIỆN
  • PHẢN XẠ
  • BẮC CẦU
  • THÍCH HỢP
  • KHÔNG ĐẦY ĐỦ

Và ghi đè hashCode, vì hợp đồng của nó dựa vào equalsviệc thực hiện.

Joshua Bloch (nhà thiết kế khuôn khổ Bộ sưu tập) đã thúc giục mạnh mẽ những quy tắc này được tuân theo.

  • mục 9: Luôn ghi đè Mã băm khi bạn ghi đè bằng

Sẽ có những ảnh hưởng nghiêm trọng ngoài ý muốn khi bạn không tuân theo các hợp đồng này. Ví dụ List#contains(Object o)có thể trả lại booleangiá trị sai khi hợp đồng chung không được thực hiện.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.