web-dev-qa-db-ja.com

JDBCページネーション

JDBCを使用してページネーションを実装したい。私が知りたい実際のことは、「ページ1と2のそれぞれについてデータベースから最初の50レコードを取得し、次に次の50レコードを取得する方法」です。

クエリは_Select * from datars.absolute(row)が最初のページのレコードをスキップする方法であることがわかりましたが、大きな結果セットでは時間がかかり、この時間をかけたくありません。また、クエリでrownumlimit + offsetを使用したくないのは、これらがクエリで使用するのに適していないからです。クエリで使用したい。

誰かがページネーションのためにResultSetを制限する方法を手伝ってくれますか、JDBCが私たちに与えている方法はありますか?

33
Zeeshan

JDBCを使用するだけでは、これを効率的に行う方法はありません。 n行に制限およびi番目の項目から開始句をSQLに直接作成して効率化する必要があります。データベースによっては、これは実際には非常に簡単かもしれません(MySQLのLIMITキーワードを参照)。Oracleなどの他のデータベースでは、少し注意が必要です(サブクエリとrow​​num疑似列を使用)。

このJDBCページネーションチュートリアルを参照してください: http://Java.avdiel.com/Tutorials/JDBCPaging.html

26
psp

クエリonly実際に必要なデータは現在のページに表示する必要があります。データセット全体をJavaのメモリに持ち込んで、そこでフィルタリングしないでください。それは物事を不必要に遅くするだけです。

これを適切に実装したり、特定のデータベースのSQLクエリを計算したりするのに実際に苦労している場合は、 my answerhere をご覧ください。

Update:Oracleを使用しているため、前述の回答からOracleをターゲットにした抜粋を次に示します。

Oracleでは、rownum句を含むサブクエリが必要です。

private static final String SQL_SUBLIST = "SELECT id, username, job, place FROM"
    + " (SELECT id, username, job, place FROM contact ORDER BY id)"
    + " WHERE ROWNUM BETWEEN %d AND %d";

public List<Contact> list(int firstrow, int rowcount) {
    String sql = String.format(SQL_SUBLIST, firstrow, firstrow + rowcount);

    // Implement JDBC.
    return contacts;
}
20
BalusC

免責事項: SQLページネーションとJDBCページネーションに関するこのブログ投稿は私によって投稿されています。

Hibernateページネーションを無視して、SQLページネーション/ JDBCページネーションを使用できます

SQLページネーション

2つの基本的なアプローチがあります。

  1. 断片的な結果セットの操作(各ページの新しいクエリ)
  2. 完全な結果セットでの操作

その方法はSQL固有です

MySQL /他の多くのSQLの場合、制限とオフセットで実行できます

Postgresql: http://microjet.ath.cx/WebWiki/ResultPaginationWithPostgresql.html

Oracleでは、「トップNクエリ」を処理するのと同じ形式を使用します。最適化されている5人の最高賃金の従業員

select *   from ( select a.*, rownum rnum

from ( YOUR_QUERY_GOES_HERE -- including the order by ) a

where rownum <= MAX_ROWS )

where rnum >= MIN_ROWS

ROW-NUMの非常に詳細な説明

類似SOスレッド

JDBCページネーション

問題は、SQLを実行すると、結果がどのようにロードされるかということです。すぐにまたは要求に応じて?これと同じSOスレッド

最初にいくつかを理解する必要があります JDBCの基本、Oracleから

Javadocごと:statement.execute()

execute: Returns true if the first object that the query returns is a ResultSet object. Use this method if the query could return one or more ResultSet objects. Retrieve the ResultSet objects returned from the query by repeatedly calling Statement.getResutSet.

カーソルを使用して、結果セットのデータにアクセスします。このカーソルは、最初はデータの最初の行の前に配置されているポインターですが、DBのカーソルとは異なります。

データは要求に応じてフェッチされます。一方、execute()を実行すると、初めてフェッチします。

次に、ロードされるデータの数は?設定可能です。 ResultSetでJava API setFetchSize()メソッドを使用して、ドライバーがDBから一度に取得する行数、一度に取得するブロックの大きさを制御できます。

たとえば、合計結果が1000であると仮定します。フェッチサイズが100の場合、1行目のフェッチはDBから100行をロードし、2行目から100行目はローカルメモリからロードされます。

JavaDocから

Gives the JDBC driver a hint as to the number of rows that should be fetched from the database when more rows are needed for ResultSet objects genrated by this Statement. If the value specified is zero, then the hint is ignored. The default value is zero.

