QA@IT
この質問・回答は、@ITの旧掲示板からインポートされたものです。

サーブレットでページング処理

サーブレットでDBに接続してJSPて表示するのですが、
良くある、10件表示したら、「次の10件」「前の10件」みたいなボタンで
出力を制御したいのです。
あっっちこっちHPみたのですが、あまり無くよくわかりません。
どなたか、教えていただけませんか。
宜しくお願いします。

質問者:りょうちん

回答

(株)ぽちさんはメモリのことだけ気にしていますが、もう一点追加。

昨今のWebアプリはWebアプリケーションサーバとDBサーバが別のマシンで
あることが良くあると思います。
こういう場合、JSP/ServletなどからDBサーバへのアクセスでは
ネットワークがボトルネックになったりします。

JDBCドライバの実装にもよるでしょうが、実際に表示に利用するのは
10レコードなのに1000レコードを取ってきていてはレスポンスにも差が出ます。
最終的な取得レコードが10レコードだったとしても、1レコードずつ
取得していてはやっぱり遅くなります。

ネットワークというと帯域に目が行きがちですが、線は太くても
回数が多ければかなりのネックになるので気をつけたほうがよいでしょう。

投稿者:まりり

編集 履歴 (0)

こんにちわ。

途中話しが少しズレてきていますが、主題のページング処理について。
わたしの場合は以下のような感じでやってす。

例えば1ページに10件ずつ表示する場合
Servlet Bean JSP のMVCでやってますが簡単に言うと、

検索ボタン押下
→ Serlvlet(bean生成、DB接続)
→ DB(検索結果取得)
→ Bean(全検索結果格納、1〜10件分を別配列に格納)
→ JSP(beanから10件分の配列取得)

次へボタン押下
→ Servlet
→ bean(11〜20件分を配列に格納しなおし)
→JSP(beanから10件分の配列取得)

こんな感じですね。前ページ、次ページ表示処理は全く同じ処理です(引数違うだけ)
実際はbeanの中で全ページ数、現在のページ数、次へボタン表示フラグ、
前へボタン表示フラグ、検索ヒット数、など色々計算、表示に必要な
パラメータは持たせてます。

僕のやり方だと検索結果をすべてメモリに格納している状態なので
「次へ」「前へ」処理にかかる時間は一定ですみます。
index指定でデータをひっこぬいてくるだけなので。

しかし、その分メモリを食います・・。

開発対象のユーザ数などにもよりますが
ハイスペックなマシンを用意する、もしくは検索ヒット数に制限をかけるか
などの必要があると思います。

僕はResultSetでDBから持ってきたものをArrayListに変換しています。
ArrayListの中身はObject[]です。

Object[]は基本的に全てStringです。

一度検索ヒット数3万件で、3枚のブラウザから検索かけた場合
tomcatがメモリあふれで落ちました・・。

調べてみたところ3万件のデータ、内部はObject[7]の状態だったのですが
30000×7=210000個のStringで約20MByte以上使用していました。

javaVMのデフォルトヒープサイズが64なので
20MByte超えが3つで64Mを超えたわけです。

このように内部にデータを持つと非常にメモリ消費が大きいです。
僕のつくりが悪いと言えばそれまでですが・・。

であれば毎回10件分のデータをDBアクセスで取得する方がいいのかもしれません。
場合によりきりかな?

投稿者:(株)ぽち

編集 履歴 (0)

コレクションの話はおいといて、
私も前から、うまいページ処理の方法ないかなぁ、って思ってみました。

これだけ幅広くどこでも使われているような処理(Googleやここの会議室でも)なので、
既存のコンポーネントやタグライブラリあってもよさそうなものですが、
どなたかご存知ありません?

表示件数や、キャッシュにレコードをもつか、毎回DBに問い合わすかとかは、
設定(か引数)で出来るようなやつ。。。

