EclipseLinkを使ってLazy Loading設定しているとき、
エンティティのequalsメソッドの扱いが面倒になることが判りました。
例えば次のようなケースを考えてみます。
1つの店舗(Shop)は、一人の従業員(Employee)を店長として持っているという関係です。
この図ではN:1の関係なので従業員は店長を兼務できるのですが、
それは今回の記事と関係ないので気にしないでください(-_-)
さて、このときに、Lazy Loading設定された状況下において、次のようなコードを書いたとします。
サンプルコードその1
まずはエンティティクラスの定義から。
import javax.persistence.*; import org.apache.commons.lang.builder.EqualsBuilder; // 店舗クラス @Entity public class Shop { private long id; private String shopName; // 従業員エンティティを遅延ロード @ManyToOne(fetch = FetchType.LAZY) private Employee chief; //getterとかsetterとか public boolean equals(Object o) { return EqualsBuilder.reflectionEquals(this, o); } } // 従業員クラス @Entity public class Employee { private long id; private String employeeName; //getterとかsetterとか public boolean equals(Object o) { return EqualsBuilder.reflectionEquals(this, o); } }
で、実行コードその1。
import javax.persistence.*; EntityManager em = ...; long shopId1 = ...; long shopId2 = ...; //shop1とshop2は違うエンティティ。 assert shopId1 != shopId2; Shop shop1 = em.find(Shop.class, shopId1); Shop shop2 = em.find(Shop.class, shopId2); boolean isSameShop = shop1.equals(shop2);
isSameShopの値はfalseです。まあ当たり前ですね。
サンプルコード2
では次のコードはどうでしょう。
import javax.persistence.*; EntityManager em = ...; long shopId1 = ...; Shop shop1 = em.find(Shop.class, shopId1); Shop shop1Another = em.find(Shop.class, shopId1); // 参照は別であるとする assert shop1 != shop1Another; boolean isSameShop = shop1.equals(shop1Another);
このとき、isSameShopの値はfalseになります。同じショップなのに。
ちなみにLazy Loadingしていない場合はtrueになります。
なぜこのような現象が発生するのでしょうか。
まあ、Lazy LoadingがONかOFFかで結果が異なることからすると、Lazy Loadingが原因なのは明らかなんですが。
原因の考察
原因は、バイトコードエンハンスされた部分にあります。
しばらく前の記事で、バイトコードエンハンスしたコードの中身が
こんな感じになることをご紹介しました。
Shop@1db3f6c[ id=1 shopName=店舗1 chief=null _persistence_chief_vh={UnitOfWorkQueryValueHolder: not instantiated} ]
Shopエンティティへ、_persistence_chief_vhフィールドがバイトコードエンハンスで追加されています。
このとき、_persistence_chief_vhフィールドはequalsでfalseを返してしまうので、
EqualsBuilder.reflectionEqualsが必ず失敗するようになるのが原因だと考えられます。
この問題を解決するには、エンティティのequalsメソッドをEqualsBuilderで手抜きなんかせず、
きちんと手書きすれば大丈夫です。
でも…メンドクサイですよね(-_-)