2013-07-18 15 views
9

私はJPAとHibernateの新機能です(私は勉強しています!)、私は些細な解決策を見つけることができないという問題を抱えています。だからここにある。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); 
    } 
} 

私のプロジェクトは春JPAに設定されているので、私は私のいくつかの@Transactionalメソッドを持っていると私はスキャンサービスクラスでCrudRepository<EntityOne,Long>を注入しているが私のJPAドメインとサービスパッケージとトランザクション。

サービスメソッドの1つが、リポジトリのfindAll()メソッドを呼び出し、EntityOneのリストを返します。

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

は、私はこのオブジェクトを初期化していると役に立つかもしれないと思ったので、私は熱心に怠惰からフェッチタイプを切り替える:私は明らかにスローされ、 twoのためのゲッターにアクセスしようとしない限り、すべてが正常に動作します。しかし、私は次のことを得ることを行った場合:

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オブジェクトを置くしようとしているように見えます。私の解釈は正しいのですか?これを回避する方法はありますか?私は何か信じられないほどばかなことをしていますか?

私は

EDITあなたの助けに感謝:私はちょうど私がここで理解しようとしていることは、ベストプラクティスは、特にJPAに関してあるとを休止するものであることを明確にしたいです。これが普通のPOJOならば、私は識別子フィールドを最終的に作ることができました(私は実際にはクラス全体を不変にして安全です)。私はJPAを使っているので、これを行うことはできません。だから質問:あなたはhashCode契約に違反していますか? Hibernateはこの違反をどのように処理しますか?これを一般的に行うJPA推奨の方法は何ですか?ハッシュベースのコレクションを取り除き、代わりにリストを使うべきですか?

ジョバンニ

+0

私はすべての関係する 'Set'フィールドの初期値を' new HashMap() 'の代わりに'新しいLinkedHashMap() 'に置き換えて動作させました。それは奇妙ではありませんか? –

答えて

0

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

@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に応じて正常に動作する必要があります:

例えば、ここでクラスEntityTwoを見ているだろうかです。この場合、JPAプロバイダは角をつけています。オブジェクトをコレクションに入れる前に、identifierという値を知っている必要があります。さらに、オブジェクトのhashCodeequalsは、マッピングがJPAプロバイダによって明示的に処理されるため呼び出されません。

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

0

あなたの解釈は正しいです。最初の最初のステップとして、hashCode()equals()をあなたのidフィールドにコーディングしてください。あなたのIDはHibernateに伝えているものです。

今後のトラブルを避けるために、第2ステップとして正しいhashCode()equals()を実装してください。あなたはそれをGoogleにすれば十分なリソースがあります。 Hereはこのサイトのものです

8

いいえ、何もやっていません。 JPAエンティティでequalsとhashCodeを実装することは、多くの熱気の問題です。私が知っているすべてのアプローチには重大な欠点があります。あなたが見逃している明らかな、些細な解決策はありません。

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

エンティティへのアクセスがすべて同じセッション内で行われるように、セッションの範囲を拡張することを検討してください。次に、Set<EntityThree>をeager-fetchedではなくlazy-fetchedにして、HHH-3799のeager-fetchingの問題を回避できます。私が取り組んできたほとんどのアプリケーションは、切り離された状態のオブジェクトの使用を控えるだけです。エンティティをロードしてから、セッションが終了してからしばらく使用しているようです。それは私がお勧めするパターンです。 Webアプリケーションを作成する場合は、「ビューで開いているセッション」パターンとSpringのOpenSessionInViewFilterを参照してください。

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

+0

残念ながら、私はその主張を削除しなければならなかった。私は同じトランザクション内でさえ失敗すると思う、私は二重チェックしなければならない。しかし、返信いただきありがとうございます! –