連載
» 2005年09月03日 00時00分 公開

Seaser Projectの全貌を探る(3):SeasarV2によるDBアクセス機能 (3/3)

[沖林正紀,@IT]
前のページへ 1|2|3       

メソッドとSQL文の分離(S2JDBC)

 先ほどの例では、メソッドの内部に直接SQL文を記述していました。しかしこの場合では、SQL文が変更されるだけでもJavaソースコード全体をコンパイルし直さなければなりません。また、DB技術者とJavaプログラマが分業している場合などでは、JavaプログラマにSQL文を書かせたくない、あるいはSQL文を読まれたくない、という場合もあるでしょう。そんなときのために、S2にはSQL文をdiconファイルに記述する方法が用意されています。

 この方法でデータベースにアクセスした場合、SELECTの結果を取得する際に、オブジェクトをListにまとめたもので受け取ったり(BeanList)、Mapに各フィールドの値を格納したもので受け取ったり(MapList)、さらには.NET Frameworkで非同期接続時に用いられるのと同様のDataTableオブジェクトで受け取ったりすることができるようになります。ここではその具体的な方法も併せて紹介していきます。

diconファイルに記述するコンポーネント

 SQL文をdiconファイル内に記述するとき、SELECTの場合はorg.seasar.extension.jdbc.impl.BasicSelectHandlerクラス、それ以外のINSERT、UPDATE、DELETEの場合はorg.seasar.extension.jdbc.impl.BasicUpdateHandlerクラスをコンポーネント定義に記述します。SQL文はそれぞれのコンポーネントのsqlプロパティの値として記述します。また、SELECTの場合は、問い合わせの結果をどのような形式で取得するのかを、resultSetHandlerプロパティに対して、その処理を行うコンポーネントを記述します。

 具体的にSELECT文を設定した例を以下に示します。resultSetHandlerプロパティの値として記述しているコンポーネント定義のBeanListResultSetHandlerクラスは、BeanList形式で問い合わせの結果を取得することを表しています。この形式については、次の項で説明します。

    SELECT文をdiconファイルに記述した例
<component name="selectHandler"
       class="org.seasar.extension.jdbc.impl.BasicSelectHandler">
  <!-- SELECT文を設定(列名とBeanのプロパティ名とを同じにする)-->
  <property name="sql">
   "SELECT id, title, amount, memo, date_create AS createDate,
   date_modified AS modifiedDate FROM account ORDER BY id"
  </property>
  <!-- 問い合わせの結果をBeanをListでまとめた形式で取得する -->
  <property name="resultSetHandler">
   <component class="
     org.seasar.extension.jdbc.impl.BeanListResultSetHandler">
    <arg>@myfirst.jdbc.Account@class</arg>
   </component>
  </property>
</component>

 では、このコンポーネントはどのように実行すればよいのでしょうか。インスタンスはS2Containerから取得すればよいのですが、実行するときには、インスタンスのexecuteメソッドを実行します。このdiconファイルに記述しているSQL文には“?”マークのパラメータは記述されていませんので、このメソッドの引数にはnullを設定します。実行結果はリスト2のインスタンスにプロパティの値が設定されたものがListにまとめられたものとして取得できます。このとき、SQL文での列名と<arg>タグで指定されているBeanのプロパティ名とが同じになるようにしなくてはなりません。データベースの列名とプロパティ名が異なるときは、SQL文内でAS句を用いてBeanのプロパティ名を指定してください。

//S2Container container = ....... ;
// container.init();
SelectHandler handler = (SelectHandler)container.getComponent( "selectHandler" );
List list = (List)handler.execute( null );  // SQL文(SELECT)を実行
for ( int i = 0; i < list.size(); ++i ) {  System.out.println( list.get( i ) );  }

 次に、diconファイル内にINSERT文を記述した例を示します。これは先述のinsertAccountメソッドで設定したのと同じSQL文が記述されています。このときは問い合わせの結果が返ってくるわけではありませんので、resultSetHandlerプロパティの値を記述することはありません。

<component name="insertHandler"
       class="org.seasar.extension.jdbc.impl.BasicUpdateHandler">
  <property name="sql">
   "INSERT INTO account
     (id_cat,title,amount,memo,date_create,date_modified )
      values ( ?, ?, ?, ?, CURDATE(), CURDATE() )"
  </property>
