連載
» 2005年12月23日 00時00分 公開

Seaser Projectの全貌を探る(5):SeasarのO/RマッピングツールS2Dao (2/2)

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

SQLファイルの内容を実行させる

 前ページで新しいカテゴリを追加する処理を行った際、SQL文を自動生成させて実行したため、IDカラムの値を保存しようとしてしまうことになってしまいました。それでも先ほどの処理はうまく実行できましたが、いつも自動生成されるSQL文だけで必要とする処理ができるとは限りません。

 そこで、開発者がカスタマイズしたSQL文を決められた形式の名称を持つファイル(SQLファイル)に保存しておくと、その内容を読み込んでデータベースに対して実行してくれる機能を用いて、新しいカテゴリを追加する処理を行うSQLをカスタマイズしてみます。

 SQLファイルには、リスト7のように書き込んでおきます。/*〜*/の部分はSQLコメントという機能で、ここではcategory変数のnameプロパティの値を表しています。その次に記述されている'カテゴリ名称'は、仮にこの値が設定されていない場合のデフォルト値です。

 これに伴って、リスト1には新たなアノテーションをBEANアノテーションのすぐ次の行に追加します(リスト8)。これはARGSアノテーションというもので、リスト7のSQLコメントに記述されている変数名にinsertCategoryメソッドの引数の値を割り当てる(バインドする)ことを示しています。

リスト7 SQLファイルに書き込む内容(CategoryDao_insertCategory.sql)
INSERT INTO category ( name ) VALUES ( /*category.name*/'カテゴリ名称' )  

リスト8 リスト1に追加するアノテーション
public static final String insertCategory_ARGS = "category";  

SQLファイルの命名規則

SQLファイルは、必ず“インターフェイス名またはクラス名_メソッド名.sql”という名称で作成します。そしてこのファイルは、ファイル名に記したインターフェイスまたはクラスと同じ階層のディレクトリにデプロイします。例えばリスト1(CategoryDao.java)はmyfirst.daoというパッケージ名ですから、リスト7もmyfirst\dao\CategoryDao_insertCategory.sqlにデプロイされることになります。

また、SQLファイルのファイル名は、データベースの種類ごとに分けることも可能です。この場合は拡張子の直前に“_データベースの種類を表す接尾辞”を付けます。例えばリスト7をMySQL用に限定する場合は、ファイル名をCategoryDao_insertCategory_mysql.sqlとします。


SQLコメントの種類

EntityManagerのメソッドには、find()、findArray()、findBean()、findObject()の4種類があります。これらのメソッドの第1引数は、自動生成されるSQL文に付け足すWHERE句またはORDER BY句です。WHERE句の場合はこの引数にWHEREを記述する必要はないのですが、最初からORDER BY句を記述する場合はORDER BYを省略することはできません。

第2引数以降は、第1引数に記述されている“?”(パラメータマーカ)に割り当てる値を設定します。これらの引数は3つまで(つまり第4引数まで)個別に設定するか、第2引数で配列により設定する方法とがあります。find()メソッドで例を挙げると以下のようになります。

public List find(String query);
public List find(String query, Object arg1);
public List find(String query, Object arg1, Object arg2);
public List find(String query, Object arg1, Object arg2, Object arg3);
public List find(String query, Object[] args);

戻り値は、それぞれのメソッドによって異なり、これが各メソッドの役割を表しています。

find()メソッド …… List (要素はエンティティオブジェクト)
findArray()メソッド …… Object[] (要素はエンティティオブジェクト)
findBean()メソッド …… Object (エンティティオブジェクト)
fintObject()メソッド …… Object (スカラー値など)

