Apr 27, 2008

MySQL Connector-Jでの再接続

JDBCのDriverManager#getConnectionによるデータベースへの接続は結構時間がかかるので、短時間に何度もDBにアクセスする場合は、Connectionオブジェクトを保持したい。
しかし、MySQLのデフォルト設定の動作では、DBへの接続は8時間使われないと切られてしまう。常駐型のservletなどの連続運転のアプリでは、途中でこのコネクションが切れることがあり得る。この時間を変更することもできるらしいが、いつか切れるので、それで解決にはならない。従って、切れれば再接続する必要がある。

JDBCアプリ側で再接続する場合、DBへの接続が有効か無効かを知る術が問題になる。接続が切れた後のクエリ送信時の例外はSocketクラスのIOExceptionであり、ドライバ(MySQL Connector-J)にてcatchされるので、JDBCアプリではcatchできない。JDKのAPI仕様書を見るとConnection#isClosedというメソッドがあるが、これはJDBCアプリからcloseしたかどうかを返すものであり、MySQL側で接続を切られてもclosedにはならない。

そもそもservletでは、こういう時の定石はconnection poolingらしい。ServletエンジンでDBへの接続を保持して、servletからの要求に応じて接続を貸し出す仕組みで、暇な時に再接続もしてくれる、J2EEに標準装備されている一般的なものである。勿論Tomcatでも使える。
しかし、Tomcatのマニュアルの"JNDI Datasource HOW-TO"のページを見ると、設定方法がかなり複雑である。もっと簡単な解決方法は無いかと思ってConnector-Jのマニュアルを眺めていると、autoReconnectという設定項目が見つかった。DBとの接続が切れていればドライバが自動的に再接続するという設定だ。Obsoleteなproperty(将来削除される設定項目)であり、SQLExceptionを処理できない場合以外は非推奨と書かれているが、今回はとりあえず目的が達成できれば良かったので、

