第5回 データベースのブラウザを作る


J2EEでのデータベースとの接続 

 説明の順序が逆になったのですが、先のJSPのコードでは前半部分が省略されています。Scriptletの最初の方に、connという変数が登場しているのですが、この変数は、Connectionという型を持っています。データベースで検索を実行するためには、まず、データベースとの接続が行われていなければなりません。Connection conn ; は、まさにデータベースとの接続のために必要なオブジェクトです。

 ここでは、J2EEでのデータベースへの接続について、基本的なことを見ておきたいと思います。先ほど、実際には最も大事といっていいJDBCでのQueryの処理について、「定型的な処理です」と簡単に片付けておいて、あらためてデータベースの接続の話題を取り上げるのには、実は理由があります。JDBCの処理やデータベース接続については、JDBCのドキュメントに説明があるのですが、J2EEのもとでのデータベース接続については、ちょっとしたTipsが必要です。しかも、それは、分かりやすい形では文書化されていません。今回のテーブル・ブラウザを動かすためには、deploy時にその知識が必要なのです。

 

 ドライバ・マネージャを利用した接続

 最初に確認したいのは、J2EEは、JDBCを使ってデータベースと接続するのですが、それには2つの方法があるということです。

 1つの方法は、次のようにドライバ・マネージャを利用する方法です。ここでは、まず、COM.cloudscape.core.RmiJdbcDriverというJDBCのドライバをロードして、ドライバ・マネージャに対してデータベースのURLを指定し、コネクションを獲得しています。このURLを見れば、データベースが、どこで(locahost)何番のポートで(1099)動いているのかが分かります。

