8

JPAクエリでレコードの位置を知るにはどうすればよいですか?

このシグネチャを持つメソッドを多かれ少なかれ実装するページ化された結果を返すサービスがあります。

List<Record> getRecordsPage(long page, int pageSize);

これが呼び出されると、クエリを作成して次のように構成するだけです。

TypedQuery<Record> query = entityManager.createQuery(criteriaQuery);
query.setFirstResult(page * pageSize);
query.setMaxResults(pageSize);

これにより、結果がページングされます。これは期待どおりに機能しており、非常に単純です。

問題

もう 1 つの要件は、特定のレコードを含むページを取得するメソッドを実装することです。次のシグネチャを持つメソッドを実装します。

List<Record> getRecordsPage(Record record, int pageSize);

このメソッドは、レコードが存在する正しいページを生成する必要があります。たとえば、getRecordsPage(RECORD4, 2)呼び出しの場合、データベースの状態を考慮します。

1. RECORD1 
2. RECORD2 
3. RECORD3 
4. RECORD4 
5. RECORD5 

返されるページは 2 を含む必要があります[RECORD3, RECORD4]

ORDER BYパラメータは常に設定され、複数のフィールドである可能性があります。

今までの解決策

これまで、次のようないくつかのソリューションがあります。

  1. まったく良くありませんが、問題は解決します:

提供されたクエリを使用して、ページングなしで id だけを選択し、indexOfその位置を見つけるために a だけを実行し、その位置に基づいてレコードのページを見つけてから、getRecordsPage(long page, int pageSize)既に実装されているものを使用して通常のプロセスを実行します。

  1. データベースとの結合が高いため、あまり良くありません:

私は mySQL を使用しているので、 : のような sql を実行してselect r from (select rownum r, id from t order by x,y) z where z.id = :id、レコードの位置を返すことができ、それを使用して を呼び出すことができましたgetRecordsPage(long page, int pageSize)

良い解決策

要件:

  1. 複数のフィールドによる順序をサポートするものとします。
  2. クエリを指定すると、レコードの位置または含まれるレコードのページ オフセットが返されます。

適切な解決策は次のとおりです。

  1. 純粋な JPA であること。
  2. レコードの位置を見つけるためだけに、データベースで追加のクエリが実行されても問題ありませ
  3. ある時点で休止状態が使用されている場合は問題ありません(この場合、休止状態は JPA の背後にあるため)。
4

2 に答える 2

6

私が正しく理解していることを確認するために:あなたはRecordアイテムを含むページを事前に選択して、すべてのレコードのページ付きリストを表示していますか?

まず第一に、リレーショナル データベースはデータベース内のレコードの暗黙的な並べ替えを提供しないことを知っておく必要があります。最初から最後に追加された順にソートされているように見えますが、これは移植性と信頼性がありません。

したがって、ページ付きリスト/グリッドは、列によって明示的にソートする必要があります。簡単にするために、グリッドが でソートされているとしますid。現在表示されているレコードの ID はわかっています (例: X)。最初に、この並べ替え順序に関して、レコードがテーブルのどの位置にあるかを把握する必要があります。

SELECT COUNT(r)
FROM Record r
WHERE r.id < :X

このクエリは、レコードののレコード数を返します。今は簡単です:

int page = count / pageSize

pageは 0 ベースです。

残念ながら、並べ替え列が一意でない場合、これはすべての場合に機能するとは限りません。ただし、列が一意でない場合、並べ替え自体が不安定になるため (同じ値を持つレコードがランダムな順序で表示される可能性があります)、追加の一意の列による並べ替えを検討してください。

...
ORDER BY r.sex, r.id

この場合、レコードは最初にsex(多数の重複) および ID でソートされます。現在のレコードの前にレコードをカウントするソリューションは引き続き機能します。

于 2012-05-06T12:00:46.863 に答える
2

報奨金がまだ割り当てられていないことを考えると、別の回答を追加します。(これはTomasz Nurkiewiczの回答と非常に似ていますが、私がトリッキーな部分だと思うことについてもう少し話します:WHERE条項を正しくすることです。失礼と見なされる場合は削除します。)

レコードの位置を見つけるためにネイティブ クエリは必要ありません。慎重に作成されたSELECT COUNT(r).

具体的にするために、Record型にプロパティがfooありbar、並べ替えに使用していると仮定しましょう (つまりORDER BY foo, bar)。次に、必要なメソッドの簡単なバージョンは次のようになります (テストされていません)。

List<Record> getRecordsPage(Record record, int pageSize) {
    // Note this is NOT a native query
    Query query = entityManager.createQuery(
        "SELECT COUNT(r) " +
        "FROM Record r " +
        "WHERE (r.foo < :foo) OR ((r.foo = :foo) AND (r.bar < :bar))");
    query.setParameter("foo", record.getFoo());
    query.setParameter("bar", record.getBar());
    int zeroBasedPosition = ((Number) query.getSingleResult()).intValue();
    int pageIndex = zeroBasedPosition / pageSize;
    return getRecordsPage(pageIndex, pageSize);
}

注意が必要な点が 2 つあります。WHERE句を正確に正しく設定することと、すべての並べ替え列で「同点」を処理することです。

句に関してWHERE、目標は、指定されたよりも「下」にソートされるレコードをカウントすることRecordです。並べ替え列が 1 つあれば簡単ですr.foo < :foo。たとえば、 を含むレコードだけです。

2 つの並べ替え列の場合、2 番目の列で切断する必要がある最初の列に「結合」が存在する可能性があるため、少し難しくなります。したがって、「下位」レコードにはr.foo < :foo、またはr.foo = :fooANDがありr.bar < :barます。(3 つある場合は、3 つのOR条件が必要になります。 のようなもの(r.foo < :foo) OR ((r.foo = :foo) AND (r.bar < :bar)) OR ((r.foo = :foo) AND (r.bar = :bar) AND (r.baz < :baz))です。)

そして、すべての並べ替え列で「同点」になる可能性があります。これが発生しgetRecordsPage(long,int)た場合、単純に実装されていると、ページャー関数 ( ) が毎回異なる結果を取得する可能性があります (データベースには、クエリが実行されるたびに異なる順序で「等しい」行を返す余裕があるため)。

これを回避する簡単な方法の 1 つは、レコード ID による並べ替えを追加して、毎回同じ方法で同点が解消されるようにすることです。そして、質問に記載されている関数の両方に対応するロジックを入れたいと思うでしょう。つまりid、クエリの最後に追加することを意味します。たとえばORDER BY foo,bar,id、ページャー関数で、別のOR条件をWHEREin に追加しgetRecordsPage(Record,int)ます。)

于 2012-07-27T03:02:50.147 に答える