JPAとHibernateを使用する場合、どのように等号とハッシュコードを実装すべきか


103

モデルクラスのイコールとハッシュコードをHibernateに実装するにはどうすればよいですか?一般的な落とし穴は何ですか?ほとんどの場合、デフォルトの実装で十分ですか?ビジネスキーを使う意味はありますか?

遅延フェッチ、ID生成、プロキシなどを考慮に入れると、あらゆる状況で正しく機能させるのはかなり難しいようです。


回答:


74

Hibernateはどのように上書きする際に/の素晴らしく、長い説明があるequals()/ hashCode()ドキュメントを

その要点は、エンティティがの一部であるSet場合、またはインスタンスをデタッチ/アタッチする場合にのみ、心配する必要があるということです。後者はそれほど一般的ではありません。前者は通常、次の方法で処理するのが最適です。

  1. ビジネスキーに基づくequals()/ hashCode()ビジネスキーに基づく-たとえば、オブジェクト(または少なくともセッション)の有効期間中に変更されない属性の一意の組み合わせ。
  2. 上記が不可能である場合、ベースequals()/ hashCode()プライマリキー(設定されている場合)とオブジェクトID / System.identityHashCode()以外の場合。ここで重要なのは、新しいエンティティを追加して永続化した後、セットをリロードする必要があることです。そうしないと、エンティティが現在のと一致しないバケットに割り当てられる可能性があるため、奇妙な動作が発生する可能性があります(最終的には、エラーやデータ破損が発生します)hashCode()

1
「リロード」@ ChssPly76と言うときは、refresh()Set契約に従うエンティティはどのようにして間違ったバケットに入れられますか(十分なハッシュコード実装があると仮定して)。
ノンセクトール、2009年

4
コレクションを更新するか、(所有者)エンティティ全体を再読み込みします(はい)。間違ったバケットに関する限り:a)設定する新しいエンティティを追加しますが、そのIDはまだ設定されていないため、エンティティをバケット#1に配置するidentityHashCodeを使用しています。b)エンティティ(セット内)が永続化され、エンティティにIDが付けられたため、そのIDに基づいてhashCode()を使用しています。上記と異なり、エンティティをバケット#2に配置します。ここで、このエンティティへの参照を別の場所に保持していると想定して、呼び出してみてください。Set.contains(entity)そうすれば戻りfalseます。同じことがget()/ put()/などにも
当てはまり

理にかなっているが、私はそれが彼らのResultTransformersのようにHibernateのソースで使用される参照もののidentityHashCodeを自分自身使ったことがない
非sequitor

1
Hibernateを使用しているときに、この問題が発生する可能性もあります。この問題はまだ解決されていません。
ジョヴァンニボッタ2013

@ ChssPly76 2つのオブジェクトが等しいかどうかを決定するビジネスルールにより、equals / hashcodeメソッドを、オブジェクトの有効期間内に変更される可能性があるプロパティに基づいて設定する必要があります。それは本当に大したことですか?もしそうなら、どうすればそれを回避できますか?
ubiquibacon 2013年

39

受け入れられた答えは正確ではないと思います。

元の質問に答えるには:

ほとんどの場合、デフォルトの実装で十分ですか?

答えはイエスです、ほとんどの場合そうです。

あなただけオーバーライドする必要があるequals()hashcode()実体がで使用される場合にはSet(非常に一般的である)エンティティからデタッチされ、その後に付け直し、(休止状態の珍しい用法である)セッションを休止します。

受け入れられた回答は、いずれかの条件が真の場合、メソッドをオーバーライドする必要があることを示しています。


これは私の観察と一致しており、その理由を調べる時間です。
VlastimilOvčáčík2016

「エンティティがセットで使用される場合は、equals()とhashcode()をオーバーライドするだけで十分です」は、一部のフィールドがオブジェクトを識別する場合に完全に十分であるため、Object.equals()に依存して識別したくない場合オブジェクト。
davidxxx 2017

17

最高equals/ hashCodeあなたが使用している場合、実装は、独自のビジネスキーを

ビジネスキーは、すべてのエンティティの状態遷移(一過性、接続、分離、削除)で一貫している必要があります。そのため、IDが等しいかどうかに依存できません。

