ควรใช้งานเท่ากับและแฮชโค้ดอย่างไรเมื่อใช้ JPA และ Hibernate


103

คลาสโมเดลควรมีการใช้งานเท่ากับและแฮชโค้ดในไฮเบอร์เนตอย่างไร อะไรคือข้อผิดพลาดที่พบบ่อย? การใช้งานเริ่มต้นดีเพียงพอสำหรับกรณีส่วนใหญ่หรือไม่ การใช้คีย์ธุรกิจมีความหมายหรือไม่?

สำหรับฉันแล้วดูเหมือนว่ามันค่อนข้างยากที่จะทำให้มันทำงานได้ถูกต้องในทุกสถานการณ์เมื่อคำนึงถึงการดึงข้อมูลการสร้างรหัสพร็อกซีและอื่น ๆ


ดูstackoverflow.com/a/39827962/548473 (การใช้งาน spring-data-jpa)
Grigory Kislin

คำตอบ:


74

Hibernate มีคำอธิบายที่ดีและยาวเกี่ยวกับเวลา / วิธีการลบล้างequals()/ hashCode()ในเอกสาร

ความสำคัญของมันคือคุณต้องกังวลเกี่ยวกับเรื่องนี้ว่าเอนทิตีของคุณจะเป็นส่วนหนึ่งของSetหรือถ้าคุณกำลังจะถอด / แนบอินสแตนซ์ หลังไม่ธรรมดานั้น อดีตมักจะจัดการได้ดีที่สุดผ่าน:

  1. ฐานequals()/hashCode()บนคีย์ธุรกิจ - เช่นชุดค่าผสมเฉพาะของแอตทริบิวต์ที่จะไม่เปลี่ยนแปลงในช่วงอายุการใช้งานของออบเจ็กต์ (หรืออย่างน้อยเซสชัน)
  2. หากเป็นไปไม่ได้ข้างต้นให้ใช้พื้นฐานequals()/ hashCode()บนคีย์หลักหากมีการตั้งค่าและข้อมูลประจำตัวของวัตถุ / System.identityHashCode()มิฉะนั้น สำคัญส่วนหนึ่งที่นี่เป็นที่ที่คุณต้องโหลดชุดของคุณหลังจากที่องค์กรใหม่ได้รับการเพิ่มและยืนยัน; มิฉะนั้นคุณอาจท้ายด้วยพฤติกรรมแปลก (ที่สุดส่งผลให้เกิดข้อผิดพลาดและ / หรือความเสียหายของข้อมูล) hashCode()เพราะนิติบุคคลของคุณอาจจะจัดสรรให้กับถังที่ไม่ตรงกับปัจจุบัน

1
เมื่อคุณพูดว่า "โหลดซ้ำ" @ ChssPly76 คุณหมายถึงทำrefresh()? นิติบุคคลของคุณซึ่งปฏิบัติตามSetสัญญาจะลงเอยด้วยถังที่ไม่ถูกต้องได้อย่างไร (สมมติว่าคุณมีการติดตั้งแฮชโค้ดที่ดีเพียงพอ)
non sequitor

4
รีเฟรชคอลเลกชันหรือโหลดเอนทิตี (เจ้าของ) ทั้งหมดใหม่ใช่ หากที่เก็บข้อมูลผิดพลาด: ก) คุณเพิ่มเอนทิตีใหม่เพื่อตั้งค่ารหัสของมันยังไม่ได้ตั้งค่าดังนั้นคุณจึงใช้ identityHashCode ซึ่งจะทำให้เอนทิตีของคุณอยู่ในที่เก็บข้อมูล # 1 b) เอนทิตีของคุณ (ภายในชุด) ยังคงอยู่ตอนนี้มี id ดังนั้นคุณจึงใช้ hashCode () ตาม id นั้น ซึ่งแตกต่างจากด้านบนและจะทำให้เอนทิตีของคุณอยู่ในที่เก็บข้อมูล # 2 ตอนนี้สมมติว่าคุณถืออ้างอิงถึงนิติบุคคลนี้ที่อื่น ๆ ลองโทรและคุณจะได้รับกลับมาSet.contains(entity) falseเช่นเดียวกันสำหรับ get () / put () / etc ...
ChssPly76

มีเหตุผล แต่ไม่เคยใช้ identityHashCode ด้วยตัวเองแม้ว่าฉันจะเห็นว่ามันใช้ในแหล่ง Hibernate เหมือนใน ResultTransformers
ไม่ใช่ sequitor

1
เมื่อใช้ไฮเบอร์เนตคุณอาจพบปัญหานี้ซึ่งฉันยังไม่พบวิธีแก้ไข
Giovanni Botta

