すべてこのように見えます
public class User { ... @OneToMany(fetch = FetchType.LAZY) private List<Phone> phones = new ArrayList<Phone>(); public List<Phone> getPhones() { return phones; } } public class Phone { ... } public class CellPhone extends Phone { ... } public class SatellitePhone extends Phone { ... }
この構成では、特定のユーザーの電話のリストを照会すると、初期化された電話オブジェクトのリスト(たとえば、既にキャッシュにある場合)とプロキシオブジェクトのリストの両方を取得できます。
ほとんどの場合、実際に何を操作しているのかは重要ではありません(実際のオブジェクトまたはそのプロキシ)。 オブジェクトのフィールドをリクエストすると、プロキシオブジェクトが自動的に初期化され、予想されるデータを受け取ります。 しかし、オブジェクトのタイプを調べる必要がある場合、すべてがおかしくなります。
これが起こる理由を見てみましょう。 主な問題は、Hibernateが超能力者ではなく、リストに含まれるオブジェクトの種類を事前に(データベースクエリを実行するまで)知ることができないことです。 したがって、Phoneから継承されたプロキシオブジェクトを含むリストを作成します。
私たちのチームが最初にこの問題に遭遇したとき、この問題を少し研究し、「松葉杖」を作成する必要があることに気付きました。 このエラーは、処理している子クラスを正確に知る必要があるサービスメソッドで発生しました。 このチェックの直前に、別の実装を行いました。オブジェクトがプロキシオブジェクトである場合、初期化されます。 その後、この不快な話を安全に忘れました。
時間が経つにつれて、プロジェクトは成長し、ビジネスロジックはより複雑になりました。 そして今、そのような松葉杖がすでに多すぎるときが来ました(これは3番目または4番目の松葉杖では機能しないことがわかりました)。 さらに、この問題は、1つのオブジェクトに他のオブジェクトの遅延リストを照会するときだけでなく、データベースからオブジェクトのリストを直接照会するときにも発生し始めました。 遅延読み込みを拒否したくありませんでした。 私たちの基地はとても重いです。 アプリケーションのアーキテクチャレイヤーを混在させず、より普遍的なものを作成することにしました。
アプリケーションのスキーム
このスキームでは、DAO層はデータベースクエリを処理します。 データベースを操作するためのすべての基本メソッドが定義されている1つの抽象クラスJpaDaoで構成されています。 そして多くのクラス-その相続人、それぞれが最終的に基本クラスのメソッドを使用します。 それでは、異なるタイプのオブジェクトのリストを共通の親で直接クエリする問題をどのように克服したのでしょうか? JpaDaoクラスにメソッドを作成して、単一のプロキシオブジェクトを初期化し、プロキシオブジェクトのリストを初期化しました。 データベースからオブジェクトのリストを要求するたびに、このリストは初期化を通過します(アプリケーションでオブジェクトのリストを要求する場合、完全に初期化されることがほとんど常に必要なので、意図的にそのようなステップに進みました)。
JpaDaoの実装例
public abstract class JpaDao<ENTITY extends BaseEntity> { ... private ENTITY unproxy(ENTITY entity) { if (entity != null) { if (entity instanceof HibernateProxy) { Hibernate.initialize(entity); entity = (ENTITY) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation(); } } return entity; } private List<ENTITY> unproxy(List<ENTITY> entities) { boolean hasProxy = false; for (ENTITY entity : entities) { if (entity instanceof HibernateProxy) { hasProxy = true; break; } } if (hasProxy) { List<ENTITY> unproxiedEntities = new LinkedList<ENTITY>(); for (ENTITY entity : entities) { unproxiedEntities.add(unproxy(entity)); } return unproxiedEntities; } return entities; } ... public List<ENTITY> findAll() { return unproxy(getEntityManager().createQuery("from " + entityClass.getName(), entityClass).getResultList()); } ... }
最初の問題の解決策では、すべてがそれほどスムーズではありませんでした。 Hibernateは遅延読み込みに直接関与しているため、上記の方法は機能しません。 そして、私たちは小さな譲歩をしました。 1つの親を持つさまざまなタイプのオブジェクトの遅延リストを含むすべてのオブジェクト(たとえば、電話リストを持つユーザー)で、これらのリストのゲッターを再定義しました。 リストが要求されない限り、すべてが正常です。 オブジェクトにはプロキシリストのみが含まれ、不要なリクエストは実行されません。 リストが要求されると、初期化されます。
ユーザーの電話番号のゲッターリストの実装例
public class User { ... @OneToMany(fetch = FetchType.LAZY) private List<Phone> phones = new ArrayList<Phone>(); public List<Phone> getPhones() { return ConverterUtil.unproxyList(phones); } } public class ConverterUtil { ... public static <T> T unproxy(T entity) { if (entity == null) { return null; } Hibernate.initialize(entity); if (entity instanceof HibernateProxy) { entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation(); } return entity; } public static <T> List<T> unproxyList(List<T> list) { boolean hasProxy = false; for (T entity : list) { if (entity instanceof HibernateProxy) { hasProxy = true; break; } } if (hasProxy) { LinkedList<T> result = new LinkedList<T>(); for (T entity : list) { if (entity instanceof HibernateProxy) { result.add(ConverterUtil.unproxy(entity)); } else { result.add(entity); } } list.clear(); list.addAll(result); } return list; } }
この記事では、チームで使用されている(1つの親を持つ)異なるタイプのオブジェクトを含むリストを使用するときに、遅延Hibernateロードを使用する方法を示しました。 この例が似たような状況の人に役立つことを願っています。 この問題を克服するためのより最適で美しい方法をご存知の場合は、記事に追加させていただきます。