今回のサンプルについて
単純な依頼リストの登録・閲覧・編集・削除を行えるアプリケーションを作成しました。データベースのテーブルも非常にシンプルなもので、画面からは依頼内容とステータスを入れる2つのフィールドを持つだけです。それ以外にキー項目となるシーケンシャルなIDが1つあります。
アプリケーションとしては、本来入力チェックや排他制御を設けるところですが、今回は単純にデータベースとのアクセスにのみ注力したアプリケーションなので割愛いたしました。
なお、本サンプルの画面遷移図は以下のようになります。
今回作成するアプリケーションの機能をざっと上げるとこの5つとなります。併せてそれぞれの機能のAction名を決定しました。
- 一覧検索 - list(登録されている全件を表示する)
- 詳細表示 - browse(一覧から選んだ1件の情報を表示し、更新-削除を選べる)
- 新規登録 - insert
- 更新 - update
- 削除 - delete
画面は簡単に一覧画面と詳細画面のみとしています。詳細画面で依頼内容以外のデータ(今回はステータスしかありませんが)を表示し、そこで編集、削除、追加をできるものとしています。また、このAction名をそのままリクエストするURLとします。例えば、新規登録のURLは/コンテキストルート/insertとなります(.actionは省略できます)。
では実装部分を見てみましょう。次に示すのは、今回の5機能を網羅したActionクラスとなります。非常に単純な実装なこともありますが、従来のStruts 1やサーブレットに比べてコード量は激減します。
public class IssueAction extends ActionSupport implements ModelDriven<IssueModel> {
…(中略)…
@Action("list")
public String list() throws Exception {
// 依頼一覧を検索する。
resultList = (List<IssueModel>)dao.getObjectList("searchIssue", null );
addActionMessage("検索が完了しました"); // メッセージ
return "list";
}
@Action("browse")
public String browse() throws Exception {
// 渡されたidで検索を行う。存在していれば1件になる。
resultList = (List<IssueModel>)dao.getObjectList("searchIssue", model);
// 検索結果を調べ、1件存在したならばその1件を表示用オブジェクトへバインド
if ( resultList != null && resultList.size() == 1 ) {
result = resultList.get(0);
}
addActionMessage("詳細を表示しました"); // メッセージ
return "browse";
}
@Action("insert")
public String insert() throws Exception {
dao.insert("insertIssue", model); // 新規登録
addActionMessage("1件追加しました"); // メッセージ
return browse();
}
@Action("update")
public String update() throws Exception {
int count = dao.update("updateIssue", model); // 更新
addActionMessage(count + "件を更新しました"); // メッセージ
return browse();
}
@Action("delete")
public String delete() throws Exception {
int count = dao.delete("deleteIssue", model); // 削除
addActionMessage(count + "件を削除しました"); // メッセージ
return browse();
}
/**
* ModelDrivenインターセプタ必須メソッド。
* リクエストパラメータをバインドする実体クラスを取得する。
* ※Struts 2により中身は自動的にバインドされる。
*/
public IssueModel getModel() {
model = new IssueModel();
return model;
}
/** 画面からのパラメータが格納される */
private IssueModel model;
private DAO dao;
public DAO getDAO() {
return dao;
}
public void setDAO(DAO dao) {
this.dao = dao;
}
/** 詳細画面の検索結果を格納する */
private IssueModel result;
public IssueModel getResult() {
return result;
}
public void setResult(IssueModel result) {
this.result = result;
}
/** 検索結果を格納する */
private List<IssueModel> resultList;
public List<IssueModel> getResultList() {
return resultList;
}
public void setResultList(List<IssueModel> resultList) {
this.resultList = resultList;
}
}
Springによるトランザクションの監視はDAOクラスに対して行われていますので、先ほどの設定ではinsert,update,deleteのメソッドに対して行われます。
では次に、このActionクラスからDAO経由で呼び出されるSQL定義になります。冒頭でも挙げましたが、今回はiBATISと、SpringのiBATIS管理クラスを利用しますので、iBATIS用の設定ならびにSQL定義ファイルを配置します。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<sqlMap resource="../ibatis/sqldefs.xml"/>
</sqlMapConfig>
<sqlMapConfig>要素で、複数の設定ファイルを読み込めるよう、別ファイルにSQL定義XMLを読みこめるようになっています。今回は1ファイルのみですが、分割してsqldefs.xmlにSQLを記述しています。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap
PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="lumi2">
…(省略)…
<select id="searchIssue" resultClass="seren.lumi2.model.IssueModel" parameterClass="seren.lumi2.model.IssueModel">
SELECT
id , issue , checked
FROM
issueInfo
<dynamic>
<isNotEmpty prepend="WHERE" property="id">
id = #id#
</isNotEmpty>
</dynamic>
</select>
<insert id="insertIssue" parameterClass="seren.lumi2.model.IssueModel">
INSERT INTO issueInfo
(issue , checked )
VALUES
(#issue# , #checked#)
</insert>
<update id="updateIssue" parameterClass="seren.lumi2.model.IssueModel">
UPDATE
issueInfo
SET
issue = #issue# ,
checked = #checked#
WHERE
id = #id#
</update>
<delete id="deleteIssue" parameterClass="seren.lumi2.model.IssueModel">
DELETE FROM
issueInfo
WHERE
id = #id#
</delete>
</sqlMap>
<sqlMap>の子要素にあたる<select>や<insert>で、実際に使われるSQLのIDを定義し、parameterClass属性で入力オブジェクトを定義します。また、SELECT文についてはレコード1行の結果をresultClassで受け取れるようになっています。今回のサンプルでは、入力も出力も単純にテーブルのレコード1行そのままを扱っていますので、そのまま扱うクラスであるIssueModelクラスを単純に使います。
なお、iBATISの詳細については、末尾の参考資料で取り上げた『iBATIS.NETでO/Rマッピングを行う(SQL Maps編)』『iBATISを使ったO/RマッピングによるDBアクセスの実例』、双方の記事を参照してください。
以上で一通りの検索~登録~更新~削除の流れまでを説明しました。実際のアプリケーションでは、さらに入力チェックや排他制御の実装、複雑なビジネスロジックを組み立てると予想されますが、それらの機能を追加するために行うActionクラスへの変更作業は、今まで取り上げたインターセプタやSpringの機能を使って役割を分担しておけば、きっちりと実装範囲を区分けすることができます。
制御も含めた実装面をきれいにすることで、単体試験の実施が簡単になり、設計も単純化しやすいという側面もあります。またSpringで作成した従来のアプリケーションのロジック部分のみを再利用できるのも多少なりとも良い点ではないでしょうか。
まとめ
今回は簡単なデータベースアクセスを扱うアプリケーションを通して、Springプラグインを紹介をしました。次回は、Struts 2でAjaxアプリケーションを作成するためにできたもう一つのStruts 2用プラグインである、JQueryプラグインを紹介します。
参考資料