try{
  Class.forName("COM.cloudscape.core.RmiJdbcDriver");
  conn = DriverManager.
  getConnection("jdbc:rmi://localhost:1099/
  jdbc:cloudscape:CloudscapeDB", "", "" );
}
  catch( Exception e ){
  System.out.println( e );
}

 この方法は、直接的で分かりやすいのですが、次のような問題があります。こうしたコードを、プログラムの中に直接組み込んだとしましょう。そうすると、このプログラムは、データベースの稼働すベきホストやポートの情報がハード・コードされていることになりますので、開発者以外の人の環境で動かすことは難しくなります。開発者自身にとっても、マシンの環境が変わるのはよくあることですね。はっきりしているのは、こうしたコードを含んだプログラムは、商品にはできないということです。

 

 データソースを使って接続する

 JDBC 2.0からは、データソースを使った次のようなコードが可能となりました。この方式のポイントは、コードの中にデータベースの情報を直接ハード・コードすることをやめて、そうした情報をJNDIを通じて実行時に獲得しようとすることです。プログラムの中には、データベースのリソースに対応するバーチャルな「名前」が書いてあるのですが、それが実際にはどのようなリソースを指すのかは、プログラムの側ではなく、アプリケーションのdeploy時に設定可能です。

try {
  InitialContext ic = new InitialContext();
  DataSource datasource = (DataSource)   
ic.lookup("java:comp/env/jdbc/EstoreDataSource");
  conn = datasource.getConnection();
} catch( Exception e ){
  System.out.println( e );
}

 このコードの先頭のnew InitialContext()というのは、JNDIとの接続に必要なオブジェクトを作り出しています。こうして生成されたオブジェクトに対して名前でlookupをかけると、JNDIはJNDIサーバに登録されているオブジェクトを返します。

 J2EEでは、このようにデータベースの接続にJNDIが使われるだけではなく、EJBのHomeインターフェイスを実装したオブジェクトを獲得するのにも、また、JMSでのメッセージングにもJNDIが利用されます。J2EEを構成する個々の要素技術については、いろいろ語られることが多いのですが、JNDIは脇役です。ただ、J2EEで、これらの技術を結び付けているのは、JNDIだと考えることもできます。今度、機会があったらJNDIを縦糸にしてJ2EEを解説してみたいと思っています。イニシャル・オブジェクトを作ってJNDIサーバに接続して、文字列で与えられる名前でルックアップしオブジェクトを獲得するというこのパターンは、J2EEでアプリケーションを書くうえでは必須のテクニックです。

 

 リソース・プロパティ・ファイル

 J2EEでのJNDIを使ったデータソースの利用では、1つ注意してほしいことがあります。
J2EEのバージョンで多少の違いがあるのですが、最新のJ2EE 1.3を例にして説明しようと思います。J2EEをインストールしたディレクトリにconfigという名前のディレクトリがあります。その中に、resource.propertiesというファイルがあると思います(J2EE 1.3以前のバージョンには、このファイルは存在しません。こうしたバージョンでは、以下に述べるデータベースのリソースの定義が、configディレクトリ以下のdefault.propertiesに入っています)。

 このファイルは、J2EEサーバの起動時に呼び込まれて、JNDIサーバの初期設定に利用されます。このファイルをよく見ると最初の何行かは、2行で1組になっていることが分かります。最初の行がデータベースにデータソースとしての名前を与え、次の行がそのデータソースの実体をなすデータベースのURLを与えています。この例では、番号0から番号4までの5つのデータソースが定義されています。データソースの名前はそれぞれ異なっているのですが、URLの部分を見ると皆同じことが分かると思います。


ファイル %J2EE_HOME%\config\resource.properties
jdbcDataSource.0.name=jdbc/Cloudscape
jdbcDataSource.0.url=jdbc:cloudscape:rmi:CloudscapeDB;create=true
jdbcDataSource.1.name=jdbc/EstoreDB
jdbcDataSource.1.url=jdbc:cloudscape:rmi:CloudscapeDB;create=true
jdbcDataSource.2.name=jdbc/InventoryDB
jdbcDataSource.2.url=jdbc:cloudscape:rmi:CloudscapeDB;create=true
jdbcDataSource.3.name=jdbc/DB1
jdbcDataSource.3.url=jdbc:cloudscape:rmi:CloudscapeDB;create=true
jdbcDataSource.4.name=jdbc/DB2
jdbcDataSource.4.url=jdbc:cloudscape:rmi:CloudscapeDB;create=true
jdbcDriver.0.name=COM.cloudscape.core.RmiJdbcDriver
..........
..........


 

 データソースの「2つの名前」

 このリソース・プロパティ・ファイルの中で、データソースの「名前」と呼ばれているものと、データソースを利用する接続の次のようなコードの中のlookupで使われている「名前」とは、全然一致していませんね。

DataSource datasource = (DataSource) ic.lookup("java:comp/env/jdbc/EstoreDataSource");

 この不一致は、もともとお互いに関係のないサンプルを2つ突き合わせたから生まれたものではありません。問題は、今回作ろうとしているテーブル・ブラウザのJSPコードの一部とそれが実際に動作する環境のリソース・プロパティ・ファイルとの間にこうした不一致があるのです。

 少し、種明かしをしようと思います。まず、J2EEのJNDIサーバは、ユーザーが与えた名前の先頭に必ずjava:comp/env/という文字列を追加します。ですから、lookupの引数に与えられている文字列からこの文字列をさっぴくとjdbc/EstoreDataSourceという文字列が残ります。問題は、この名前がリソース・プロパティ・ファイルの中に存在するかということです。残念ながら、ファイル中の5つの名前の中にはjdbc/EstoreDataSourceという名前は見つかりません。

 もう1つ種明かしをしないといけません。実は、J2EEではデータベースは2つのデータソース名を持っているのです。1つは、コンポーネント側のコードで利用される「名前」で、lookupメソッドの呼び出しでは、先頭にjava:comp/env/という文字列を付け加えて利用される「名前」です。もう1つは、リソース・プロパティ・ファイルで定義されて、J2EEサーバの立ち上げ時にJNDIサーバの初期化に利用される「名前」です。

 

 deploy時に、deploytoolで
  2つの名前を結び付ける

 前者の「名前」は、リソース参照(Reference)の「コードされた名前(Coded Name)」あるいは、「参照名(Reference Name)」と呼ばれ、後者の名前は、「JNDI名(JNDI Name)」と呼ばれています。deploytoolは、この2つの名前を結び付ける働きを持っています。

 Webコンポーネントを作るウィザードの中でもこの指定はできるのですが、ウィザードで指定できるものはdeploytoolの中からでも指定できます。筆者は、ウィザードの指定は、適当なところで切り上げて、あとでdeploytoolのタブを開いてdeployの環境設定をするのが便利だと思っています。

 図3を見てください。左側のツリーでWebコンポーネント(エメラルド色のビーンがビンに入っているやつです)を選択して、Resource Refsというタブを開きます。Coded Nameの部分には、JSPの中で、lookupの引数に指定した名前が入っています。最初は、下の段のJNDI Nameの部分はマスクがかかっているのですが、上の列をクリックすると、マスクが外れて入力可能になります。ここでは、リソース・プロパティ・ファイルからjdbc/EstoreDBという名前を選んでいます。

図3 Resource Refsタブを開いたところ

 WebコンポーネントのJNDI Namesタブ(図4)や、アプリケーションのJNDI Namesタブ(図5)でも、この2つの名前の結合が可能です。

図4 WebコンポーネントのJNDI Namesタブを表示したところ

図5 アプリケーションのJNDI Namesタブを表示したところ


 

 テーブルの属性をシステム・テーブルから読む

 呼び出し側の処理の大まかな流れは見てきました。今度は、テーブルの属性やテーブルの内容を表示する処理を見ておきましょう。まず、テーブル属性の表示ですが、次のSQL文は、Cloudscapeで、テーブルITEMの項目名、項目のテーブル内の順番、項目のデータ型、また、この項目がNULLの値を取り得るかの情報を表示(図6)します。

SELECT c.COLUMNNAME, c.COLUMNNUMBER,
  c.COLUMNDATATYPE.getSQLstring(),
  c.COLUMNDATATYPE.isNullable()
FROM SYS.SYSCOLUMNS c, SYS.SYSTABLES t
WHERE c.REFERENCEID = t.TABLEID
  AND t.TABLENAME = 'ITEM'
ORDER by c.COLUMNNUMBER

図6 ITEMの項目を表示したところ

 いくつか説明が必要です。まず、このSQL文では、SYS.SYSCOLUMNSSYS.SYSTABLESという2つのテーブルが使われています。このJ2EEアプリケーション自身を使った、2つのテーブルの内容の表示は、次のようなものです(SYS.SYSCOLUMNS(図7)、SYS.SYSTABLES(図8))。SYS.SYSCOLUMNSは、データベース中のすべてのテーブルのすべての項目をまとめたテーブルです。

図7 テーブル「SYS.SYSCOLUMNS」

図8 テーブル「SYS.SYSTABLES」

 テーブルの先頭のREFERENCEID項目は、この項目が属するテーブルのTABLEIDです。この出力の最初の2行のCOLUMNNANEがSCHEMAIDとTABLEIDのREFERENCEIDは80000010-00d0-fd77-3ed8-000a0a0b1900で、SYS.SYSTABLESの出力の2行目の、SYSTABLESのTABLEIDと一致していることが分かりますね。これが先のSQL文でのジョインを説明します。最後の、ORDER BY句は行の出力をテーブル中での項目の順番で並べ替えるものです。

 

 CloudscapeのJava対応拡張機能

 ジョインや行の整列は、SQLとしては普通のことですが、先のSelect文には、おそらく、皆さんがあまり見たことのないようなシンタックスが含まれています。Selectの後ろの、項目が並ぶ部分の3番目と4番目の次のような表現は、Cloudscape独自のものです。

c.COLUMNDATATYPE.getSQLstring(),
c.COLUMNDATATYPE.isNullable()

 SYS.SYSCOLUMNSテーブルの属性(図9)を見てみれば、COLUMNDATATYPEという項目のデータ型はSerializableで、型はCOM.cloudscape.types.TypeDescriptorというように表示されているのが分かりますでしょうか? Cloudscapeでは、テーブルの項目にJavaのオブジェクトが置けるというのが大きな特徴です。この例では、テーブルSYS.SYSCOLUMNSの中の項目COLUMNDATATYPEからJavaのオブジェクトを取り出して、そのオブジェクトに対してメソッドgetSQLstring()とメソッドisNullable()を適用して、その結果をSelect文が返すということになります。

図9 COLUMNDATATYPEE項目のデータ型はSerializableで、型はCOM.cloudscape.types.TypeDescriptorとなっている

 

 HTMLでテーブルを作る

 HTMLでテーブル(データベースのテーブルではありません)を作るには次のようなタグを使います。tableタグやtrタグは、<table bgcolor="#336666"> <tr bgcolor="#aacd88">のような形でテーブルの背景色を指定することができます。

<table>
<tr> <th>header1</th> <th>header2</th> <th>header3</th> ..... </tr> // 見出し部分
<tr> <td>data11 </td> <td>data12 </td> <td>data13 </td> ..... </tr> // データ部分1行目
<tr> <td>data21 </td> <td>data22 </td> <td>data23 </td> ..... </tr> // データ部分2行目
<tr> <td>data31 </td> <td>data32 </td> <td>data33 </td> ..... </tr> // データ部分3行目
...................
...................
<tr> <td>datan1 </td> <td>datan2 </td> <td>datan3 </td> ..... </tr> // データ部分n行目
</table>

 見出し部分に、データベース・テーブルの項目名を置き、データ部分にSelectで検索した結果を置くとWebのページのテーブルができます。これで準備ができました。HTMLのテーブルを出力するJSPコードを見てみましょう。

 ここでは、ResultSetのインスタンスに対する、次のようなメソッドrs.getString(i)に注目してください。以前の例ではrs.getString("SCHEMANAME")のような形で、直接に項目名を文字列で指定して項目の値を獲得していましたが、このように、テーブル内での項目の位置を指定して項目の値を得ることもできます。ここでのテーブル内の項目の位置とは、項目に振られたCOLUMNNUMBERにほかなりません。

<table bgcolor="#336666">
<tr bgcolor="#aacd88">
  <th>COLUMNNAME</th>
  <th>NUM</th>
  <th>DATATYPE</th>
  <th>Nullable</th>
</tr>
<%
  try {
    query = "SELECT c.COLUMNNAME, c.COLUMNNUMBER,"
    + " c.COLUMNDATATYPE.getSQLstring() ,"
    + " c.COLUMNDATATYPE.isNullable() "
    + "FROM SYS.SYSCOLUMNS c, SYS.SYSTABLES t "
    + "WHERE c.REFERENCEID = t.TABLEID "
    + " AND t.TABLENAME = '" + table + "'"
    + "ORDER by c.COLUMNNUMBER ";

    ResultSet rs = stmt.executeQuery( query );
    while( rs.next() ){
      out.println("<tr bgcolor=\"#eeebcc\">");
      String s = "" ;
      for ( int j=0; j < 4 ; j++)
      s += "<td>" + rs.getString(j+1) + "</td>";
      out.println(s+"</tr>");
    }
  }
    catch( SQLException e ){
      System.out.println( e );
  }
%>
</table>

 

 四角テーブルについてのメタデータの利用

 このAttributesの出力テーブルでは、見出しは4つに固定されています。これは、SQLのSelectで、4つの項目の検索を行っていたからです。具体的なテーブルの検索では、テーブルのどの項目を検索しているのかは、たいていの場合は明確です。しかし、テーブルの名前を知っていてもそれ以外の情報を持たない場合もあり得ます。こうした場合、SQLでは、例えばSelect * from SYS.SYSTABLESという形の検索を行います。こうした場合、JDBCのJavaプログラムから検索しているテーブルの項目の名前や項目の数を獲得する方法があります。次のコードを見てください。

query = "Select * from SYS.SYSTABLES" ;
ResultSet rs = stmt.executeQuery( query );

ResultSetMetaData meta = rs.getMetaData();
int size = meta.getColumnCount();


for( int i = 0; i < size; i++ )
out.println("<th>" + meta.getColumnLabel( i+1 ) + "</th>" );

 ポイントは、ResultSetのインスタンスから、getMetaDataメソッドでResultSetMetaDataという型のインスタンスを獲得しているところです。このメタデータを使えば、getColumnCountメソッドでテーブルの項目数が、また、getColumnLabelメソッドで項目のラベル名を獲得することができます。

 

 Java Pet Storeのイメージ・データを利用する

 これでテーブル・ブラウザの基本的な骨組みはできました。全体のソースと起動の仕方は、最後に述べるとして、ちょっといたずらをしてみましょう。今回のテーブル・ブラウザは、機能としては汎用的ですが、その働きをチェックするためには対象となるデータベースが必要です。一番簡単なのは、J2EEがサンプルを提供しているJava Pet Storeのデータベースを、このテーブル・ブラウザの対象にすることです。

 Java Pet Storeデモのデータベースには、ちょっと面白いデータが含まれています。例えば、PRODUCTテーブルのDESCN項目には、次のような形式のデータが含まれています。

<image src="../images/fish1.jpg">Salt Water fish from Australia

 これは、HTMLのimageタグですね。今回のJ2EEアプリケーションは、もともとデータベースの内容をWebでブラウズすることを目的としたものですから、このテーブル・ブラウザのJSPが置かれたディレクトリの1つ上のディレクトリに、imagesというディレクトリを作って、そこにJPSのイメージ・データを置けば、このテーブル・ブラウザで、JPSの画像を見ることができるはずです。

 

 deploytoolで、アプリケーションに
  ファイルを追加する

 JPSのイメージ・データは、最新のjps1.1.2でしたら、次のディレクトリに置かれています。

%JPS_HOME%\src\petstore\src\docroot\images

 deploytoolで、左側のツリーからWebコンポーネントを選び、左のGeneralタブで[Edit...]ボタンをクリックします。そうすると、図10のようなパネルが開きますので、Addボタンを押して上部のパネルでJPSのイメージ・ディレクトリを探し、それを下部のパネルにドロップします。この図は、ドロップが終わってからの状態のものです。Webコンポーネントのどの位置にimagesディレクトリがくればいいのかは、よくチェックしてください。追加が終われば次のような状態になっているはずです(図11)。あとは、Toolsメニューから再配置を選んで[File]メニューで保存しておきます。

図10 [Add]ボタンを押して上部のパネルでJPSのイメージ・ディレクトリを探し、それを下部のパネルにドロップする

図11 JPSの追加が終わったところ

 このように、deploytoolを利用すれば、J2EEアプリケーションの希望する場所に、簡単にファイルを追加することができます。

 

 イメージ付きのJPSのテーブルにアクセスする

 図12〜14を見てください。これらは、テーブル・ブラウザで、JPSのテーブルPRODUCT、CATEGORY、BANNERDATAをブラウズしてみたところです。

図12 PRODUCTをブラウズしたところ

図13 CATEGORYをブラウズしたところ

図14 BANNERDATAをブラウズしたところ

2/3

J2EEの基礎(第5回)
  テーブル・ブラウザを作る
J2EEでのデータベースとの接続
  テーブル・ブラウザをインストールし実行する


連載内容
J2EEの基礎
  第1回 Java Pet Storeで、J2EEを体験する(1)
  第2回 Java Pet Storeで、J2EEを体験する(2)
 

第3回 J2EEアプリケーションと配置(deployment)

  第4回 J2EEアプリケーションを構成するコンポーネント
第5回 データベースのブラウザを作る
  第6回 EJBにおけるコンテナとコンポーネント
  第7回 J2EEのセキュリティのキホンを知る
  第8回 J2EEのトランザクション処理


連載記事一覧




Java Agile フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Java Agile 記事ランキング

本日 月間