@ ChssPly76 เนื่องจากกฎทางธุรกิจที่กำหนดว่าวัตถุสองชิ้นเท่ากันหรือไม่ฉันจะต้องใช้วิธีการเท่ากับ / แฮชโค้ดบนคุณสมบัติที่อาจเปลี่ยนแปลงภายในอายุการใช้งานของวัตถุ เป็นเรื่องใหญ่จริงหรือ? ถ้าเป็นเช่นนั้นฉันจะหลีกเลี่ยงมันได้อย่างไร
ubiquibacon

39

ฉันไม่คิดว่าคำตอบที่ยอมรับนั้นถูกต้อง

ในการตอบคำถามเดิม:

การใช้งานเริ่มต้นดีเพียงพอสำหรับกรณีส่วนใหญ่หรือไม่

คำตอบคือใช่ในกรณีส่วนใหญ่คือ

คุณเพียงแค่ต้องลบล้างequals()และhashcode()หากเอนทิตีจะถูกใช้ใน a Set(ซึ่งเป็นเรื่องธรรมดามาก) และเอนทิตีนั้นจะถูกแยกออกจากนั้นและต่อมาจะเชื่อมต่ออีกครั้งกับเซสชันไฮเบอร์เนต (ซึ่งเป็นการใช้ไฮเบอร์เนตที่ผิดปกติ)

คำตอบที่ได้รับการยอมรับระบุว่าเมธอดจำเป็นต้องถูกลบล้างหากเงื่อนไขใดเงื่อนไขหนึ่งเป็นจริง


สอดคล้องนี้กับการสังเกตของฉันเวลาที่จะหาว่าทำไม
Vlastimil Ovčáčík

"คุณจะต้องแทนที่ equals () และ hashcode () เท่านั้นหากจะใช้เอนทิตีในชุด" ก็เพียงพอแล้วหากบางฟิลด์ระบุวัตถุดังนั้นคุณจึงไม่ต้องการพึ่งพา Object.equals () เพื่อระบุ วัตถุ
davidxxx

17

ที่ดีที่สุดequals/ hashCodeการดำเนินงานคือเมื่อคุณใช้คีย์ธุรกิจที่ไม่ซ้ำกัน

คีย์ธุรกิจควรสอดคล้องกันในการเปลี่ยนสถานะเอนทิตีทั้งหมด(ชั่วคราว, แนบ, แยก, ลบ) นั่นเป็นเหตุผลที่คุณไม่สามารถพึ่งพา id เพื่อความเท่าเทียมกันได้

อีกทางเลือกหนึ่งคือการเปลี่ยนไปใช้ตัวระบุ UUIDซึ่งกำหนดโดยตรรกะของแอปพลิเคชัน ด้วยวิธีนี้คุณสามารถใช้ UUID สำหรับequals/ hashCodeเนื่องจาก id ถูกกำหนดก่อนที่เอนทิตีจะถูกล้าง

คุณยังสามารถใช้ตัวระบุเอนทิตีสำหรับequalsและhashCodeแต่ต้องให้คุณคืนhashCodeค่าเดียวกันเสมอเพื่อให้แน่ใจว่าค่าแฮชโค้ดเอนทิตีสอดคล้องกันในการเปลี่ยนสถานะเอนทิตีทั้งหมด ดูโพสต์นี้สำหรับข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อนี้


+1 สำหรับแนวทาง uuid ใส่สิ่งนั้นลงในBaseEntityและไม่คิดถึงปัญหานั้นอีก ใช้พื้นที่ด้านฐานข้อมูลเล็กน้อย แต่ราคานั้นคุณควรจ่ายเพื่อความสะดวกสบาย :)
Martin Frey

12

เมื่อเอนทิตีถูกโหลดผ่านการโหลดแบบขี้เกียจมันไม่ใช่อินสแตนซ์ของประเภทพื้นฐาน แต่เป็นประเภทย่อยที่สร้างขึ้นแบบไดนามิกที่สร้างขึ้นโดย javassist ดังนั้นการตรวจสอบประเภทคลาสเดียวกันจะล้มเหลวดังนั้นอย่าใช้:

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

ใช้แทน:

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

ซึ่งยังเป็นวิธีที่ดีตามที่อธิบายไว้ในการดำเนินการเท่ากับใน Java การปฏิบัติ

ด้วยเหตุผลเดียวกันการเข้าถึงฟิลด์โดยตรงอาจไม่ทำงานและส่งคืนค่าว่างแทนค่าพื้นฐานดังนั้นอย่าใช้การเปรียบเทียบกับคุณสมบัติ แต่ใช้ getters เนื่องจากอาจทำให้เกิดการโหลดค่าพื้นฐาน


1
สิ่งนี้ใช้ได้ผลถ้าคุณกำลังเปรียบเทียบวัตถุของคลาสคอนกรีตซึ่งไม่ได้ผลในสถานการณ์ของฉัน ฉันกำลังเปรียบเทียบออบเจ็กต์ของคลาสระดับสูงซึ่งในกรณีนี้รหัสนี้ใช้ได้กับฉัน: obj1.getClass () isInstance (obj2)
Tad

