SAStruts + S2JDBC でサンプルアプリを作ってみました

以前、WEB+DB PRESS vol. 41 にて特集2『つらいJavaからたのしいJavaSeasar2 サクサク開発 実践カリキュラム』を執筆させて頂きました。
(http://gihyo.jp/magazine/wdpress/archive/2007/vol41)


この記事では、プレゼンテーション層にTeeda、データアクセス層にDBFluteというフレームワーク使ってサンプルアプリケーションを作成しました。


今回は、前回と同じ外部仕様、同じデータベースによる
サンプルアプリケーションのSAStruts + S2JDBC版を作ってみました。


ただし、現時点では「正常系が動作した」というレベルの完成度です。
前回のサンプルアプリケーションと比較して、以下のような未実装な機能が
多く存在するので注意が必要です。

  • 一部の日付書式
  • バリデーション
  • 二度押し防止
  • 戻るボタン対策
  • 共通レイアウト
  • 共通エラーページ


ソースコードを見てもらう前に外部仕様を見てみましょう。
『検索条件入力』 ⇒ 『検索結果一覧 』
⇒ 『更新』 ⇒ 『更新確認』 ⇒ 『更新完了』
という典型的な流れです。


次に、データベース(ERD)を見てみましょう。
従業員(EMP)テーブルに部署(DEPT)テーブルが関連している
典型的なデータ構造です。


アプリケーション用に作成したクラスやJSPファイルは以下のとおりです。

■Actionクラス


■Entityクラス


JSPファイル


では、ソースコードを見てみましょう。
長くなるので解説は省略しますが、
可読性が高いので、読めばわりと理解できるのではないでしょうか。
(分かりにくい箇所があれば、コメント欄で答えます。)


検索条件入力のURLは次のとおりです。seasardemoアプリケーションのEmpActionクラスのsearchメソッドが呼ばれます。

http://localhost:8080/seasardemo/emp/search


EmpAction.java

package sample.action;

import java.util.List;

import org.seasar.extension.jdbc.JdbcManager;
import org.seasar.framework.beans.util.BeanMap;
import org.seasar.framework.beans.util.Beans;
import org.seasar.struts.annotation.DateType;
import org.seasar.struts.annotation.Execute;
import org.seasar.struts.annotation.IntegerType;

import sample.entity.Dept;
import sample.entity.Emp;

public class EmpAction {

    public JdbcManager jdbcManager;

    /** 検索条件: 従業員名の前方一致 */
    public String condition_name_STARTS;

    /** 検索条件:入社日の範囲検索(開始) */
    @DateType
    public String condition_hireDate_GE;

    /** 検索条件:入社日の範囲検索(終了) */
    @DateType
    public String condition_hireDate_LE;

    /** 識別子(従業員)です。 */
    @IntegerType
    public String id;

    /** 名前(従業員)です。 */
    public String name;

    /** 入社日(従業員)です。 */
    @DateType
    public String hireDate;

    /** 部署の識別子です。 */
    @IntegerType
    public String deptId;

    /** バージョン(従業員)です。 */
    @IntegerType
    public String versionNo;

    /** 部署名です。 */
    public String deptName;

    /** 従業員のリストです。 */
    public List<Emp> empItems;

    /** 部署のリストです。 */
    public List<Dept> deptItems;
	
    @Execute(validator = false)
    public String search() {
        return "search.jsp";
    }

    @Execute(validator = false)
    public String list() {		
        empItems = jdbcManager.from(Emp.class)
            .leftOuterJoin("dept")
            .where(Beans.createAndCopy(BeanMap.class, this)				
                .prefix("condition_")
                .excludesWhitespace()
                .execute())
            .orderBy("hireDate")
            .getResultList();

        return "list.jsp";
    }

    @Execute(validator = false, urlPattern = "update/{id}")
    public String update() {
        deptItems = jdbcManager.from(Dept.class).orderBy("id").getResultList();

        Emp emp = jdbcManager.from(Emp.class).id(id).getSingleResult();
        Beans.copy(emp, this).dateConverter("yyyy/MM/dd", "hireDate").execute();

        return "updateInput.jsp";
    }

    @Execute(validator = false)
    public String showConfirm() {
        if ("".equals(deptId)) {
            deptName = "";
        } else { 
            Dept dept = jdbcManager.from(Dept.class).id(deptId).getSingleResult();
            deptName = dept.name;
        }

        return "updateConfirm.jsp";
    }

    @Execute(validator = false)
    public String executeUpdate() {
        Emp emp = Beans.createAndCopy(Emp.class, this).execute();
        jdbcManager.update(emp).execute();

        return "updateComplete.jsp";
    }
}


Emp.java

package sample.entity;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;

/**
 * 従業員です。
 * 
 * @author dewa
 * 
 */
@Entity
public class Emp {

    /** 識別子です。 */
    @Id
    @GeneratedValue
    public Integer id;

    /** 名前です。 */
    public String name;


    /** 入社日です。 */
    @Temporal(TemporalType.DATE)
    public Date hireDate;
    
    /** 部署の識別子です。 */
    public Integer deptId;

    /** 部署です。 */
    @ManyToOne
    public Dept dept;

    /** バージョンです。 */
    @Version
    public Integer versionNo;
}


Dept.java

package sample.entity;

import java.util.List;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Version;

/**
 * 部署です。
 * 
 * @author dewa
 */
@Entity
public class Dept {

    /** 識別子です。 */
    @Id
    @GeneratedValue
    public Integer id;
    
    /** 名前です。 */
    public String name;

    /** バージョンです。 */
    @Version
    public Integer versionNo;
}


search.jsp

<html>
<head>
<title>社員検索</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<s:form action="/emp">
<h1>社員検索</h1>
社員名:
  <input name="condition_name_STARTS" type="text" title="社員名"/>(前方一致)<br>
入社日:
  <input name="condition_hireDate_GE" type="text" title="入社日(開始)" /><input name="condition_hireDate_LE" type="text" title="入社日(終了)" /><br>
  
  <input type="submit" name="list" value="検索" />
</s:form>
</body></html>


list.jsp

<html>
<head>
<title>社員検索結果一覧</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>

<h1>社員検索結果一覧</h1>

社員名: ${f:h(condition_name_STARTS)}<br>
入社日: ${f:h(condition_hireDate_GE)}  〜 ${f:h(condition_hireDate_LE)}<br>
  
<table border="1" >
  <thead>
    <tr>
      <th>社員名</th><th>入社日</th><th>部署名</th><th></th>
    </tr>
  </thead>
  <tbody>
    <c:forEach var="emp" varStatus="s" items="${empItems}">
      <tr style="background-color: ${s.index % 2 == 0 ? 'white' : 'lightblue'};">
        <td>${emp.name}</td>
        <td><fmt:formatDate value="${emp.hireDate}" type="DATE" dateStyle="FULL"/></td>
        <td>${emp.dept.name}</td>		
        <td><a href="update/${f:u(emp.id)}" target="_blank"> 変更 </a></td>
      </tr>
    </c:forEach>
  </tbody>
</table>

</body></html>


updateInput.jsp

<html>
<head>
<title>社員変更</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style TYPE="text/css">
</style>
</head>
<body>
<s:form action="/emp">
<html:hidden property="id"/>
<html:hidden property="versionNo"/>

<h1>社員変更</h1>
<html:errors/>
社員名*: <html:text property="name"/><br />
入社日  : <html:text property="hireDate"/><br />
部 署 :
  <html:select property="deptId">
  <html:option value=""></html:option>
    <c:forEach var="dept" items="${deptItems}">
      <html:option value="${f:h(dept.id)}">${f:h(dept.name)}</html:option>
    </c:forEach>
  </html:select>

<br />
<input type="submit" name="showConfirm" value="確認" />
</s:form>
</body></html>


updateConfirm.jsp

<html>
<head>
<title>社員変更確認</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>

<s:form action="/emp">
<h1>社員変更確認</h1>

社員名: ${f:h(name)} <br>
	<html:hidden property="name"/>
入社日: ${f:h(hireDate)} <br>
	<html:hidden property="hireDate"/>
部 署: ${f:h(deptName)} <br>
	<html:hidden property="deptId"/>

<html:hidden property="id"/>
<html:hidden property="versionNo"/>

<input type="submit" name="executeUpdate" value="変更" />
</s:form>
</body></html>


updateComplete.jsp

<html>
<head>
<title>社員変更完了</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>社員変更完了</h1>
社員変更処理が完了しました。
</body></html>


現時点では、非機能要件的な機能が実装されていないにも関わらず、
ファイル数やコード量がとても少ないですねぇ。


参考:
SAStruts
http://sastruts.seasar.org/

S2JDBC
http://s2container.seasar.org/2.4/ja/s2jdbc.html