「ヒント」という言葉に注意してください-ドライバー固有の実装によって上書きされる可能性があります。

これは、SQL開発者のようなクライアントの「行を100に制限する」機能にも基づいています。

ソリューション全体を完成させて、結果をスクロールするには、APIのResultSetタイプとScrollableCursorを考慮する必要があります。

Oracleのこの投稿から実装例を見つけることができます

oracle Toplink開発者ガイドの例112 JDBCドライバのフェッチ・サイズの本から

ReadAllQuery query = new ReadAllQuery();

query.setReferenceClass(Employee.class);

query.setSelectionCriteria(new ExpressionBuilder.get("id").greaterThan(100));

// Set the JDBC fetch size

query.setFetchSize(50);

// Configure the query to return results as a ScrollableCursor

query.useScrollableCursor();

// Execute the query

ScrollableCursor cursor = (ScrollableCursor) session.executeQuery(query);

// Iterate over the results

while (cursor.hasNext()) {

System.out.println(cursor.next().toString());

}

cursor.close();

.....................

結局のところ、質問は沸騰します

ページネーションを行うより良い方法はどれですか?

SQLアプローチで意味をなすために、SQLはORDER byである必要があることに注意してください。

それ以外の場合、次のページでいくつかの行を再度表示することができます。

以下は、JDBCドライバーおよびその他のSO回答に関するPostgresqlのドキュメントからのポイントです。

まず、ページングソリューションを適切に機能させるために、元のクエリにORDER BY句が必要です。それ以外の場合、Oracleが最初のページ、2番目のページ、およびN番目のページに同じ500行を返すことは完全に有効です

主な違いはJDBCの方法です。フェッチ中に接続を保持する必要があります。これは、たとえば、ステートレスWebアプリケーションには適さない場合があります。

SQLウェイの場合

構文はSQL固有であり、保守が容易ではない場合があります。 JDBCウェイの場合

  • サーバーへの接続にはV3プロトコルを使用する必要があります。これは、サーバーバージョン7.4以降のデフォルトです(サポートされているのはサーバーバージョンのみです)。
  • 接続は自動コミットモードであってはなりません。バックエンドはトランザクションの終了時にカーソルを閉じます。そのため、自動コミットモードでは、バックエンドは何かをフェッチする前にカーソルを閉じます。
  • Statementは、ResultSet.TYPE_FORWARD_ONLYのResultSetタイプで作成する必要があります。これはデフォルトであるため、これを利用するためにコードを書き換える必要はありませんが、逆方向にスクロールしたり、ResultSet内をジャンプしたりすることはできません。
  • 指定するクエリは、セミコロンでつながれた複数のステートメントではなく、単一のステートメントでなければなりません。

いくつかのさらなる読み物

この投稿は、オプティカルフェッチサイズを使用したパフォーマンスチューニングについてです

9
vincentlcy

MySQLまたはPostgreSQLを使用している場合limitおよびoffsetがキーワードです。 MSSqlServerとOracleには似たような機能がありますが、私はもう少し痛いようです。

MySQLおよびPostgreSQLの場合は次のとおりです。

http://www.petefreitag.com/item/451.cfm

Oracleについては、こちらをご覧ください。

http://www.Oracle-base.com/forums/viewtopic.php?f=2&t=8635

2
Nils Schmidt

私はこの質問が古いことを知っていますが、これは私がページングを実装する方法です、私はそれが誰かを助けることを願っています

int pageNo = ....;    
String query = "SELECT * FROM data LIMIT ";

switch (pageNo) {
case 0: // no pagination, get all rows
    query += Integer.MAX_VALUE; // a big value to get all rows
    break;
case 1: // get 1st page i.e 50 rows
    query += 50;
    break;
default:
    query += String.valueOf((pageNo-1)*50 + 1) ", " + String.valueOf(50);
    break;
}

PreparedStatement ps = connection.prepareStatement(query);
....

値50をpageSizeという名前の定数にして、任意の数値に変更できるようにします

2
HeisenBerg

これは、結果をページ分割するための休止状態ソリューションへのリンクです。 HQL-ページネーションの行識別子

2
Abhishek Gupta

JDBC接続に1つの巨大な結果セットを持たせたくないことを暗黙的に理解します。これは非常に長い間開いたままで、必要に応じてナビゲートします。

通常のアプローチは、完全なリクエストのサブセットのみを取得するために必要なSQLを追加することです。これは、残念ながらデータベースごとに異なり、SQLステートメントをベンダー固有にします。正しく思い出せば、LIMITがMySQLで使用されます。リクエストごとに適切な範囲を尋ねてください。