6

ใช่มันยาก ในโปรเจ็กต์ของฉันเท่ากับและ hashCode ทั้งสองอาศัย id ของอ็อบเจ็กต์ ปัญหาของวิธีแก้ปัญหานี้คือทั้งสองอย่างไม่ทำงานหากวัตถุยังไม่ถูกคงอยู่เนื่องจาก id ถูกสร้างขึ้นโดยฐานข้อมูล ในกรณีของฉันมันสามารถยอมรับได้เนื่องจากในเกือบทุกกรณีวัตถุยังคงอยู่ในทันที นอกจากนั้นยังใช้งานได้ดีและใช้งานง่าย


สิ่งที่ฉันคิดว่าเราทำคือการใช้เอกลักษณ์ของวัตถุในกรณีที่ไม่ได้สร้างรหัส
Kathy Van Stone

2
ปัญหาก็คือถ้าคุณยังคงมีวัตถุอยู่แฮชโค้ดของคุณจะเปลี่ยนไป ซึ่งอาจมีผลลัพธ์ที่เป็นอันตรายอย่างมากหากวัตถุนั้นเป็นส่วนหนึ่งของโครงสร้างข้อมูลที่ใช้แฮชอยู่แล้ว ดังนั้นหากคุณใช้ข้อมูลประจำตัววัตถุคุณควรใช้ obj id ต่อไปจนกว่าวัตถุจะถูกปลดปล่อยอย่างสมบูรณ์ (หรือลบวัตถุออกจากโครงสร้างที่ใช้แฮชใด ๆ ให้คงอยู่จากนั้นเพิ่มกลับเข้าไปใหม่) โดยส่วนตัวแล้วฉันคิดว่ามันจะเป็นการดีที่สุดที่จะไม่ใช้ id และตั้งค่าแฮชตามคุณสมบัติที่ไม่เปลี่ยนรูปของวัตถุ
Kevin Day

1

ในเอกสารของ Hibernate 5.2 ระบุว่าคุณอาจไม่ต้องการใช้ hashCode และเท่ากับเลย - ขึ้นอยู่กับสถานการณ์ของคุณ

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

โดยทั่วไปวัตถุสองชิ้นที่โหลดจากเซสชันเดียวกันจะเท่ากันหากมีค่าเท่ากันในฐานข้อมูล (โดยไม่ต้องใช้ hashCode และเท่ากับ)

จะซับซ้อนหากคุณใช้สองเซสชันขึ้นไป ในกรณีนี้ความเท่าเทียมกันของวัตถุสองชิ้นขึ้นอยู่กับการใช้วิธีการเท่ากับของคุณ

นอกจากนี้คุณจะประสบปัญหาหากวิธีการเท่ากับของคุณกำลังเปรียบเทียบ ID ที่สร้างขึ้นในขณะที่ยังคงมีวัตถุเป็นครั้งแรก พวกเขาอาจยังไม่อยู่ที่นั่นเมื่อเรียกเท่ากับ


0

มีบทความดีๆอยู่ที่นี่: https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

อ้างถึงบรรทัดสำคัญจากบทความ:

เราขอแนะนำให้ใช้ equals () และ hashCode () โดยใช้ Business key equivalent ความเท่าเทียมกันของคีย์ธุรกิจหมายความว่าวิธีการ equals () จะเปรียบเทียบเฉพาะคุณสมบัติที่เป็นคีย์ธุรกิจซึ่งเป็นคีย์ที่ระบุอินสแตนซ์ของเราในโลกแห่งความเป็นจริง (คีย์ตัวเลือกตามธรรมชาติ):

พูดง่ายๆคือ

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

หากคุณเกิดขึ้นเพื่อลบล้างequalsตรวจสอบให้แน่ใจว่าคุณปฏิบัติตามสัญญา: -

  • สมมาตร
  • สะท้อนกลับ
  • เปลี่ยน
  • สม่ำเสมอ
  • ไม่เป็นโมฆะ

และลบล้างhashCodeเนื่องจากสัญญาขึ้นอยู่กับequalsการใช้งาน

Joshua Bloch (ผู้ออกแบบกรอบงาน Collection) ขอเรียกร้องให้ปฏิบัติตามกฎเหล่านี้อย่างจริงจัง

  • ข้อ 9: แทนที่ hashCode เสมอเมื่อคุณลบล้างเท่ากับ

มีผลร้ายแรงโดยไม่ได้ตั้งใจเมื่อคุณไม่ทำตามสัญญาเหล่านี้ ตัวอย่างเช่นList#contains(Object o)อาจส่งคืนbooleanค่าที่ไม่ถูกต้องเนื่องจากไม่ปฏิบัติตามสัญญาทั่วไป

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.