12

私は JPA と Hibernate にまったく慣れていませんが (一生懸命勉強しています!)、些細な解決策が見つからないように見える問題に苦しんでいるので、ここにあります。

次のようなエンティティがあります。

@Entity
@Table(name = "mytable1")
public class EntityOne {
  // surrogate key, database generated
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "id")
  private Long id;

  // business key
  @Column(name = "identifier", nullable = false, unique = true)
  private String identifier;

  @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
  @JoinColumn(name = "twoId", nullable = false)
  private EntityTwo two;

  @OneToMany(mappedBy = "entityOne", fetch = FetchType.EAGER, 
    cascade = {CascadeType.ALL}, orphanRemoval = true)
  private Set<EntityThree> resources = new HashSet<>();

  // getters/setters omitted

  @Override
  public int hashCode() {
    // the business key should always be defined (through constructor/query)
    // if this is null the class violates the general hashcode contract 
    // that the integer value returned must always be the same
    Assert.notNull(identifier);
    // a dirty alternative would be:
    // if(identifier==null) return 0;
    return identifier.hashCode();
  }

  @Override
  public boolean equals(Object o) {
    return o instanceof ResourceGroup 
      && ((ResourceGroup) o).identifier.equals(identifier);
  }
}

私のプロジェクトはSpring JPAでセットアップされているのでCrudRepository<EntityOne,Long>、いくつかのメソッドを持つサービスクラスに注入し、@TransactionalJPAとトランザクションのドメイン/サービスパッケージをそれぞれスキャンします。

サービス メソッドの 1 つがリポジトリのfindAll()メソッドを呼び出し、 のリストを返しますEntityOne。のゲッターにアクセスしようとしない限り、すべて正常に動作しますtwo

org.hibernate.LazyInitializationException: could not initialize proxy - no Session

このオブジェクトを初期化すると便利かもしれないと思ったので、取得の種類を遅延型から熱心型に切り替えました。ただし、それを行うと、次のようになります。

java.lang.IllegalArgumentException: [Assertion failed] - this argument is required; it must not be null
    at org.springframework.util.Assert.notNull(Assert.java:112)
    at org.springframework.util.Assert.notNull(Assert.java:123)
    at my.pkg.domain.EntityOne.hashCode(ResourceGroup.java:74)
    at java.util.HashMap.hash(HashMap.java:351)
    at java.util.HashMap.put(HashMap.java:471)
    at java.util.HashSet.add(HashSet.java:217)
    at java.util.AbstractCollection.addAll(AbstractCollection.java:334)
    at org.hibernate.collection.internal.PersistentSet.endRead(PersistentSet.java:346)
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection(CollectionLoadContext.java:243)
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:233)
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:209)
    at org.hibernate.loader.Loader.endCollectionLoad(Loader.java:1149)
//...

Hibernate のソース コードを簡単に調べたところ、ビジネス キーが初期化される前にEntityOneオブジェクトをセットに入れようとしているようです。私の解釈は正しいですか?これを回避する方法はありますか?私は信じられないほど愚かなことをしていますか?

私はあなたの助けに感謝します

編集:ここで理解しようとしているのは、特にJPA と Hibernate に関するベストプラクティスであることを明確にしたいだけです。これが単純な POJO の場合、identifier フィールドを final (実際にはクラス全体を不変にする) にして安全にすることができます。JPAを使用しているため、これを行うことはできません。質問: hashCode 契約に違反していますか? どのように違反していますか? Hibernate はこの違反にどのように対処しますか? 一般的にこれを行うJPA推奨の方法は何ですか? ハッシュベースのコレクションを完全に取り除き、代わりにリストを使用する必要がありますか?

ジョバンニ

4

3 に答える 3

10

いいえ、あなたは愚かなことをしていません。JPA エンティティーに equals と hashCode を実装することは、非常に激しい議論の問題であり、私が知っているすべてのアプローチには重大な欠点があります。あなたが見逃している明白で些細な解決策はありません。

しかし、何らかの理由であまり議論されていないケースを見つけました。Hibernate wikiでは、ビジネス キーを使用することを推奨しており、398 ページの「Hibernate による Java Persistence」(Bauer / King、2007 年、標準的な Hibernate の参考書として広く認識されています) も同じことを推奨しています。しかし、状況によっては、Hibernate は、フィールドが初期化される前にエンティティを Set に追加できるため、ご指摘のとおり、ビジネス キー ベースの hashCode は機能しません。このケースの議論については、Hibernate issue HHH-3799を参照してください。失敗することが予想されるテストケースがある2010 年に追加された問題を示す Hibernate ソース コードで、少なくとも 1 人の Hibernate 開発者がこれをバグと見なし、修正したいと考えていますが、2010 年以降は活動がありません。その問題に投票することを検討してください。

検討できる解決策の 1 つは、エンティティへのすべてのアクセスが同じセッション内で発生するように、セッションの範囲を拡大することです。次にSet<EntityThree>、熱心なフェッチの代わりに遅延フェッチを行うことができ、HHH-3799 の熱心なフェッチの問題を回避できます。私が取り組んできたほとんどのアプリケーションは、デタッチされた状態のオブジェクトを控えめにしか使用しません。エンティティを読み込んで、セッションが終了してからしばらく使用しているようです。それは私が反対することをお勧めするパターンです。Web アプリケーションを作成している場合は、「ビューでセッションを開く」パターンと、これを行う方法のアイデアについて Spring の OpenSessionInViewFilter を参照してください。

ちなみに、ビジネス キーが初期化されていない場合に例外をスローする方法が気に入っています。そうすれば、コーディング エラーをすばやく見つけることができます。私たちのアプリケーションには、HHH-3799 による厄介なバグがあり、not-null アサーションを使用していた場合、開発中に発見できた可能性があります。

于 2013-09-12T23:54:08.080 に答える
0

コレクションにオブジェクトを貼り付ける前に、Hibernate (または任意の JPA プロバイダー) がキーを利用できるようにするなど、これを少し改善する方法を実際に見つけたと思います。このシナリオでは、オブジェクトは適切に初期化され、ビジネス キーが null にならないことを確認できます。

たとえば、クラスEntityTwoは次のようになります。

@Entity
@Table(name = "mytable2")
public class EntityTwo {
  // other code omitted ...
  @OneToMany(mappedBy = "entityTwo", fetch = FetchType.EAGER, 
    cascade = {CascadeType.ALL}, orphanRemoval = true)
  @MapKey(name = "identifier")
  private Map<String, EntityOne> entityOnes = new HashMap<>();
}

この特定のコードはテストしていませんが、他の実際の例があり、JPA docsに従って正常に動作するはずです。この場合、JPA プロバイダーは追い詰められますidentifier。オブジェクトをコレクションに入れる前に、 の値を知る必要があります。さらに、マッピングは JPA プロバイダーによって明示的に処理されるため、オブジェクトのhashCodeandは呼び出されません。equals

これは、物事がモデル化され、相互に関連している方法をツールに明示的に理解させることが大きな利益につながるケースです。

于 2014-01-24T14:58:41.383 に答える