別のオプションは、アプリケーションロジックによって割り当てられたUUID識別子の使用に切り替えることです。この方法では、エンティティーがフラッシュされる前にIDが割り当てられるため、equals/にUUIDを使用できますhashCode

equalsおよびのエンティティ識別子を使用することもできますが、エンティティのhashCode値がすべてのエンティティの状態遷移で一貫していることを確認するためにhashCode、常に同じhashCode値を返す必要があります。このトピックの詳細については、この投稿を確認してください。


uuidアプローチの場合は+1。それをに入れて、BaseEntityその問題について二度と考えないでください。db側には少しスペースが必要ですが、快適さのためにその価格を支払う方が良いでしょう:)
Martin Frey

12

エンティティが遅延読み込みで読み込まれた場合、それは基本型のインスタンスではなく、javassistによって動的に生成されたサブタイプであるため、同じクラス型のチェックは失敗するため、使用しないでください。

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

代わりに:

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

これは、Javaプラクティスでのequalsの実装で説明されているように、良いプラクティスでもあります。

同じ理由で、フィールドに直接アクセスすると、機能せず、基になる値の代わりにnullを返す場合があるため、プロパティで比較を使用せず、ゲッターを使用します。


1
これは、私の状況では機能しなかった具象クラスのオブジェクトを比較する場合に機能します。スーパークラスのオブジェクトを比較していました。この場合、このコードが機能しました。obj1.getClass()。isInstance(obj2)
Tad

6

ええ、それは難しいです。私のプロジェクトでは、equalsとhashCodeはどちらもオブジェクトのIDに依存しています。このソリューションの問題は、IDがデータベースによって生成されるため、オブジェクトがまだ永続化されていない場合はどちらも機能しないことです。私の場合、ほとんどすべての場合でオブジェクトはすぐに永続化されるので、それは許容できます。それ以外は、それは素晴らしい働きをし、実装が簡単です。


私たちが行ったのは、IDが生成されていない場合にオブジェクトIDを使用することです
キャシーヴァンストーン

2
ここでの問題は、オブジェクトを永続化すると、ハッシュコードが変更されることです。オブジェクトが既にハッシュベースのデータ構造の一部である場合、それは大きな有害な結果をもたらす可能性があります。したがって、オブジェクトIDを使用して終了する場合は、オブジェクトが完全に解放されるまでobj idを引き続き使用することをお勧めします(または、ハッシュベースの構造からオブジェクトを削除し、永続化してから再度追加します)。個人的には、idを使用せず、オブジェクトの不変のプロパティに基づいてハッシュを作成するのが最善だと思います。
ケビンの日、

1

Hibernate 5.2のドキュメントには、状況によっては、hashCodeとequalsをまったく実装したくない場合があると記載されています。

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

一般に、同じセッションからロードされた2つのオブジェクトは、データベース内で等しい場合(hashCodeとequalsを実装せずに)等しくなります。

2つ以上のセッションを使用している場合は、複雑になります。この場合、2つのオブジェクトが等しいかどうかは、equalsメソッドの実装によって異なります。

さらに、equalsメソッドが、オブジェクトを初めて永続化するときにのみ生成されるIDを比較している場合は、問題が発生します。equalsが呼び出されたとき、まだ存在していない可能性があります。


0

ここにとても良い記事があります:https//docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

記事の重要な行を引用すると:

ビジネスキーの等価性を使用して、equals()およびhashCode()を実装することをお勧めします。ビジネスキーの等価性とは、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、その契約を確実に満たしてください:-

  • 対称
  • 反射
  • 推移的
  • 一貫した
  • NULL以外

オーバーライドhashCodeは、その契約がequals実装に依存しているためです。

Joshua Bloch(コレクションフレームワークの設計者)は、これらのルールに従うよう強く求めました。

  • 項目9:equalsをオーバーライドするときは常にhashCodeをオーバーライドする

これらの契約に従わないと、重大な意図しない影響があります。たとえば、一般契約が履行されていないため、List#contains(Object o)誤ったboolean値が返される場合があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.