いよいよクラスライブラリ活用編も大詰めの今回は「データベース編」です。なんといっても、サイトにさまざまな可能性を与え、高度なデータ処理を支えるのはデータベースサーバをおいてほかにありません。
もっとも、データベース連携だからといって決してかまえることはありません。本稿をご覧いただければきっとご理解いただけるように、データベース連携を司るjava.sqlパッケージ(JDBC)の用法は、これまでとなんら変わることなく、極めて定型的です。いくつかの定石的なアルゴリズムをさえ押さえてしまえば、8割方の用途は十分に満たせるはずです。データベースは決して難しいものではありません。
ただ、ある一定量を超えたデータの操作に対して常にパフォーマンスを維持するためには、やはりそれなりのテクニックが求められるでしょう。もしもデータベース連携プログラミングに難しい点があるとしたら、その時々のボトルネックを適切に把握し、数ある手法の中から最適な1つを「選ばなければならない」という点にあると思います。本稿では、そんな使い分けのポイントも意識して、データベース連携の主要なアプローチをご紹介します。
注:本稿でご紹介するサンプルを実行するには、あらかじめサーバ環境上にMySQLをインストールしておく必要があります。MySQLのインストール方法に関しては、筆者サポートサイト「サーバサイド技術の学び舎 - WINGS(http://www.wings.msn.to/)」より「サーバサイド環境構築設定(http://www.wings.msn.to/redirect.php/-/B-08/)」などを参考にしてみてください。
編集注:本記事は、データベースに関連するクラスライブラリの基本的な利用方法を解説しています。よって、実際のアプリケーション開発においては、さらに応用的な知識が必要になりますのでご注意ください。また、同様の趣旨により、プログラム内では例外処理を割愛しています。これはすべての処理が問題なく動作した場合は問題ありませんが、データベースが起動していない、ネットワークの不通等で接続できない場合などを想定した場合、適切な例外処理を行いう必要があります。
データベースの内容を読み込む −Connection/Statement/ResultSetインターフェイス−
まずはごく基本的なデータベースアクセスの手法からです。Connection/Statement/ResultSetという、java.sqlパッケージの中でも最も利用頻度の多い3つのインターフェイスを使って、データベース検索結果の表示を行ってみることにしましょう。
以下が検索結果を表示するコードです。
<%@ page contentType="text/html; charset=Shift_JIS" import="java.sql.*,java.text.*" %> <table border="1"> <tr> <th>書名</th><th>筆者名</th><th>出版社</th> <th>価格</th><th>出版日</th> </tr> <% Class.forName("org.gjt.mm.mysql.Driver"); Connection db=DriverManager.getConnection("jdbc:mysql://localhost/sample? user=root&password=root&useUnicode=true&characterEncoding=Shift_JIS"); db.setReadOnly(true); Statement objSql=db.createStatement(); ResultSet rs=objSql.executeQuery("SELECT * FROM books"); DecimalFormat objFmt=new DecimalFormat("#,###円"); while(rs.next()){ %> <tr> <td><%=rs.getString("title")%></td> <td><%=rs.getString("author")%></td> <td><%=rs.getString("published")%></td> <td align="right"><%=objFmt.format(rs.getLong("price"))%></td> <td><%=rs.getDate("publishDate")%></td> </tr> <% } rs.close(); objSql.close(); db.close(); %> </table>
上記のコードを実行すると以下のような結果が得られます。
ロジックの概要
すべてのデータベース操作に先立ってまず行わなければならないことは、データベースサーバへの接続です。接続には大きく2段階のステップが必要です。
最初にClass.forNameメソッドによるJDBCドライバのロード。標準のコアAPI(java.sqlパッケージ)に含まれるのはあくまでデータベース接続への「取り決め」(インターフェイス)と接続ドライバを管理するためのマネージャクラスであるにすぎません。個別のデータベースサーバへの接続機能を提供するのは、あくまでも「JDBCドライバ」と呼ばれるものなのです。
主要なデータベースサーバと、それに対応するJDBCドライバの入手先は以下のとおりです。
*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***
forNameメソッドによって有効化されたJDBCドライバはすでにインスタンス(オブジェクト)化されていますので、あらためてnew演算子でオブジェクト生成する必要はありません。DriverManager#getConnectionメソッドが、指定された「接続文字列」に基づいて適切なドライバを選択し、データベースへの接続を確立します。
接続するデータベースサーバによって接続文字列の書式は異なりますが、以下ではMySQLに接続する場合の書式を表しておくことにしましょう。
jdbc:mysql://ホスト名/データベース名[?属性情報]
属性情報とは接続するための付加的な情報であり、接続時に使用するユーザIDやパスワードなどを「属性名=属性値」のセットで指定します。複数の属性は「&」で接続します。末尾の「useUnicode=true&characterEncoding=Shift_JIS」の部分は、文字化けを防ぐためのおまじないのようなものだと思っておけばよいでしょう。通常、JDBCドライバは入力文字列を適切な文字エンコーディングで自動処理しますが、環境によってはそれがうまく動作しないことがあります。そこで、属性情報で文字エンコーディングを明示的に宣言しておくのです。ここでは「Shift_JIS」としていますが、もちろんUnix系OS環境でEUC-JPを使用している場合には適宜書き換えてください。
データベースへの接続が完了したら、いよいよ具体的なSQL命令をデータベースに対して発行してみます。SQL命令を実行するには、まずSQL命令を管理するためのStatementオブジェクトを生成する必要があります(Connection#createStatementメソッド)。Statementオブジェクト自体は生成された直後の時点で、コマンドを格納するためのただの器であるにすぎませんので、これに対して具体的な命令をセットし、実行を促してやる必要があります。
それがexecuteQueryメソッドの役割です。executeQueryメソッドは指定されたSQL命令を実行し、データベースから抽出されたレコードセット(結果セット)を表わすResultSetオブジェクトを返します。ちなみに、INSERTやUPDATE、DELETE命令のように結果セットを返さないSQL命令を実行する場合には、executeUpdateメソッドを使用します。executeUpdateメソッドは戻り値としてSQL命令の結果、影響を受けたレコード数を返します。
取得した結果セットは、ResultSet#nextメソッドとgetXxxxxメソッドとの組み合わせで先頭レコードから順番に読み取っていくことができます。結果セットはメモリ上に展開された仮想的な部分テーブルのようなものです。内部的に現在のレコード位置を表わす「レコードポインタ」を保持しており、結果セットの内容を読み込む場合にはこのポインタを移動させながら1レコードずつ読み込んでいく必要があります。
結果セットが生成された初期のタイミングで、ポインタは結果セットの先頭に位置しています。つまり、結果セット内のすべてのレコードにアクセスしたいと思ったら、ResultSet#nextメソッドでポインタを順に後方に移動させつつ、getXxxxxメソッドでカレント(現在の)レコードの各フィールド値を読み込んでいけばよいということになります。繰り返しループは、nextメソッドがfalseを返す(次のレコードが存在しない)タイミングで終了します。
ちなみに、getXxxxxメソッドには取得するデータ型ごとにgetArray、getAsciiStream、getBigDecimal、getBinaryStream、getBlob、getBoolean、getByte、getBytes、getCharacterStream、getClob、getDate、getDouble、getFloat、getInt、getLong、getObject、getRef、getShort、getString、getTime、getTimestamp、getUnicodeStream、getURLなどが用意されています。getXxxxxメソッドはフィールド値が指定されたデータ型に変換できない場合には例外を返しますので、注意してください。
データベースや結果セットは明示的にクローズすべきか
データベースや結果セットなどのリソースは、処理の実行後、不要になった任意のタイミングで自動的にガーベジコレクト(廃棄物処理)されます。従って、明示的な解放を表わすcloseメソッドの呼び出しは「必須ではありません」。
ただし、不要なリソースをいつまでもメモリ上に残すということは、限りあるサーバリソースを浪費するという意味でも好ましいことではありません。また、あらかじめいくつかの接続を確保しておき、複数の要求で使い回す「コネクションプール」(次回以降でご紹介します)の機能を利用している場合、closeメソッドの実行によって接続がプールに戻されます。つまり、closeメソッドを明示的に宣言しないと、プール上の接続がすぐに不足してしまう可能性があるということです。
以上のような理由からも、リソースのクローズは必ず行う癖をつけておきたいものです。
データベースを読み取り専用で開く
今回の例のように、データベースに対して検索(読み取り)処理しか行わないことがあらかじめ分かっている場合、データベースを読み書き可能な状態で開くことはリソースの無駄遣いです。書き込みを行うということは、ほかからの同時書き込みがあった場合にレコードに矛盾が生じないように監視する(ロックする)ということでもあり、そこに要する負荷は決して少ないものではありません。
そこで、あらかじめ読み取り処理しか行わないことが分かっている場合には、データベースを読み取り専用で開くことで必要なリソースを極小化することができます。
Connection#setReadOnlyメソッドは、引数をtrueに設定することで、データベース接続を読み取り専用でオープンします(デフォルトはfalse)。ただし、setReadOnlyメソッドはトランザクションの実行中には呼び出すことができませんので、注意してください。
取得した数値を整形するDecimalFormatクラス
数値データを出力する場合、表記フォーマットを統一したいというケースがよくあります。小数点以下のけた数をそろえたい、千の単位でけた区切りしたい、あるいは負数の表記を「▲123456」のようにしたい、などなど。
これをいったん文字列変換し、加工してやらなければならないとしたら大変面倒くさいことですが、Javaにはあらかじめ便利な専用のクラスが用意されています。それがDecimalFormatクラスの役割です。使い方は極めて簡単です。
DecimalFormat objFmt=new DecimalFormat("#,###円"); …中略… <td align="right"><%=objFmt.format(rs.getLong("price"))%></td>
のように数値フォーマットを指定し、formatメソッドを実行するだけです。formatメソッドの引数には任意の数値型を指定することができます。これによって、上の例では「2,800円」のような「単位けた区切り+通貨単位」の形式で数値データが出力されるというわけです。
数値フォーマット式として指定可能な識別子は、以下のとおりです。
*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***
One Point
データベースへの接続/命令の発行にはConnection/Statementインターフェイスを使用します。取得した結果セットはResultSetインターフェイスを介して、読み取ることが可能です。
Copyright © ITmedia, Inc. All Rights Reserved.