ログインしてさらにmixiを楽しもう

コメントを投稿して情報交換!
更新通知を受け取って、最新情報をゲット!

Java質問&情報提供サイトコミュのPrintWriterのバッファの制御

  • mixiチェック
  • このエントリーをはてなブックマークに追加
よくお世話になっています。
バッファの制御がうまく出来ないのでお知恵を貸してください。

プログラムの流れは

画像をクリック

クリックされた座標とユーザーIDをフォームで送信

ユーザーIDをもとにオブジェクトを特定し、そのオブジェクト内にもっている配列とクリックされた座標を比べて値をもとめる。

もとめた値を、DBを管理するクラスに渡す

その値をもとにDBから情報を取り出して出力

となっています。
まずサーブレットのdoGetメソッド内で

//応答文字コードのセット
res.setContentType("text/html; charset=utf-8");
//出力ストリームの取得
PrintWriter out = res.getWriter();

このように宣言したoutをオブジェクトに渡して、さらにデータベースを扱うクラスに渡して使っています。
(この構造が既に無理やりな感じですが…ご容赦ください;;)

DBを扱うクラスでは、情報を取り出したら

number=rs.getString("number");
name=rs.getString("name");
out.write("番号:"+number+"<br>");
out.write("name":"+name+"<br>");


out.write("\n");
out.flush();

というふうに出力しています。
ところがこの出力が不安定で、できる時とできない時があるのです。
できない時はいくつかケースがあるのですが

・まったく表示されない。
・クリックし続けていると、そのうち取得したデータがまとめて出力される時もある。(このとき、上の例の“番号”の部分の文字化けが起こります。DBから取り出された日本語は化けていませんでした)
・flush()でNullPointerExceptionエラーがでる。
・入口のhtmlファイルを開いた時に、その内容ではなく↑のデータが出てくる。
などです。
最後のは、更新するとhtmlファイルの内容が表示されます。

writeではなくprintやprintlnでもやってみましたがwriteが一番出力してくれる確率が高いような気がして採用してみました。
同じメソッド内でSystem.out.println()でコンソールにも出力していますが、こちらは確実に出力してくれていますので通ってくれているはずなんですが…。

納得できないのが、flushしているのに出力されなかったり、writeしているのにNullPointerExceptionがでることです。
バッファを確実に出力していく方法や、解決策わかる方いらっしゃいませんでしょうか?
よろしくおねがいします。


環境
OS:Win XP
JAVA:1.6.0_02
Tomcat 5.5
Eclipse 3.2.2
MySQL 5.1
JDBCドライバ:MySQL Connector/J 5.1.5
文字コードはUTF-8

コメント(15)

Yさん

NullPointerExceptionのstackTraceが無いので勘ですが、
outはスレッドセーフになっていますか?
>Cappuccinoさん

レスありがとうございます。
スレッドセーフにはしていないです…。しなくちゃダメですね;;
ただ、ユーザーが一人しかアクセスしていない状態でも起こっています。
スレッドの問題が起こるのは複数のユーザーが同時に利用した場合だけですよね?
連投すみません。

現在eclipseで開発中なんですが、デバッグした時のPrintWriterの構造を教えてもらえませんか?
入れ子構造で何が起こってるのかつかみづらいんです…。
-が開いている状態、+が閉じている状態、()内は値です。

-out(CoyoteWriter)
    autoFlush(false)
    error(false)
    formatter(null)
   +lineSeparator("\r\n")
   +lock(OutputBuffer)
   +ob(OutputBuffer)
   +out(OutputBuffer)
    psOut(null)
    trouble(false)
    writeBuffer(null)
    writeBufferSize(1024)

この中のlock,ob,outを開くと
-lock
  +bb(ByteChunk)
   BYTE_STATE(2)
   bytesWritten(0)
  +cb(CharChunk)
   CHAR_STATE(1)
   charWritten(0)
   closed(false)
  +conv(C2BConverter)
  +coyoteResponse(Response)
   doFlush(false)
  +enc("utf-8")
  +encoders(HashMap<K,V>)
   gotEnc(true)
   INITIAL_STATE(0)
  +lock(OutBuffer)
  +outputChunk(ByteChunk)
   state(0)
   suspended(false)
   writeBuffer(null)
   writeBufferSize(1024)

lock,ob,outともこの内容です。まずここがよく分らないです…。
そして肝心の、出力したい文字はcbの中に入っていくことは分かるのですが
例えば「lockの中のlockの中のcb」など、あちこちに同じものがあるので、結局どこを見ていれば良いのか分からなくなってしまいました。
アドバイスお願いします!
<4はクラス名を書き間違えたので削除>

PrintWriterを使ったプログラムのテストなのにTomcatが出てくるのはおかしくないですか?
というか、PrintWriterのそんな深いとこ追って嵌まってもしょうがない。アプローチを変えた方がいいです。原因調査に時間をかけるのはあなたにとっては勉強になるかもしれないけど、会社としては止めてほしい作業なんですから。

それから、原因がPrintWriterと思っているようですが、他にあるかもしれません。プログラムを分割してテストするのが無難です。今のままでは原因がIOアクセスなのかMySQLなのかTomcatなのか、あるいはEclipseプラグインなのか分かりません。PrintWriterの呼び出し部分を分離してテストプログラムを作って、それでも不安定ならソースとテストプログラムを展開してみてください。
>chunさん

PrintWriterとTomcatっておかしいんでしょうか?
Servletからクライアント側へ出力するにはPrintWriterを使うのが簡単だと思っていたのですが…。

でもたしかに、今回はデバッグで値を見ていくだけでなく分解して確実に動くものを組み立て直した方が良さそうですね。
もうどういじったら良いのか分からなくなっていたんですけど、やってみます。
ありがとうございます!

ぁ、ちなみにこれ卒論です笑。
卒論ですか・・。
普通、このあたりはJSPを使うのですが、きっと何らかの制約があるのでしょうね。
ちなみに、僕のPCでは下記のプログラムでエラーになることはありませんでした。これを移植しても不安定ならPrintWriterの書き方以外に原因があります。(Tomcat5.5.9, com.sysdeo.eclipse.tomcat_3.1.0, Eclipse3.2.0, WindowsVista, Java1.6.0_01)

public class TestServlet extends HttpServlet {
protected void doGet(HttpServletRequest req,HttpServletResponse res)
throws IOException,ServletException{
PrintWriter pw = res.getWriter();
pw.write("hogehoge<br>");
pw.write("hogehoge<br>");
pw.write("hogehoge<br>");
pw.write(new Date()+"<br>");
pw.write("\n");
pw.flush();
}
}

情報が少ないので勘ですが、単に同期エラーという気がします。脆弱なプラグインだとPCのスペックによっては同期に時間がかかり不安定になることがあります。サービスでTomcatを起動し、WARファイルを作ってwebappsに置いて実行すれば安定するかもしれません。
>chunさん

やはりServletで計算して、それをJSPに渡して出力する方が良いんですかね…。
JSP使ってないのはただ書き慣れていないからだけです;;

あとサンプルまで作っていただいたのに、すみません!ServletのdoGetメソッドやdoPostメソッドで書いたところは問題ないんです。
DBにアクセスするクラスにPrintWriterを渡していて、DBで取り出すループの中で出力していってるんです。それが不安定で…。
説明足りなくてすみません。
こんな感じです。

----Servletクラス----
PrintWriter out = res.getWriter();
String userID=req.getParameter("userID");
UserData userdata=new UserData(userID,out);

----UserDataクラス----
public UserData(String userid,PrintWriter out){
this.start=System.currentTimeMillis();
this.id=userid;
this.out=out;
}

(画面がクリックされる)
----Servletクラス----
double x=Double.parseDouble(req.getParameter("dblClickedX"));
double y=Double.parseDouble(req.getParameter("dblClickedY"));

//ユーザーオブジェクトを探す
userdata=serch(userID);

userdata.get_data(x,y);

----UserDataクラス----
public void get_data(double x,double y){

//クリックされた座標から一番近い星の番号をさがす。
String[][] numbers=serch(x,y);

aDB.open();
aDB.select_data(numbers, out);
aDB.close();
}

----DBクラス----
void select_data(String num[][],PrintWriter out){
String select; //SQL命令文
ResultSet rs;

String number=null,name=null,mag=null,color=null,memo,date;
boolean check=true;

try{
for(int j=0; j<num.length; j++){
if(num[j][0].equals(null)){
break;
}
//テーブルからデータを取り出す
select="select * from sample_planetTABLE6 where number='"+num[j][0]+"';";
//ステートメントオブジェクトを生成
ps=con.prepareStatement(select);
//クエリーを実行して結果セットを取得
rs=ps.executeQuery();
while(rs.next()){
number=rs.getString("number");
name=rs.getString("name");
mag=rs.getString("mag");
color=rs.getString("color");

//データの表示
out.write("<pre>");
out.write("catalog number:"+number+"<br>");
out.write("<br>");
out.write("名前<br>"+" "+name+"<br>");
out.write("赤経<br>");
out.write(" "+num[j][1]+"h "+num[j][2]+"m "+num[j][3]+"s<br>");
out.write("赤緯<br>");
out.write(" "+num[j][4]+"°"+num[j][5]+"' "+num[j][6]+"\"<br>");
out.write("視等級<br>"+" "+mag+"<br>");
out.write("色<br>"+" "+color+"<br>");

select="select memo,date from sample_pBBS where number='"+number+"';";
ps=con.prepareStatement(select);
ResultSet rs2=ps.executeQuery();

while(rs2.next()){
memo=rs2.getString("memo");
date=rs2.getString("date");
out.write("・"+memo+"<br> ("+date+")<br>");
}
out.write("<pre>");
out.write("<br>");
out.write("<form action='PlanetariumServlet' method='POST'>");
out.write("*"+name+" に情報を追加する</pre>");
out.write("<textarea name='message' cols='30' rows='5'>ここに入力してください</textarea><br>");
out.write("<input type='hidden' name='number' value='"+number+"'>");
out.write("<input type='hidden' name='star' value='"+name+"'>");
out.write("<input type='submit' value='送信'>");
out.write("</form>");
out.write("<pre>");
}
check=false;

}
}catch(StringIndexOutOfBoundsException s){
}catch(NumberFormatException e){
}catch(SQLException e) {
}catch(NullPointerException e){
if(check){
out.write("近くに星がありません。<br>");
System.out.println("近くに星はありません。");
}
}
out.write("</body>");
out.write("</html>");
try{
out.write("\n");
out.flush();
}catch(NullPointerException e){
System.out.println("NullPointerExceptionです。");
out.print("データが取得できませんでした。<br>やり直してください。<br>");
}
}

このDBクラスの出力が不安定なんです。
PrintWriterをサーブレットクラス以外で使うことに問題があるのかな、と思っているんですが、関係あるのでしょうか…。
> userdata=serch(userID);
userdataをリクエスト(doGet)間で使いまわしてませんか?

であれば、リクエストごとにuserdataを作り直してみてください。
もしくは、userdataのPrintWriterをリクエストごとに入れ替えてみてください。
>みなさま
じゃまさんのおっしゃっていた通りでした!!!
PrintWriterの使い方、ある意味わかってなかったみたいです…使いまわせると思って、UserDataクラスに最初に渡してました。

じゃまさん、ありがとうございました!
ほんとにこれだけがネックだったので、これで卒業できます…笑。

ログインすると、みんなのコメントがもっと見れるよ

mixiユーザー
ログインしてコメントしよう!

Java質問&情報提供サイト 更新情報

Java質問&情報提供サイトのメンバーはこんなコミュニティにも参加しています

星印の数は、共通して参加しているメンバーが多いほど増えます。

人気コミュニティランキング