クエリの一部を再利用してカウントするJavaコーディングのベストプラクティス
-
05-07-2019 - |
質問
implementing-result-paging-in- hibernate-getting-total-number-of-rows の質問は、いくつかの実装上の懸念に関する別の質問を引き起こします:
HQLクエリの一部を再利用してカウントを行う必要があることがわかったので、効率的に再利用する方法を教えてください
2つのHQLクエリの違いは次のとおりです。
- 選択範囲は、pojoまたはプロパティ(またはリスト)ではなく、
count(?)
です - フェッチは発生しないため、一部のテーブルは結合されません
-
order by
が消えます
他の違いはありますか?
この再利用を効率的に達成するためのコーディングのベストプラクティス(懸念:努力、明確さ、パフォーマンス)をお持ちですか?
単純なHQLクエリの例:
select a from A a join fetch a.b b where a.id=66 order by a.name
select count(a.id) from A a where a.id=66
更新済み
次についての回答を受け取りました:
- 基準を使用します(ただし、HQLを主に使用します)
- 文字列の操作クエリ(ただし、複雑で非常に安全ではないことに全員が同意しています)
- クエリのラッピング、データベースの最適化に依存しています(ただし、これは安全ではないと感じています)
誰かが別のパスに沿ってオプションを与えることを望んでいました。これは文字列の連結に関連しています。
共通部分を使用して両方のHQLクエリを作成できますか?
解決
いい質問。これは私が過去にやったことです(あなたがすでに言及した多くのこと):
- SELECT 句が存在するかどうかを確認します。
- そうでない場合は、
select count(*)
を追加します
- それ以外の場合は、 DISTINCT または集約関数が含まれているかどうかを確認します。 ANTLRを使用してクエリを解析している場合、それらを回避することは可能ですが、かなり複雑です。
select count(*)from()
で全体をラップすることをお勧めします。
- そうでない場合は、
- 削除
すべてのプロパティを取得
- 文字列としてHQLを解析している場合は、結合から
fetch
を削除します。 ANTLRでクエリを本当に解析している場合は、left join
を完全に削除できます。考えられるすべての参照を確認するのはかなり面倒です。 -
order by
を削除します
- 1.2で行ったことに応じて、
group by
/having
を削除/調整する必要があります。
当然、上記はHQLに適用されます。 Criteriaクエリでは、操作が簡単ではないため、できることは非常に限られています。 Criteriaの上で何らかのラッパーレイヤーを使用している場合、ANTLR解析結果の(制限された)サブセットと同等の結果になり、その場合は上記のほとんどを適用できます。
通常は現在のページと合計カウントのオフセットを保持するので、通常、最初に指定された制限/オフセットで実際のクエリを実行し、次の場合にのみ count(*)
クエリを実行します結果の数は制限以上であり、オフセットはゼロです(他のすべての場合、前に count(*)
を実行したか、とにかくすべての結果が返されました)。もちろん、これは同時変更に関する楽観的なアプローチです。
更新(HQLを手動で組み立てる場合)
このアプローチは特に好きではありません。 HQLには、名前付きクエリとしてマップされると、ビルド時エラーチェックの利点があります(通常は、統合テスト中に行われますがSessionFactoryをビルドする必要があるため、技術的にはランタイムです)。実行時に生成されると、実行時に失敗します:-)パフォーマンスの最適化を行うことも、簡単ではありません。
もちろん、同じ基準がCriteriaにも当てはまりますが、文字列の連結とは対照的にAPIが明確に定義されているため、やや難しくなります。 2つのHQLクエリ(ページ1と「グローバルカウント」1)を並行して構築すると、コードの重複(および潜在的なさらなるバグ)が発生したり、何らかのラッパーレイヤーを上に記述して強制的に作成したりします。どちらの方法も理想からはほど遠い。また、APIを介してクライアントコードからこれを行う必要がある場合、問題はさらに悪化します。
実際、この問題についてかなり考えました。 Hibernate-Generic-DAO からの検索APIは妥当な妥協のようです。上記のリンクされた質問への私の回答には詳細があります。
他のヒント
(SQL?)基準に投影を設定して、Hibernateに対して意図を明確にしようとしましたか? 私は主にCriteriaを使用しているので、これがどのようにあなたのケースに当てはまるかわかりませんが、私は使用しています
getSession().createCriteria(persistentClass).
setProjection(Projections.rowCount()).uniqueResult()
そしてHibernateがキャッシング/再利用/スマートなものをそれ自体で把握できるようにします。実際にどの程度スマートなものが実際に行われるかはわかりません。
まあ、これがベストプラクティスかどうかはわかりませんが、私のプラクティスです:)
クエリとして次のようなものがある場合:
select A.f1,A.f2,A.f3 from A, B where A.f2=B.f2 order by A.f1, B.f3
そして、私はただいくつの結果が得られるのか知りたいのです、私は実行します:
select count(*) from ( select A.f1, ... order by A.f1, B.f3 )
そして、結果を整数として取得します。マッピング結果はPOJOになりません。
「order by」は非常に複雑であるため、クエリを解析して一部を削除します。優れたRDBMSはクエリを最適化します。
良い質問です。
フリーハンドのHQLの状況では、このようなものを使用しますが、特定のエンティティに非常に固有であるため、これは再利用できません
Integer count = (Integer) session.createQuery("select count(*) from ....").uniqueResult();
これを1回実行し、ページ移動するまで開始番号を調整します。
基準については、このようなサンプルを使用しています
final Criteria criteria = session.createCriteria(clazz);
List<Criterion> restrictions = factory.assemble(command.getFilter());
for (Criterion restriction : restrictions)
criteria.add(restriction);
criteria.add(Restrictions.conjunction());
if(this.projections != null)
criteria.setProjection(factory.loadProjections(this.projections));
criteria.addOrder(command.getDir().equals("ASC")?Order.asc(command.getSort()):Order.desc(command.getSort()));
ScrollableResults scrollable = criteria.scroll(ScrollMode.SCROLL_INSENSITIVE);
if(scrollable.last()){//returns true if there is a resultset
genericDTO.setTotalCount(scrollable.getRowNumber() + 1);
criteria.setFirstResult(command.getStart())
.setMaxResults(command.getLimit());
genericDTO.setLineItems(Collections.unmodifiableList(criteria.list()));
}
scrollable.close();
return genericDTO;
しかし、これは ScrollableResults:last()
を呼び出すことで毎回カウントします。