余談ですが、
検索件数の指定はOracleではROWIDを使った記憶があります。
でも昔の話なので、今はもっと便利な方法があるかも。

投稿者:みやも

編集 履歴 (0)

表題と話題が離れているので気がひけますが、
興味がある話題なので。

ArrayListとVectorをソースで見比べてみると、
add()やset()など、synchronizedがついているかどうか
という以外ほとんど差がないんですよね。
その差が大きいと。

Servletをやってて、Servletってマルチスレッドプログラミング
してることになるんだと知ったのは白状するとわりと最近です。
マルチスレッドにともなう問題については基礎として誰もが
知っておくべきことなんだと目から鱗が落ちました。

ところで、Vectorなどレガシーコレクションクラスについて、

>シングルスレッドな場面では別に同期問題は発生しないから必要ない

という点はいいと思います。
無用なsynchronized句がいい影響を与えるわけはありません。

しかし、

>マルチスレッドな場面ではパフォーマンスの問題から使えない

という点に対して、素朴な疑問がうかびました。
私はそういう経験はありませんが、例えば複数のスレッドが非同期に
アクセスするVectorなりArrayListがあったとして、
Vectorならsynchronizedなのでそのままadd等できる、
ArrayListだったらunsynchronizedなので
自分でスレッドセーフにするために排他制御しなければならない。
とすると、そこに生じるパフォーマンスの差はそんなに大きいのだろうか、
というものです。
少なくとも、「使えない」とまで私は断言できません。

日ごろ漠然と考えてたんです。
実験してみりゃいいじゃんという話ではありますが、
皆さんの意見はどうでしょうか。

投稿者:amnaky

編集 履歴 (0)

同時利用者数によって、リニア(それ以上の場合も?)にレスポンスタイムが
悪化するというお話でしょうか。確かにそれだとぞっとしない状況ではあります。
最悪ではデッドロックを起こします.

デッドロックを起こさない場合でも,「それ以上」になりえます.
スケジューリングやコンテキストスイッチばかりで,スレッドが
ほとんど全く走らない時などにこうなります.

上り下り合わせて100車線の道路のうち,99車線が工事中で通行止めという
状況を想像してもらえばわかりますよね.これが1000車線になったり,
1万車線になったりすれば,どうなるか.....