</component>

 このコンポーネントを用いてデータベースにデータを追加する例を以下に示します。S2Containerから、コンポーネントのインスタンスを取得するのは同じですが、SQL文を実行するexecuteメソッドでObject[]型の引数を設定しています。これはSQL文に記述されている“?”マークのパラメータに値を設定するためです。SQL文内の左にある“?”マークから順に配列の要素を先頭から割り当てていきます。CURDATE()はMySQLサーバの現在日付を表します。

//S2Container container = ....... ;
// container.init();
UpdateHandler handler = (UpdateHandler)container.getComponent( "insertHandler" );
// SQL文(INSERT)を実行  new Object[]でパラメータの値を設定
int records = handler.execute( new Object[] { 1, "ハンバーガー", 500, "今日もおいしかった。" } );

問い合わせ結果の取得

 ここからは、SELECT文の実行結果をどのように取得するかを説明していきます。具体的には、BeanList形式、MapList形式、DataTable形式の3種類の方法を取り上げます。

●BeanListによる結果の取得

 まずは先ほど登場したBeanListによる場合です。このときは、SELECT文の問い合わせ結果を1行取得するたびにdiconファイルに記述されたBeanクラスのインスタンスを生成し、列名と同じ名称のプロパティに値を設定していきます。すべての値が設定し終わったら、そのインスタンスがListの要素として追加されます。

●MapListによるデータの取得

 MapListというのは、問い合わせ結果の1行分の各列の値を、列名と対にしてMapオブジェクトに追加します。そしてそれをさらにListに追加していきます。この場合、Beanとなるクラスをdiconファイルに設定する必要がありませんし、データベースに設定された列名をそのままアプリケーション内で使用することができ、プロパティ名への置き換えを必要としません。その代わり、データベースのテーブルが変更された際は、この方法を用いたJavaプログラムがその変更の影響をそのまま受けることになります。

 シンプルな例として、categoryテーブルに登録されているデータを取得する例を紹介します。まずコンポーネント定義は以下のようになります。これまでBeanListResultSetHandlerクラスを設定していた部分がMapListResultSetHandlerクラスに置き換えられています。

<component name="selectCategoryMapList"
       class="org.seasar.extension.jdbc.impl.BasicSelectHandler">
  <!-- SQL文の設定 -->
  <property name="sql">
     "SELECT id, name FROM category ORDER BY id"
  </property>
  <!-- MapListによる問い合わせ結果の取得 -->
  <property name="resultSetHandler">
   <component class="
   org.seasar.extension.jdbc.impl.MapListResultSetHandler" />
  </property>
</component>

 このコンポーネントにより問い合わせ結果を取得する例を以下に示します。実行結果もその次に示します。この方法は列名や列の数の制限がなく、プログラムもとてもシンプルになりますので、取りあえずデータの取得状況をテストしたい場合にも用いることができます。

MapListResultSetHandlerにより問い合わせ結果を取得する例
// S2Container container = ....... ;
// container.init();
SelectHandler handler =
   (SelectHandler)container.getComponent( "selectCategoryMapList" );
// 問い合わせ結果を取得する
List catList = (List)handler.execute( null );
// 取得データ表示
for( Object m : catList )  {  System.out.println( (Map)m );  }


   実行結果
{id=1, name=食費}
{id=2, name=交通費}
{id=3, name=書籍代}
{id=4, name=光熱費}
{id=5, name=家賃}
{id=6, name=給料}

●DataTableによるデータの取得

 問い合わせ結果の取得をよりデータベースと同じ状態に近づけたい場合はDataTableを用います。これはデータベースのテーブルと同じように2次元の表形式でデータを保持するオブジェクトで、ここから列名や行数も取得することができます。

 DataTableによりデータを取得する場合は、diconファイルにおいて、以下のように、resultSetHandlerプロパティにDataTableResultSetHandlerクラスをコンポーネントとして記述します。このときDataTableインスタンスに付けるテーブル名を<arg>タグの間に記述します。

   問い合わせ結果をDataTableで取得することを記述した例
<property name="resultSetHandler">
   <component class="
org.seasar.extension.dataset.impl.DataTableResultSetHandler">
  <arg>"Category"</arg>
   </component>
