モデルクラスのequalsとハッシュコードをHibernateでどのように実装する必要がありますか?よくある落とし穴は何ですか?ほとんどの場合、デフォルトの実装で十分ですか?ビジネスキーを使用する意味はありますか?
遅延フェッチ、ID生成、プロキシなどを考慮に入れると、あらゆる状況で正しく機能させるのはかなり難しいように思われます。
Hibernateには、いつ/どのようにオーバーライドするかequals()
/ドキュメントhashCode()
に長い説明があります。
Set
その要点は、エンティティがの一部になる場合、またはインスタンスをデタッチ/アタッチする場合にのみ心配する必要があるということです。後者はそれほど一般的ではありません。前者は通常、次の方法で処理するのが最適です。
equals()
/hashCode()
ビジネスキーに基づく-たとえば、オブジェクト(または少なくともセッション)の存続期間中に変更されない属性の一意の組み合わせ。equals()
主hashCode()
キーが設定されている場合は主キーに基づいて、System.identityHashCode()
それ以外の場合はオブジェクトIDに基づいてください。ここで重要なのは、新しいエンティティが追加されて永続化された後、セットをリロードする必要があるということです。そうしないと、エンティティが現在のと一致しないバケットに割り当てられる可能性があるため、奇妙な動作が発生する可能性があります(最終的にエラーやデータ破損が発生します)hashCode()
。受け入れられた答えは正確ではないと思います。
元の質問に答えるには:
ほとんどの場合、デフォルトの実装で十分ですか?
答えはイエスです。ほとんどの場合、そうです。
オーバーライドする必要があるのはequals()
、エンティティが(非常に一般的です)でhashcode()
使用され、エンティティが休止状態のセッションから切り離され、その後再接続される場合(休止状態の一般的な使用法ではありません)のみです。Set
受け入れられた回答は、いずれかの条件が真の場合、メソッドをオーバーライドする必要があることを示しています。
最適なequals
実装hashCode
は、次のように一意のビジネスキーまたは自然識別子を使用する場合です。
@Entity
public class Company {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(unique = true, updatable = false)
private String name;
@Override
public int hashCode() {
HashCodeBuilder hcb = new HashCodeBuilder();
hcb.append(name);
return hcb.toHashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Company)) {
return false;
}
Company that = (Company) obj;
EqualsBuilder eb = new EqualsBuilder();
eb.append(name, that.name);
return eb.isEquals();
}
}
ビジネスキーは、すべてのエンティティ状態遷移(一時的、接続済み、分離済み、削除済み)で一貫している必要があります。そのため、同等性をIDに依存することはできません。
もう1つのオプションは、アプリケーションロジックによって割り当てられたUUID識別子の使用に切り替えることです。このように、エンティティがフラッシュされる前にIDが割り当てられるため、 equals
/にUUIDを使用できます。hashCode
equals
とのエンティティ識別子を使用することもできますが、次のように、エンティティのhashCode値がすべてのエンティティの状態遷移で一貫していることを確認するためにhashCode
、常に同じ[値を返す必要があります。hashCode
@Entity(name = "Post")
@Table(name = "post")
public class Post implements Identifiable<Long> {
@Id
@GeneratedValue
private Long id;
private String title;
public Post() {}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Post))
return false;
Post other = (Post) o;
return id != null &&
id.equals(other.getId());
}
@Override
public int hashCode() {
return getClass().hashCode();
}
//Getters and setters omitted for brevity
}
エンティティが遅延読み込みによって読み込まれる場合、それは基本型のインスタンスではなく、javassistによって生成された動的に生成されたサブ型であるため、同じクラス型のチェックは失敗するため、使用しないでください。
if (getClass() != that.getClass()) return false;
代わりに以下を使用してください:
if (!(otherObject instanceof Unit)) return false;
これは、Javaプラクティスでのequalsの実装で説明されているように、良いプラクティスでもあります。
同じ理由で、フィールドに直接アクセスすると、基になる値の代わりにnullが返され、機能しない場合があります。したがって、プロパティの比較は使用せず、基になる値の読み込みをトリガーする可能性があるため、ゲッターを使用してください。
ええ、それは難しいです。私のプロジェクトでは、equalsとhashCodeはどちらもオブジェクトのIDに依存しています。このソリューションの問題は、IDがデータベースによって生成されるため、オブジェクトがまだ永続化されていない場合、どちらも機能しないことです。私の場合、ほとんどすべての場合、オブジェクトはすぐに永続化されるため、これは許容範囲です。それ以外は、うまく機能し、実装も簡単です。
Hibernate 5.2のドキュメントでは、状況によっては、hashCodeとequalsを実装したくない場合があると記載されています。
一般に、同じセッションからロードされた2つのオブジェクトは、データベース内で等しい場合(hashCodeとequalsを実装しない場合)は等しくなります。
2つ以上のセッションを使用している場合は、複雑になります。この場合、2つのオブジェクトが等しいかどうかは、equalsメソッドの実装によって異なります。
さらに、equalsメソッドがオブジェクトを初めて永続化するときにのみ生成されるIDを比較している場合は、問題が発生します。equalsが呼び出されたとき、それらはまだそこにない可能性があります。
ここに非常に素晴らしい記事があります: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
}
}
たまたまオーバーライドした場合equals
は、必ずその契約を履行してください。-
hashCode
そして、そのコントラクトは実装に依存しているため、オーバーライドしますequals
。
Joshua Bloch(コレクションフレームワークの設計者)は、これらのルールに従うよう強く求めました。
これらの契約に従わないと、意図しない重大な影響があります。たとえば、一般契約が履行されていList#contains(Object o)
ないために間違った値を返す場合があります。boolean