conn = DriverManager.getConnection("jdbc:mysql:/
/localhost/xxxx?user=xxxx&password=xxxx&autoReconnect=true");

という風にautoReconnectの設定を追加した。

実にシンプルかつスマートに解決できたと思っていたが、Tomcatのログを見て、時々エラーが発生していることに気付いた。調べてみると、どうやらautoReconnectの設定をしていても、DBとの接続が切れると、1回はクエリ送信が例外(SQLException)で終わり、それによって再接続され、その次のクエリから接続が有効になるようだ。
そこで、1回はSQLExceptionに対して再試行する、ということも考えたが、そもそもSQLExceptionをcatchして再接続するのならautoReconnectの機能を使う意味が無い、connection poolingの方がスマートだろう、と考えてやめた。

という訳で、connection poolingを試すべく、TomcatのHowToの通りに設定を書き、テストプログラムをコンパイルすると、javax.sql.*が無いというエラーになった。一瞬で確信した通り、プールされたconnectionを取り出すためのDataSourceクラスが1.3のJVMには存在しないのだった(javax.sqlは1.4以降の仕様らしい)。

途方に暮れてさらにConnector-Jのマニュアルを読み進めると、"Common Problems and Solutions"の所に答えが書いてあった。Connection poolingするか、autoReconnectにした上でSQLExceptionをcatchし、MySQLのエラーコードを調べて、回数の上限を決めて再施行するか、のどちらかをする必要があるそうだ。SQLException#getSQLStateでMySQLのエラーコードがわかり、MySQLのinfoによると接続系のエラーが起こるとSQLStateが"08S01"になるらしいので、SQLStateが"08S01"の場合に最大2回クエリを再施行することで解決した。

int retryCount = 3;
boolean transactionCompleted = false;
do{
try{
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("xxxx");
transactionCompleted = true;
}
catch(SQLException e){
String sqlState = e.getSQLState();
if (sqlState.equals("08S01")){
retryCount--;
}else{
retryCount = 0;
}
}
finally{
...
}
} while(!transactionCompleted && retryCount > 0);

See more ...

Posted at 17:59 in Java | WriteBacks (0)
WriteBacks

Apr 20, 2008

玄箱+Debianで試験運用中

このウェブサイトを支えているPentium1機の動作が不安になってきたことと、ノートPCだが古いので消費電力が気になってきたこともあって、かねてから気になっていた玄箱を(主に玩具としてだが)購入した。
Web上に情報が豊富にあるため、Debian化はすぐにできた。さらに、Debianのパッケージ管理ツール(apt-get/aptitude)がとてもわかりやすく便利なので、webサーバとしてのセットアップも大体楽にできた。玄箱のこともDebianのことも全く何も知らなかったが、結局組み立てから1週間で最低限のwebサーバ化ができた。

MovableType、Tomcatの移行もできたので、試しにしばらく玄箱でこのサイトを動かすことにした。

しかし、早速いくつか問題が起こっている。
CPUの速度は、計算だけのベンチマークテストでは玄箱(PowerPC 200MHz)の方が倍くらい速いようだが、実用上の動作は玄箱の方が遅いケースもある。CGIの動作は常に玄箱の方が遅い。ApacheもPerlも同じバージョンで、設定にも大差ないので、OSが遅いかPerlが遅いくらいしか原因が思いつかない(Pentium 1機のFreeBSDの方はカーネルの再構築もPerlの再コンパイルもしている)。

RAMが少ないのも問題だ。玄箱はRAMが64Mしかないので、Tomcatやmod_perlのMovableTypeを動かすとswapが多発する。例えば、Tomcatを動かしながらcpanを動かすと、時々シェルがキー入力に反応しなくなり、リモートから操作困難になった。

RAM 128Mの玄箱PROを買うべきだったか?

See more ...

WriteBacks

Tomcat5をなるべく実メモリ少なく動かすために、Java VMをいくつかインストールして試してみた。 Java VMの使用RAM領域を完全にスワップアウトしてから小さいservletを動かす方法で、GCJとKaffeとIBM Java VM(for 32bit-ppc ver.142 SR9)(全てJDK1.4.2互換)を比較した所、Tomcat5のservletの動作に必要な実メモリの最小値は、GCJとIBM Java VMが約10M、Kaffeが約6Mだった。 但し、Kaffeは起動時間が半端じゃなく長い。GCJやBM Java VMで2~3分のTomcatの起動に1時間以上かかる。KaffeのJavaコンパイラも強烈に遅いので、残念ながら玄箱では使い物にならない。

Posted by ynomura at 04/26/2008 12:45:34 AM

もう少しMebius MN-340(Pentium 150MHz, RAM 96M)と玄箱の性能を比べてみると、PerlのCGIの動作速度がほぼ互角な以外は、全体的に玄箱の方が1.2~2倍くらい速いようだ。特に、MN-340はHDDが遅いため、swapが比較にならないほど遅いことがわかった。10年以上前に買ったマシンだけのことはある。色々実験していて、64Mのmallocに1分以上かかったのには笑ってしまった(40GのHDDを積んだ玄箱では遅くても4秒くらいで終わる)。RAMを多目に増設していたので、これまでかなり救われていたようだ。

Posted by ynomura at 04/26/2008 08:18:35 PM

Apr 19, 2008

MySQLと日本語文字

MySQLで日本語文字を使うために調べたことを記録する。

●基本事項
サーバーではデータベース毎に文字コードセット(コーディング)を決めることができる(表毎、列毎の指定も可能)。またサーバー~クライアント間のデータ送受信時に使用する文字コードセットは随時変更することができる。
従って、DB上の文字列の文字コードに関わらず、クライアントから所望の文字コードセットで文字列を送受信することが可能である。

MySQLはコンパイル時(configure時)にデフォルトで全ての文字コードセットに対応している訳ではないので、日本語文字コードセットに対応するようにコンパイルする(configure実行時に--with-charsetオプションを付ける)必要がある。

動作中のMySQLで使用できる文字コードセットは、クライアントにて

SHOW CHARSET;
で見ることができる。

●サーバー側の文字コードセット指定
デフォルトの文字コードセットは、my.cnfの[server]エントリーのdefault-character-setで指定できる。
特定のDBに対しては、CREATE DATABASEまたはALTER DATABASE文にてCHARACTER SETオプションで指定できる。

●クライアント側の文字コードセット指定
クライアントで使用する文字コードセットは、

SET NAMES '文字コードセット名';
で指定できる。EUCは'ujis'、UTF-8は'utf8'、Shift-JISは'sjis'である。
実際には、これによって関係する複数の環境変数が同時に更新される。
文字コードセットに関係する環境変数の値は、
SHOW VARIABLES LIKE 'character%';
とすると全て見ることができる。

なお、MySQLのinfoを含め色々な所にmy.cnfの[client]エントリーのdefault-character-setにてデフォルトの文字コードセットを設定できるようなことが書かれているが、筆者の環境(FreeBSD 4.11+MySQL 5.0.27)ではその設定は反映されない。Webを見ると、他でも同じ現象が少なからず発生しているようだ。my.cnfにてこれを設定する場合は、1回mysqlを起動して環境変数が意図通りに変わっているかどうか確認しておくべきだと思う。

●LOAD DATA INFILEで使われる文字コードセット
MySQLのLOAD DATA INFILEでファイル読み出し時に使われる文字コードはcharacter_set_databaseの値であり、上述のSET NAMESでは更新されない。従って、対象ファイルにてデフォルトでない文字コードが含まれる場合は、先に

set character_set_database=文字コードセット名;
のようにして設定する必要がある。
なお、文字コードによっては区切り文字の判定に曖昧さが出る可能性があるため、日本語文字列は""(ダブルクォート)で括ってLOAD DATA INFILE文にFIELDS ENCLOSED BY '"'(シングルクォート2つの間にダブルクォート1つ)を指定するなどした方が良いと思う。
(参考:http://www.hirohama.biz/mysql/2008/01/31-100131.html

See more ...

Posted at 21:27 in PC一般 | WriteBacks (0)
WriteBacks

Apr 17, 2008

Javaと日本語文字

Javaアプレットとサーブレットを作っていて、日本語文字、エンコーディング関連でつまずいたが何とか解決したことを記録する。
ここに書くものは、1つの成功例であって、最善の方法であるとは限らない。

●Servletからappletへの日本語文字列の受け渡し
Applet-servlet間の通信は通常HTTPで行うことになるので、appletからはjava.net.URLConnectionを使うとする。
Servlet側では、javax.servlet.HttpServletResponse#setContentType()でエンコーディングを指定する。
例:

public void doGet(HttpServletRequest req, HttpServletResponse res){
res.setContentType("text/html; charset=utf-8");

Applet側では、InputStreamReaderを使って、送られてきた文字列を読み出す。InputStreamReaderの作成時にエンコーディングを指定する。
例:
URL url = new URL("http://...");
URLConnection uc = url.openConnection();
InputStreamReader isr = new InputStreamReader(uc.getInputStream(), "utf-8");
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();

●ソースコードへの日本語文字の記述
Swingのラベルや標準出力への出力文字列など、プログラムが表示する日本語文字をソースコードに直接埋め込む場合は、javacコマンドでコンパイルする時に、ソースコードで使用しているコーディングの名前を-encodingオプションで指定する。
例:

javac -encoding EUC-JP eucfile.java

コーディング名は、日本語だと"EUC-JP","ISO-2022-JP","UTF-8","Shift_JIS"などがある。
(参考:encoding.doc

●APIからの日本語文字の取得
JDBC等のAPIがJava内部で使用するものでないコーディングで文字コードを返す場合は、文字コードの変換が必要になる。Stringクラスのコンストラクタを使うとJavaのコーディングに変換できる。
例:

ResultSet rs;
String jstr = new String(rs.getBytes(1), "euc-jp");

●LinuxでJavaアプレットを表示すると日本語フォントが出ない(□になる)場合の対処
Mozilla上のJavaアプレットの表示がそうなるのも、appletviewerでそうなるのも共通の原因で、JREに適切にフォントパスを設定すると解決すると思われる。
JREのインストールディレクトリのlib/fonts/の下にfallbackというディレクトリを作成し、日本語を含むフォントファイルへのシンボリックリンクを置く。
例:

cd /usr/java/jdk1.5.0_07/jre/lib/fonts
mkdir fallback
ln -s /usr/share/fonts/ja/TrueType/kochi-gothic.ttf fallback/
ln -s /usr/share/fonts/ja/TrueType/kochi-mincho.ttf fallback/

(参考:JavaSE 5.0のリリースノート (日本語)(English)

See more ...

Posted at 21:56 in Java | WriteBacks (0)
WriteBacks

Apr 13, 2008

JDBC+servlet連携appletを作ってみた

せっかくこの長老マシンに苦労してMySQLとTomcatをインストールしてJDBCとservletが動くようになったのに、これまで簡単なテストプログラムしか動かしておらず、有効活用できていなかったし、あまり勉強にもなっていなかったので、Javaの復習とSwingの勉強を兼ねて、JDBC servletと連携するJavaアプレットを作ってみた。
3択首都当てクイズのページへ
アプリケーションのネタが思いつかなかったので、とりあえず3択クイズにした。

Servletとappletの役割分担は、MVCモデルに従って、クイズの作成、正誤判定、終了判断といったモデルは全てservlet側で行い、applet側は表示だけを行うようにした。すなわち、Mをサーバー側、VとCをクライアント側に分けた。そのため、appletは1問ずつ問題をservletから取得し、解答者の選択をservletに送っている。クイズの終了判定もservletが行うため、servletから問題でなく成績表が送られてくると終了という設計にした。

セッション管理にはjavax.servlet.HttpSessionを使っており、複数のクライアントが同時にクイズを始めても、servletで全てのクライアントの途中経過が管理される。Cookieが無効にされているとサーバー側からセッションIDが送られ、クライアント側でURLのquery部にセッションIDを付けるようにしているので、CookieがOFFでも動くはず。

クイズの問題はMySQLのデータベース上にあり、servletは1問ずつJDBC経由で問題を取得する。1つのセッションの中で問題が重複しないように、出題する問題の番号だけはセッションの新規作成時に全て決めておくようにした。

クライアント側の描画にはSwingを使用した。CardLayoutでペインを切り替えるようにしている。クイズ画面の動く背景は、SwingタイマーとAWTを使って描画している。最初はこの背景描画が非常に重く、試行錯誤して改良している内に、事前に画像ファイルを用意してタイル貼りする方が負荷が軽いことが判明したが、画像ファイルを作成するのが面倒なので、そこまではしなかった。

See more ...

Posted at 20:56 in Java | WriteBacks (2)
WriteBacks

よければjavaのソースを教えてください。

Posted by ななし at 01/11/2012 01:57:35 PM

ここに置いてます。

Posted by ynomura at 01/11/2012 09:33:20 PM

Apr 02, 2008

Tomcat起動時のエラーログ

TomcatでJavaのservletを動かす実験をしていて、ログを見ると、servletは正常に動くのにTomcatが起動時に下のようなもので始まる大量のエラーを出してることに気付いた。

[ERROR] Digester - Parse Error at line 37 column 15: The content of element type
"servlet" must match "(icon?,servlet-name,display-name?,description?,(servlet-c
lass|jsp-file),init-param*,(以下略)

"Parse"とあるので私が追加したweb.xml(XML形式の設定ファイル)がおかしいのだろうとは思ったが、そもそも例えば上で書かれてるような37行目に"servlet"という文字列は無いし、"must match ..."とか言われても、"servlet-name"エレメントと"servlet-class"エレメントしか使ってないので、きちんとmatchしてるように思え、何が悪いのかわからなかった。しばらくしてやっと、上記の右側が正規表現ぽいことから、"servlet"エレメントの中身がicon, servlet-name, display-name, ...がこの順でないといけない、"?"がついてるエレメントはあっても無くても良い、servlet-classまたはjsp-fileのどちらか1つが必須である、という意味だと気付いた。

<servlet>~</servlet>の間に<servlet-name>と<servlet-class>を交互に書いていたのが問題だったようだ。同様に、<web-app>~</web-app>の間では、<servlet>と<servlet-mapping>を交互に置いてはいけないらしい。

See more ...

WriteBacks