</property>

 DataTableにより問い合わせ結果を取得し、画面に表示させる例を以下に示します。実行結果もその次に示します。SQL文は前項のMapListによる場合と同じとします。diconファイルで指定されたテーブル名はgetTableNameメソッドで取得できます。結果の行数はgetRowSizeメソッド、列数はgetColumnSizeメソッドで取得します。

 問い合わせ結果の1行ごとの値はDataRowオブジェクトに保持されていますので、getValueメソッドで各列を取得して画面に表示しています。

   問い合わせ結果をDataTableで取得する例
DataTable catTable = (DataTable)handler.execute( null );
// 取得データ表示
System.out.println( catTable.getTableName() + "テーブルのデータ" );
for( int rows = 0; rows < catTable.getRowSize(); rows++ )  {
   DataRow dRow = catTable.getRow( rows );
   System.out.print( "[ " );
   String comma = "";
   for( int columns = 0; columns < catTable.getColumnSize(); columns++ )  {
  System.out.print( comma + catTable.getColumnName( columns ) + " = " + dRow.getValue( columns ) );
  comma = ", ";
   }
   System.out.println( " ] " );
}


   実行結果
Category [ id = 1, category = 食費 ]
Category [ id = 2, category = 交通費 ]
Category [ id = 3, category = 書籍代 ]
Category [ id = 4, category = 光熱費 ]
Category [ id = 5, category = 家賃 ]
Category [ id = 6, category = 給料 ]

DataTableによるデータの更新

 最後に、SQL文を使わずに、DataTableを使ってデータベース上のデータを更新する方法を紹介しておきます。

 まずDataTableと関連するオブジェクトを先に紹介しておきます。DataTableの各行を表すオブジェクトがDataRowで、複数のDataTableを1つにまとめたものがDataSetです。DataSet、DataTable、DataRowというクラス名は、C#やVB.NETでおなじみの.NET Frameworkにも存在しており、どちらもその役割はよく似ています。これらのクラスはデータベースやテーブルを模したもので、DataTableにデータを取得した後、データの変更を一括して行うことができるようになっています。

 具体的には、まずデータベースを接続し、そのあとテーブルにアクセスします。そして、ataTableにデータを写し取った後にデータベースを切断します。そしてDataTableにあるデータを変更したうえで、再びデータベースに接続したときに、今度はそれをデータベースのテーブルに反映させるのです。これによりデータベース上のデータを一括して変更することができます。ただしDataTable中のデータを変更している間はデータベースに接続していませんので、その最中に別のユーザーがデータベース上のデータを更新したとしても、DataTableの内容をデータベースに反映した時点でそれが無効になってしまう恐れがあります。よって、データの更新を行うタイミングに注意する必要があります。

 では、categoryテーブルに「洋服代」というカテゴリを追加する例を紹介します。まず新しい行を追加するときは、DataTableオブジェクトのaddRowメソッドによりDataRowオブジェクトを取得し、そこにデータを追加します。そしてデータが追加されたDataTableオブジェクトをDataSetにまとめて、SqlWriterクラスを用いてデータベース上にその内容を反映します。SqlWriterクラスは、diconファイルでの定義により、あらかじめデータベースの接続先となるDataSourceが設定されています。

   DataTableによりデータベース上にデータを追加する例
DataRow newRow = table.addRow();
newRow.setValue( "name", "洋服代" );  // 新しい行にデータを追加する
DataSetImpl dataSet = new DataSetImpl();
dataSet.addTable( table );  // DataTableをDataSetにまとめる
SqlWriter sw = (SqlWriter)container.getComponent( "sqlWriter" );
sw.write( dataSet );  // データベース上のテーブルにDataTable上のデータを反映させる


   データベースにデータを追加するSqlWriterクラスのdiconファイルでの定義
<component name="sqlWriter"
    class="org.seasar.extension.dataset.impl.SqlWriter">
  <arg>dataSource</arg><!-- データベースの接続先 -->
</component>

 今回は、データベースの接続設定、SQL文をdiconファイルに記述し、Javaコードと完全に分離できるS2の特長を紹介しました。次回は、Junitを拡張したS2のテストフレームワーク「S2Unit」をご紹介します。


前のページへ 1|2|3       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。