詳細はS2Daoのドキュメント(http://www.seasar.org/s2dao.html)を参照してください。


 SQLコメントは、SQLファイルやSQLアノテーションに記述するときに、“/*〜*/”で囲んで変数名や制御文などを記述するものです。“/*”の次に空白があるとSQLコメントとしては扱われず、何の処理も行わないコメントとして扱われますので注意してください。

 SQLコメントには、上記のような変数に値を割り当てるためのものだけでなく、以下に示す種類のものがあります。詳細はS2Daoのドキュメント(http://www.seasar.org/s2dao.html)を参照してください。

  • バインド変数コメント(/*変数名.プロパティ名*/ :使用例は上記
  • 埋め込み変数コメント(/*$変数名.プロパティ*/)

  両者はともにARGSアノテーションで設定された変数の値をここに割り当てます。ただし、変数名の先頭に$が付いていない場合は、オブジェクトの値を文字列に直したもの(toStringメソッドを実行したもの)が割り当てられます。先頭に$が付いていればオブジェクトの値そのものが割り当てられます。

 この直後にリテラルの文字列が記述されているときは、その値がデフォルトになります。

  • IFコメント(/*IF 条件式*/リテラル/*END*/)
    条件式の値に応じてリテラルをSQL文に含めるかどうかが決まります。
  • BEGINコメント(/*BEGIN*/WHERE句/*END*/)
    WHERE句の中に複数のIFコメントを用いたとき、そのIFコメントでいずれもリテラルを出力しないときにはWHERE句自体を実行しません。どれか1つのIFコメントでリテラルが出力されることになったら、WHERE句は実行されます。

EntityManagerによる検索処理

 EntityManagerとは、S2Daoのorg.seasar.dao.EntityManagerインターフェイスを実装したオブジェクトのことで、検索処理を行うfind〜で始まるメソッドを多数実装しています。これらのメソッドの内部では、SELECT文の自動生成が行われるため、Javaソースコードをシンプルにすることができます。なお、EntityManagerでは更新用のメソッドは用意されていません。

 こうした処理を行うには、リスト9のように、必ずorg.seasar.dao.impl.AbstractDaoクラスを継承し、コンストラクタにorg.seasar.dao.DaoMetaDataFactoryオブジェクトが設定されるようにしなくてはなりません。このクラスの中でEntityManagerを用いるときはgetEntityManager()メソッドを記述します。

 この中でEntityManagerのfindメソッドの引数には“ORDER BY id”と設定されていますが、これは自動生成されたSELECT文の後にこれを付け足してデータベースで実行させることを意味します。そしてfindBean()メソッドの第1引数“id = ?”は、自動生成したSQLのWHERE句となるもので、その次の引数の値を“?”の部分に割り当てて(バインドして)からSQL文をデータベースで実行させます。

リスト9 EntityManagerを用いた検索処理を行うクラス
package myfirst.dao;

import java.util.List;

import org.seasar.dao.DaoMetaDataFactory;
import org.seasar.dao.impl.AbstractDao;

public class CategoryDaoImpl extends AbstractDao
 implements CategoryDao  {

  public CategoryDaoImpl( DaoMetaDataFactory factory )  {  super( factory );  }

  public List<Category>  getCategories()   {
    return (List<Category>)getEntityManager().find( "ORDER BY id" );
  }

  public Category getCategory( int id )  {
    return (Category)getEntityManager().findBean( "id = ?", new Integer( id ) );
  }

  // ..... (省略) .....


EntityManagerのメソッドの種類

EntityManagerのメソッドには、find()、findArray()、findBean()、findObject()の4種類があります。これらのメソッドの第1引数は、自動生成されるSQL文に付け足すWHERE句またはORDER BY句です。WHERE句の場合はこの引数にWHEREを記述する必要はないのですが、最初からORDER BY句を記述する場合はORDER BYを省略することはできません。

第2引数以降は、第1引数に記述されている“?”(パラメータマーカ)に割り当てる値を設定します。これらの引数は3つまで(つまり第4引数まで)個別に設定するか、第2引数で配列により設定する方法とがあります。find()メソッドで例を挙げると以下のようになります。

public List find(String query);
public List find(String query, Object arg1);
public List find(String query, Object arg1, Object arg2);
public List find(String query, Object arg1, Object arg2, Object arg3);
public List find(String query, Object[] args);

戻り値は、それぞれのメソッドによって異なり、これが各メソッドの役割を表しています。

find()メソッド …… List (要素はエンティティオブジェクト)
findArray()メソッド …… Object[] (要素はエンティティオブジェクト)
findBean()メソッド …… Object (エンティティオブジェクト)
fintObject()メソッド …… Object (スカラー値など)

詳細はS2Daoのドキュメント(http://www.seasar.org/s2dao.html)を参照してください。


n:1マッピングによるリレーションの設定

 categoryテーブルのidカラムの値は、accountテーブルのid_catカラムの値と関連付けられ、あるaccountデータが属するカテゴリを示しています。こうしたときはN:1マッピングの設定を行うと、両方のテーブルに対してLEFT OUTER JOINを行うことができます。前回のリスト12で示したAccount.javaに少し変更を加え、この機能を利用してデータの一覧を取得してみましょう。

 Account.javaには、S2Daoのアノテーションをいくつか追加します。その中でn:1マッピングを行うための設定はRELNO定数とRELKEYS定数という2つです。これらについて説明します。

 RELNO定数はマッピングの際に用いる番号のことで、マッピングされるテーブルを数値で表したものです。この数値は自動生成されるSQL文で用いられます。実行例は後述します。RELKEYS定数は関連付けられるカラムを“:”で仕切って表したものです。リスト10で“ID_CAT:ID”とあるのは、accountテーブルのid_catカラムの値とcategoryテーブルのidカラムの値とが関連付けられることを表します。関連付ける相手のテーブルは、テーブル(表1)の作成時に外部キー制約が付けられた相手ということになります。

categoryテーブル
フィールド データ型 制約
id INTEGER PRIMARY KEY
name VARCHAR(100) UNIQUE

accountテーブル
フィールド データ型 制約
id INTEGER PRIMARY KEY
id_cat INTEGER FOREIGN KEY REFERENCEScategory(id)
title VARCHAR(200) NOT NULL
amount INTEGER NOT NULL
memo TEXT  
date_create DATE NOT NULL
date_modified DATE NOT NULL
accountdbに作成したテーブル
リスト10 前回のリスト12にN:1マッピング設定を追加したもの リスト10 前回のリスト12にN:1マッピング設定を追加したもの

 accountテーブルにアクセスするためのDAOインターフェイスをリスト11のように作成し、accountテーブルのデータを取得してみます。これを用いてデータベースにアクセスする処理をリスト12のように作成して実行するとリスト13が出力されます。DEBUGで始まる行がS2Daoによる出力で、Accountで始まる行が、データベースから取得したAccountオブジェクトのプロパティの値を表しています。

 このとき自動生成されたSQLにLEFT OUTER JOINが含まれていることを確認してください。そしてAccountクラスのcategoryプロパティにCategoryクラスのプロパティの値がid、nameともに含まれていることも一緒に確認してみてください。

リスト11 accountテーブルにアクセスするためのDAOインターフェイス
package myfirst.dao;

import java.util.List;
import java.sql.SQLException;

public interface AccountDao  {

  public static final Class  BEAN = Account.class;
  public static final String getAccountById_QUERY = "account.id = ?";

  List<Account> getAccounts()                       throws SQLException;
  List<Account> getAccountByCategory( CategoryCondition cc ) throws SQLException;
  Account       getAccountById( int id ) throws SQLException;


リスト12 categoryとaccountの利用テーブルをJOINしたデータを取得する
// …コンテナの設定(省略)
// DAOオブジェクトの取得
AccountDao dao = (AccountDao)container.getComponent( AccountDao.class );
// データベースにアクセスする
List<Account> list = dao.getAccounts();
for( Account a : list )  System.out.println( a );
// …コンテナの終了処理(省略) 

   リスト13 リスト12の実行結果(例)
DEBUG 2005-08-26 20:16:59,518 [main] 物理的なコネクションを取得しました
DEBUG 2005-08-26 20:16:59,528 [main] 論理的なコネクションを取得しました
DEBUG 2005-08-26 20:16:59,748 [main] 論理的なコネクションを閉じました
DEBUG 2005-08-26 20:16:59,878 [main] SELECT ACCOUNT.id, ACCOUNT.amount, ACCOUNT.memo, ACCOUNT.date_create, ACCOUNT.date_modified, ACCOUNT.title, category.id AS id_0, category.name AS name_0 FROM ACCOUNT LEFT OUTER JOIN CATEGORY category ON ACCOUNT.ID_CAT = category.ID
DEBUG 2005-08-26 20:16:59,878 [main] 論理的なコネクションを取得しました
DEBUG 2005-08-26 20:17:00,009 [main] 論理的なコネクションを閉じました
Account [ id = 1, category = Category [ id = 2, category = 交通費 ], title = 地下鉄, amount = -190, memo = 半蔵門線 渋谷->大手町, createDate = 2005-06-24 00:00:00.0, modifiedDate = 2005-06-24 00:00:00.0 ]
Account [ id = 2, category = Category [ id = 2, category = 交通費 ], title = 地下鉄, amount = -190, memo = 半蔵門線 大手町->渋谷, createDate = 2005-06-24 00:00:00.0, modifiedDate = 2005-06-24 00:00:00.0 ]
Account [ id = 3, category = Category [ id = 2, category = 交通費 ], title = 私鉄, amount = -190, memo = 井の頭線 渋谷->吉祥寺, createDate = 2005-06-24 00:00:00.0, modifiedDate = 2005-06-24 00:00:00.0 ]
Account [ id = 4, category = Category [ id = 1, category = 食費 ], title = スーパーで買い物, amount = -4500, memo = ちょっと買い過ぎたかな, createDate = 2005-06-24 00:00:00.0, modifiedDate = 2005-06-24 00:00:00.0 ]
Account [ id = 5, category = Category [ id = 6, category = 給料 ], title = 月給, amount = 250000, memo = もうちょっと頑張らないと..., createDate = 2005-06-24 00:00:00.0, modifiedDate = 2005-06-24 00:00:00.0 ]
Account [ id = 6, category = Category [ id = 3, category = 書籍代 ], title = Javaの本いろいろ, amount = -7500, memo = Seasarの本も出ないかな, createDate = 2005-06-25 00:00:00.0, modifiedDate = 2005-06-25 00:00:00.0 ]
Account [ id = 7, category = Category [ id = 4, category = 光熱費 ], title = 引き落とし, amount = -11000, memo = 省エネ効果あり?, createDate = 2005-06-27 00:00:00.0, modifiedDate = 2005-06-27 00:00:00.0 ]
Account [ id = 8, category = Category [ id = 5, category = 家賃 ], title = 引き落とし, amount = -55000, memo = もっと広いところに住みたいな, createDate = 2005-06-27 00:00:00.0, modifiedDate = 2005-06-27 00:00:00.0 ]
DEBUG 2005-08-26 20:17:00,049 [main] 物理的なコネクションを閉じました

 最後に、accountテーブルに保存されているデータのうち、同じカテゴリIDを持つもののみを取得する例を紹介しましょう。このときは、データを取得する条件を設定するためのクラス(リスト14)を作成し、カテゴリIDが関連付けられるカラム(id_0)をCOLUMNアノテーションで設定しておきます。なぜid_0という名称なのかというと、リスト13で実行されている自動生成されたSQL文に“category.id AS id_0”という部分があるからです。

 あとはリスト11のgetAccountByCategory()メソッドを実行すれば完了です。リスト16で実行されたSQL文と実行結果とを確認してみてください。

リスト14 accountテーブルのデータを取得する際の条件(カテゴリID)を設定するためのクラス
package myfirst.dao;

public class CategoryCondition  {

  public static final String id_COLUMN = "id_0";  // 関連付けられるカラム名

  private int    id;  // categoryテーブルのid

  public void setId( int id )  {  this.id   = id;  }
  public int  getId()          {  return id;  }


リスト15 あるカテゴリIDを持つaccountテーブルのデータを取得する
// … コンテナの設定、DAOオブジェクトの取得(省略)
CategoryCondition cc = new CategoryCondition();
cc.setId( 2 );  // 交通費
List<Account> list = dao.getAccountByCategory( cc );
// … コンテナの終了処理(省略) 

   リスト16 リスト15の実行結果(例)
DEBUG 2005-08-26 20:35:56,653 [main] 物理的なコネクションを取得しました
DEBUG 2005-08-26 20:35:56,653 [main] 論理的なコネクションを取得しました
DEBUG 2005-08-26 20:35:56,783 [main] 論理的なコネクションを閉じました
DEBUG 2005-08-26 20:35:56,883 [main] SELECT ACCOUNT.id, ACCOUNT.amount, ACCOUNT.memo, ACCOUNT.date_create, ACCOUNT.date_modified, ACCOUNT.title, category.id AS id_0, category.name AS name_0 FROM ACCOUNT LEFT OUTER JOIN CATEGORY category ON ACCOUNT.ID_CAT = category.ID WHERE category.id = 2
DEBUG 2005-08-26 20:35:56,883 [main] 論理的なコネクションを取得しました
DEBUG 2005-08-26 20:35:56,953 [main] 論理的なコネクションを閉じました
Account [ id = 1, category = Category [ id = 2, category = 交通費 ], title = 地下鉄, amount = -190, memo = 半蔵門線 渋谷->大手町, createDate = 2005-06-24 00:00:00.0, modifiedDate = 2005-06-24 00:00:00.0 ]
Account [ id = 2, category = Category [ id = 2, category = 交通費 ], title = 地下鉄, amount = -190, memo = 半蔵門線 大手町->渋谷, createDate = 2005-06-24 00:00:00.0, modifiedDate = 2005-06-24 00:00:00.0 ]
Account [ id = 3, category = Category [ id = 2, category = 交通費 ], title = 私鉄, amount = -190, memo = 井の頭線 渋谷->吉祥寺, createDate = 2005-06-24 00:00:00.0, modifiedDate = 2005-06-24 00:00:00.0 ]
DEBUG 2005-08-26 20:35:56,993 [main] 物理的なコネクションを閉じました

 今回はS2プロダクトのうち、O/Rマッピングを行うS2Daoを紹介しました。XMLの設定を使わないなど、ユニークな特長を持っており、開発を効率化させるための仕組みがいろいろと用意されていることが分かりました。S2本体と一緒に使うことでその仕組みを存分に生かすことができますので、S2によるアプリケーションを開発する際にはS2Daoを使うことも検討してみてください。


前のページへ 1|2       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

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

メールマガジン登録

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