ちらっと検索をかけてみると、ここでも強く警告されていました。
http://www.netgene.co.jp/java/docs/javaPressVol19.html#14
またマニアックなページを...f(^^;
#いや,悪いわけじゃないんだけど...

マルチスレッドな場面ではパフォーマンスの問題から使えない、シングルスレッドな
場面では別に同期問題は発生しないから必要ないということでしょうか?<Vector
まさにその通りです.
#始めたばかりでこの結論に到達できるとは,意外とセンスはあるのでは?

……そうすると、Vectorのことは忘れた方がいいということになりそうですが。
おそらくは上記の理由により,

VectorはJava2以降は,既にレガシー(時代遅れの遺物)コレクションクラス
(集合を扱うクラス)です.これははっきりと明記されています.「推奨されない」
クラス扱いになっていないのは,既に幅広く使われているために,突然削除すると
影響が大きすぎると判断されたためのようです.

ただし,レベルの低い入門書の中には,まだJava2に対応しておらずVectorを
「基本的で重要なクラス」として扱っているものもあるようです.やはり
「プログラミング言語Java 第三版」のような良書を手元に置いておくことは
レベルアップのためには必要でしょう.

投稿者:未記入

編集 履歴 (0)

VectorとHashtableはサーバーサイドでは,絶対に使わない方が
良いでしょう.(クライアントなら良いというわけでもないが.)
. 伊達にレガシーコレクションクラスじゃありません.
#これらに相当するのは,ArrayListとHashMapだったかな?

私は ArrayList と Vector / Hashtable と HashMap の違いは
自動同期のありなしとしか認識してませんが、それ以外に何かあるのでしょうか?

むしろ、同期を自分でとらざるをえない ArrayList/HashMap を使って
同期のし忘れによるもっともデバグしにくい謎の挙動が出てくるよりかは、
パフォーマンスを少々犠牲にしてでも、安全のためには Vector/Hashtable のほうがいいと思っているのですが。

# まぁ、同期の必要があるモノもそうそうあるわけじゃないですけどね。

投稿者:しょむ

編集 履歴 (0)

しょむさんの書き込み (2002-09-20 01:49) より:

私は ArrayList と Vector / Hashtable と HashMap の違いは

自動同期のありなしとしか認識してませんが、それ以外に何かあるのでしょうか?

実装によりますが、VectorとArrayListで拡大するサイズが違ったりします。
Vectorは上限まで来たら、サイズを2倍ずつ拡大するのに、ArrayListは1.5倍ずつでしか拡大しない、とか。(何回もサイズを増やすような場合、結果的にVectorの方が早くなる)

理屈として、ArrayListを使え、とは言われますけれど、現状ではVectorでの実装で問題があるとは思えませんね。
(ArrayListって何?って聞かれることも多いですし)

投稿者:DaikiRyuto

編集 履歴 (0)

永井%Javaは今春に始めたばかり、です。

むしろ、同期を自分でとらざるをえない ArrayList/HashMap を使って

同期のし忘れによるもっともデバグしにくい謎の挙動が出てくるよりかは、

パフォーマンスを少々犠牲にしてでも、安全のためには Vector/Hashtable の

ほうがいいと思っているのですが。

これは大間違い.少々どころじゃありません.

#特にサーバーサイドJavaでは....

同時利用者数によって、リニア(それ以上の場合も?)にレスポンスタイムが
悪化するというお話でしょうか。確かにそれだとぞっとしない状況ではあります。
#しかも、一旦溜まりだすとその傾向が加速して一気に爆発する?

ちらっと検索をかけてみると、ここでも強く警告されていました。
http://www.netgene.co.jp/java/docs/javaPressVol19.html#14

かくいう私なんかも、まだ頭が逐次処理ですので、今まではあまり
意識したことはなかったのですけれど。<Vector

マルチスレッドな場面ではパフォーマンスの問題から使えない、シングルスレッドな
場面では別に同期問題は発生しないから必要ないということでしょうか?<Vector
……そうすると、Vectorのことは忘れた方がいいということになりそうですが。

投稿者:永井和彦

編集 履歴 (0)

私は ArrayList と Vector / Hashtable と HashMap の違いは
自動同期のありなしとしか認識してませんが、それ以外に何かあるのでしょうか?
だいたいそんなもので.

むしろ、同期を自分でとらざるをえない ArrayList/HashMap を使って
同期のし忘れによるもっともデバグしにくい謎の挙動が出てくるよりかは、
パフォーマンスを少々犠牲にしてでも、安全のためには Vector/Hashtable の
ほうがいいと思っているのですが。
これは大間違い.少々どころじゃありません.
#特にサーバーサイドJavaでは....

だからこそ,同期化こそがVectorとHashtableの致命的な設計ミスと
言っても良いくらいです.これはマルチスレッドプログラミングの初歩の
初歩です.ちっとは基礎を勉強してください.

実装によりますが、VectorとArrayListで拡大するサイズが違ったりします
ここは実装依存部分だと思うので,それこそランタイムが変われば
変わる部分だと思います.

(ArrayListって何?って聞かれることも多いですし)
まあ,知らないクラスがあるのはしょうがないにしても,マルチスレッド
の基礎さえ知ってれば,Vectorの恐さは嫌というほど知っているものです.

投稿者:未記入

編集 履歴 (0)

あと蛇足ですが,このVectorをセッションに置いといて,
2回目からは、そっちから持ってくるとかしないと、毎回
新しいVectorになっちゃって、効率が悪いですし、DBが
毎回同じ順序で、「所属」を返してくれないとうまく動き
ません。それだとDBの更新なんかに対応できません。

まあ、ホント突込みどころ満載のコードなので,頑張ってください。

投稿者:わらび

編集 履歴 (0)

1 SQLで制御する。

   最初にDBの主キーだけ取ってきて,Iteratorなりに残しておいて,

   表示件数に合わせて、WHERE句に入れる。

   SELECT * FROM DB

WHERE ID=1 OR ID=2 OR ID=3 OR ... OR ID=10

私が以前ページ処理を実装したときは、SQLでデータを取得する際に
ソートをかけて、offsetとlimitを使用しました。

SQL> SELECT * FROM t_table LIMIT 10 OFFSET 20 ORDER BY hogehoge;

といった感じで、任意のキーでソートしながらデータを得ます。

[追記]
この時使用してたDBMSはPostgreSQLです。今、何気なく検索をかけたら
http://www.orangesoft.co.jp/RDB/rdb_memo_misc.html
にあるように、DBMSによって違うようですのでお気を付け下さい。
[ メッセージ編集済み 編集者: 永井和彦 編集日時 2002-09-19 15:33 ]

投稿者:永井和彦

編集 履歴 (0)

飽くまで即興なので、変数の名前とか、Vectorでホントにいいのか、とか、

VectorとHashtableはサーバーサイドでは,絶対に使わない方が
良いでしょう.(クライアントなら良いというわけでもないが.)
伊達にレガシーコレクションクラスじゃありません.
#これらに相当するのは,ArrayListとHashMapだったかな?

投稿者:未記入

編集 履歴 (0)

こんばんわ。終わる目途がなかなか立たない案件から
逃避しているわらびです。(もう残業時間ですね、まずい)

ようするに、どこまで表示していて、ボタンが押されたときに
どこから表示するのかを、残しておいてやらねばならないわけです。

そこで、JSPでは、リクエスト領域にsetした"end"をgetして、
NEXTが押された、javascriptかなんかで、10足して、BACKなら10引いて
FORMのhiddenかなにかで、新しいインデックスを送り返してもらう、という
イメージなんですね。

確かに名前が"end"じゃまぎらわしいですね。

飽くまで即興なので、変数の名前とか、Vectorでホントにいいのか、とか、
一度に表示できる件数を変える場合を考えたコーディングとか、改善の余地は
すごくありますので、雰囲気だけつかんでもらって、具体的なコーディングは
りょうちんさんが、工夫されるといいと思いますよ。
[ メッセージ編集済み 編集者: わらび 編集日時 2002-09-19 18:29 ]

投稿者:わらび

編集 履歴 (0)

私はOracleを使用しています。

Oracleを使っているのでしたらそれこそOracleで通るSQLを考えればよいです。
だから誰か教えてくれというのは甘えてますね。

「矢印」の部分の「end」がよくわりません。
これはどういうことなんですか?

わらびさんが何をしようとしていたのかちゃんと考えていますか?
ミスタイプだろうと想像はつくと思いますが・・・

[ メッセージ編集済み 編集者: まりり 編集日時 2002-09-19 17:21 ]

投稿者:まりり

編集 履歴 (0)

わらびさん>
ありがとうございます。

今わらびさんのおっしゃったとうりに書いているんですが、
一つ解らない点があります。よろしいでしょうか。。
******************************

int start = Integer.parseInt((String)request.getParameter("end")) + 1
                                            ↑
******************************
「矢印」の部分の「end」がよくわりません。
これはどういうことなんですか?
宜しくおねがいします。

[ メッセージ編集済み 編集者: りょうちん 編集日時 2002-09-19 16:36 ]
[ メッセージ編集済み 編集者: りょうちん 編集日時 2002-09-19 16:36 ]

投稿者:りょうちん

編集 履歴 (0)

りょうちんさんの書き込み (2002-09-19 13:07) より:

この部分はまず「所属」というものがあるんですけど、

それをキーにしてヒットするものをまず全部とってきますよ。

そして、10件なら10件をwhere句でまわすというう事なんですか?

こんにちは。

たとえば、

String sql = "SELECT 所属 FROM TABLE";

connection = UtilApplication.getConnection();
statement = connection.createStatement();
resultset = statement.executeQuery(sql);

Vector key = new Vector();

while(resultset.next()){
    key.add(resultset.getString(1));
}

int start = Integer.parseInt((String)request.getParameter("end")) + 1;
int end = start + 10;
request.setAttribute("end",new String(end));

StringBuffer query = new StringBuffer("SELECT * FROM TABLE WHERE ");
query.append("所属=").append(key.get(start));

for(int i = start + 1;i < end;i++){
    query.append(" OR 所属=").append(key.get(i));
}

statement = connection.createStatement();
resultset = statement.executeQuery(query.toString());

こんな感じでしょうか。

即興なので,相当いいかげんですが,雰囲気だけでも。

投稿者:わらび

編集 履歴 (0)

わらびさん>
ありがとうございます。
先日もありがとうございました。
先日の件はできました。

そこで先日のPGにこのページング処理を追加しようと考えていたのです。
*********************************
1 SQLで制御する。
   最初にDBの主キーだけ取ってきて,Iteratorなりに残しておいて,
   表示件数に合わせて、WHERE句に入れる。

   SELECT * FROM DB
WHERE ID=1 OR ID=2 OR ID=3 OR ... OR ID=10

**********************************
この部分はまず「所属」というものがあるんですけど、
それをキーにしてヒットするものをまず全部とってきますよ。
そして、10件なら10件をwhere句でまわすというう事なんですか?

宜しくお願いします。

投稿者:りょうちん

編集 履歴 (0)

こんにちは。わらびです。

具体的にどのように書けばいいのかが解らないのです・・・
参考になるHPとかありましたら、教えていただけませんか?

参考となるHPは知りませんが、いろいろあると思います。
条件によっていろいろ使い分ければいいと思いますが,

1 SQLで制御する。
   最初にDBの主キーだけ取ってきて,Iteratorなりに残しておいて,
   表示件数に合わせて、WHERE句に入れる。

   SELECT * FROM DB
WHERE ID=1 OR ID=2 OR ID=3 OR ... OR ID=10

   みたいなSQLをservletを作り、NEXTボタンが押された場合は
   11〜20が入るようにする

2 DBの全フィールドを持ってきて,そのデータを持ったオブジェクトを
  Iterator等に入れて,出力する。

とりあえず、私が思いついたのはこんな感じですが,工夫次第で,いくらでも
ロジックが組めると思います。

投稿者:わらび

編集 履歴 (0)

悪夢を統べるものさんの書き込み (2002-09-19 10:08) より:

良くある、10件表示したら、「次の10件」「前の10件」みたいなボタンで

出力を制御したいのです。

ボタンからの入力がリクエストとしてサーバーに送られるので,

あとはそれにあわせてServletなりJSPなりでページを

生成するだけだとおもいますが?

#別にボタンが出力を制御するわけじゃない.

ありがとうございます。
ボタンが出力制御しない事はわかったのですが、
具体的にどのように書けばいいのかが解らないのです・・・
参考になるHPとかありましたら、教えていただけませんか?

投稿者:りょうちん

編集 履歴 (0)

良くある、10件表示したら、「次の10件」「前の10件」みたいなボタンで
出力を制御したいのです。
ボタンからの入力がリクエストとしてサーバーに送られるので,
あとはそれにあわせてServletなりJSPなりでページを
生成するだけだとおもいますが?
#別にボタンが出力を制御するわけじゃない.

投稿者:未記入

編集 履歴 (0)

永井和彦さん>
ありがとうございます。

私はOracleを使用しています。

投稿者:りょうちん

編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。