また、HibernateにはHQLでこれを実行できる機能が含まれていると思いますが、私はそれをよく知りません。

HibernateやさらにJava Persistence APIまたは単なるSQLなど)のようなORMフレームワークを使用していますか?

私の答え:LIMITとOFFSETを使用してください http://www.petefreitag.com/item/451.cfm

または、ROWNUM演算子を使用してください。SQLの周りにラッパーが必要ですが、基本的には

  select * from (select bla.*, ROWNUM rn from (
  <your sql here>
  ) bla where rownum < 200) where rn >= 150'
1
Lars
_PreparedStatement pStmt = // ... however you make it
pStmt.setFetchSize( /* desired number of records to get into memory */ ); 
_

setFetchSize(int)ヒントにすぎない -たとえば、MySQLで前回使用していたときに、サポートされていませんでした。 Oracleのドキュメントを簡単に見てみると、JDBCがサポートしているようです。私はそれについては引用しませんが、少なくとも試してみる価値はあります。そして、はい、この答えは脆弱ですが、堅牢なソリューションを実装するよりも頭痛が少ないかもしれません。

基本的に、すべてに対してリクエストを発行でき、一度にメモリにフェッチサイズを取得するだけです(以前の結果を保持していない場合)。したがって、フェッチサイズを50に設定し、接続/クエリを作成し、最初の50の結果を表示します(クエリの次のバイトで別のフェッチを発生させます)。

1
Carl

入力:

  1. 注文情報の例(A2またはD3)(A/Dの昇順/降順)+列
  2. 注文情報の例(A2またはD3)(A/Dの昇順/降順)+列
  3. フィルター値
  4. 開始行
  5. 開始行
  6. 行の最大数

結果:

  • 選択された値
  • 選択したページ
  • この順序での行のインデックス
  • 利用可能なデータを数えます。 (2番目のクエリを保存)

利点のみのクエリ:

  • このフィルターで使用可能な列の合計
  • 選択したページのみをdbから転送します
  • 動的SQLなしで正しく注文された

不利益:

  • Oracle依存

    select x。* from(select c.pk_field、c.numeric_a、c.char_b、c.char_c ROW_NUMBER()over(ORDER BY decode(?、 'A1'、to_char(c.numeric_a、 'FM00000000')、 'A2 '、c.char_b、' A3 '、c.char_c、' A ')asc、decode(?、' D1 '、to_char(c.numeric_a、' FM00000000 ')、' D2 '、c.char_b、' D3 ' 、c.char_c、 'A')desc、c.pk_field asc
    )AS "idx"、COUNT(*)オーバー(1で並べる)myTable cの "cnt" c.haystack =? )xここで、greatest(nvl(?、1)、1)とnvl(?、1)-1+?の間のx。 "idx"

1
SkateScout

Oracleは8i以降の標準のROW_NUMBER()ウィンドウ関数をサポートしているため、使用できます。パラメータ化されたクエリとして実行できるため、開始行番号と終了行番号を設定するだけで済みます。例えば。

SELECT * 
   FROM ( SELECT *, ROW_NUMBER() ORDER BY (sort key) AS rowNumber FROM <your table name> ) AS data
WHERE 
   rowNumber>=:start AND
   rowNumber<:end

(名前付きパラメーターを使用していない場合は、:start /:endを位置パラメーターのプレースホルダー「?」に置き換えます)

ウィキペディアの SELECT SQL、ウィンドウ関数 を参照してください。この記事では、ROW_NUMBER()標準ウィンドウ関数をサポートする他のDBもリストしています。

1
mdma

これには以下の方法を使用できます。

  1. ここでrowNoは、n個のレコードを取得する開始行の番号です。
  2. pageSizeは、取得するレコードの総数です。
 
 
 public CachedRowSet getScrollableList(PreparedStatement prestmt、int rowNo)
 {
 getPageSize(); 
 ResultSet rs = null; 
 CachedRowSet crs = null; 
 try 
 {
 rs = prestmt.executeQuery(); 
 crs = new CachedRowSetImpl(); 
 crs.setPageSize(pageSize); 
 crs.populate(rs、rowNo); 
} 
 catch(SQLException ex)
 {
 logger.error( "getScrollableList()メソッドのDatabaseInterfaceの例外:" + ex.getMessage()、ex); 
} 
 finally 
 {
 // close rs 
 // psを閉じるtmt 
} 
 return crs; 
} 
 
 
 
 
0
Vaibhav Sharma