Hatena::ブログ(Diary)

出羽ブログ RSSフィード

Seasar Conference 2008 Autumn - 9/6(SAT), Tokyo

2008-01-31

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

| 18:43 | SAStruts + S2JDBC でサンプルアプリを作ってみましたを含むブックマーク

以前、WEB+DB PRESS vol. 41 にて特集2『つらいJavaからたのしいJavaSeasar2 サクサク開発 実践カリキュラム』を執筆させて頂きました。

(http://gihyo.jp/magazine/wdpress/archive/2007/vol41)


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


今回は、前回と同じ外部仕様、同じデータベースによる

サンプルアプリケーションSAStruts + S2JDBC版を作ってみました。


ただし、現時点では「正常系が動作した」というレベルの完成度です。

前回のサンプルアプリケーションと比較して、以下のような未実装な機能が

多く存在するので注意が必要です。

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

ソースコードを見てもらう前に外部仕様を見てみましょう。

『検索条件入力』 ⇒ 『検索結果一覧 』

⇒ 『更新』 ⇒ 『更新確認』 ⇒ 『更新完了』

という典型的な流れです。

f:id:dewa:20080131175626j:image


次に、データベース(ERD)を見てみましょう。

従業員(EMP)テーブルに部署(DEPT)テーブルが関連している

典型的なデータ構造です。

f:id:dewa:20080131175634j:image


アプリケーション用に作成したクラスや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

higayasuohigayasuo 2008/01/31 19:43 入力値を表示する場合は、f:h()を使ってエスケープしたほうが良いです。
アンカータグのhrefはf:u()を使ってエンコーディングしたほうが良いです。

dewadewa 2008/01/31 20:45 > 入力値を表示する場合は、f:h()を使ってエスケープしたほうが良いです。
> アンカータグのhrefはf:u()を使ってエンコーディングしたほうが良いです。

エスケープ&エンコーディングを施すように修正しました。

masamimasami 2010/03/24 01:16 サンプルを参考にさせていただこうと思い、自分のPCで動かしてみたのですが、
社員変更確認画面で[変更]ボタンを押すと、以下の例外が発生してしまいます。
いろいろと調べてみたのですが、解決できません。
何か心当たりございますでしょうか?よろしくお願いいたします。

org.seasar.framework.exception.ParseRuntimeException: [ESSR0050]解析に失敗しました。理由はjava.text.ParseException: Unparseable date: "1990/01/01"

masamimasami 2010/03/24 22:38 申し訳ありません。↓のようにdateConverterを使えば良かったようです。
上手くいきました。

Emp emp = Beans.createAndCopy(Emp.class, this).dateConverter("yyyy/MM/dd").execute();

hirohiro 2010/04/05 16:01 自分のPCで動かしてみたのですが、社員検索画面以降500エラーが出て、動きませんでした。
コンソールには、以下のエラーが出ているのですが、原因が分かりません。
何か心当たりがあれば、教えて頂けませんでしょうか。
DEBUG 2010-04-05 15:57:43,055 [http-8090-1] BEGIN emp.action.EmpAction#list()
DEBUG 2010-04-05 15:57:43,065 [http-8090-1] トランザクションを開始しました。tx=[FormatId=4360, GlobalId=1270450663065/0, BranchId=]
ERROR 2010-04-05 15:57:43,151 [http-8090-1] クラス(org.seasar.extension.jdbc.query.AutoSelectImpl)のメソッド(getResultList)が不正です。
ERROR 2010-04-05 15:57:43,151 [http-8090-1] エンティティ(emp.entity.Emp)が不正です。
DEBUG 2010-04-05 15:57:43,156 [http-8090-1] トランザクションをロールバックしました。tx=[FormatId=4360, GlobalId=1270450663065/0, BranchId=]
DEBUG 2010-04-05 15:57:43,156 [http-8090-1] END emp.action.EmpAction#list() Throwable:java.lang.NullPointerException
2010-04-05 15:57:43,158 [http-8090-1] WARN org.apache.struts.action.RequestProcessor - 処理できない例外がスローされました: class java.lang.NullPointerException
DEBUG 2010-04-05 15:57:43,160 [http-8090-1]

hirohiro 2010/04/05 16:54 上記は解決しました。

<filter>
<filter-name>routingfilter</filter-name>
<filter-class>
org.seasar.struts.filter.RoutingFilter
</filter-class>
<init-param>
<param-name>jspDirectAccess</param-name>
<param-value>false</param-value>
</init-param>
</filter>
⇒trueからfalseへ変更したら解決できましたが、新たな問題が・・・。
検索ボタンを押すと、NullPointerが出てしまいます。なぜでしょうか。。

----------------------------------------------------



説明 The server encountered an internal error () that prevented it from fulfilling this request.

例外

javax.servlet.ServletException: java.lang.NullPointerException
org.apache.struts.action.RequestProcessor.processException(RequestProcessor.java:535)
org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:433)
org.seasar.struts.action.S2RequestProcessor.process(S2RequestProcessor.java:132)
org.apache.struts.action.ActionServlet.process(ActionServlet.java:1196)
org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432)
javax.servlet.http.HttpServlet.service(HttpServlet.java:710)
javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
org.seasar.extension.filter.RequestDumpFilter.doFilter(RequestDumpFilter.java:127)
org.seasar.framework.container.filter.S2ContainerFilter.doFilter(S2ContainerFilter.java:79)
org.seasar.framework.container.hotdeploy.HotdeployFilter.doHotdeployFilter(HotdeployFilter.java:86)
org.seasar.framework.container.hotdeploy.HotdeployFilter.doFilter(HotdeployFilter.java:67)
org.seasar.struts.filter.RoutingFilter.forward(RoutingFilter.java:219)
org.seasar.struts.filter.RoutingFilter.doFilter(RoutingFilter.java:90)
org.seasar.framework.container.filter.S2ContainerFilter.doFilter(S2ContainerFilter.java:79)
org.seasar.framework.container.hotdeploy.HotdeployFilter.doHotdeployFilter(HotdeployFilter.java:99)
org.seasar.framework.container.hotdeploy.HotdeployFilter.doFilter(HotdeployFilter.java:67)
org.seasar.extension.filter.EncodingFilter.doFilter(EncodingFilter.java:69)


原因

java.lang.NullPointerException
org.seasar.extension.jdbc.util.DataSourceUtil.getConnection(DataSourceUtil.java:51)
org.seasar.extension.jdbc.manager.JdbcManagerImpl.getJdbcContext(JdbcManagerImpl.java:376)
org.seasar.extension.jdbc.query.AbstractSelect.getResultListInternal(AbstractSelect.java:223)
org.seasar.extension.jdbc.query.AbstractSelect.getResultList(AbstractSelect.java:172)
emp.action.EmpAction.list(EmpAction.java:73)
emp.action.EmpAction$$EnhancedByS2AOP$$1f7efb7.$$list$$invokeSuperMethod$$(EmpAction$$EnhancedByS2AOP$$1f7efb7.java)
emp.action.EmpAction$$EnhancedByS2AOP$$1f7efb7$$MethodInvocation$$list0.proceed(MethodInvocationClassGenerator.java)
org.seasar.extension.tx.DefaultTransactionCallback.execute(DefaultTransactionCallback.java:58)
org.seasar.extension.tx.adapter.JTATransactionManagerAdapter.required(JTATransactionManagerAdapter.java:65)
org.seasar.extension.tx.RequiredInterceptor.invoke(RequiredInterceptor.java:50)
emp.action.EmpAction$$EnhancedByS2AOP$$1f7efb7$$MethodInvocation$$list0.proceed(MethodInvocationClassGenerator.java)
org.seasar.framework.aop.interceptors.TraceInterceptor.invoke(TraceInterceptor.java:73)
emp.action.EmpAction$$EnhancedByS2AOP$$1f7efb7$$MethodInvocation$$list0.proceed(MethodInvocationClassGenerator.java)
org.seasar.framework.aop.interceptors.ThrowsInterceptor.invoke(ThrowsInterceptor.java:79)
emp.action.EmpAction$$EnhancedByS2AOP$$1f7efb7$$MethodInvocation$$list0.proceed(MethodInvocationClassGenerator.java)
emp.action.EmpAction$$EnhancedByS2AOP$$1f7efb7.list(EmpAction$$EnhancedByS2AOP$$1f7efb7.java)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
java.lang.reflect.Method.invoke(Method.java:597)
org.seasar.framework.util.MethodUtil.invoke(MethodUtil.java:96)
org.seasar.struts.action.ActionWrapper.execute(ActionWrapper.java:139)
org.seasar.struts.action.ActionWrapper.execute(ActionWrapper.java:87)
org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431)
org.seasar.struts.action.S2RequestProcessor.process(S2RequestProcessor.java:132)
org.apache.struts.action.ActionServlet.process(ActionServlet.java:1196)
org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432)
javax.servlet.http.HttpServlet.service(HttpServlet.java:710)
javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
org.seasar.extension.filter.RequestDumpFilter.doFilter(RequestDumpFilter.java:127)
org.seasar.framework.container.filter.S2ContainerFilter.doFilter(S2ContainerFilter.java:79)
org.seasar.framework.container.hotdeploy.HotdeployFilter.doHotdeployFilter(HotdeployFilter.java:86)
org.seasar.framework.container.hotdeploy.HotdeployFilter.doFilter(HotdeployFilter.java:67)
org.seasar.struts.filter.RoutingFilter.forward(RoutingFilter.java:219)
org.seasar.struts.filter.RoutingFilter.doFilter(RoutingFilter.java:90)
org.seasar.framework.container.filter.S2ContainerFilter.doFilter(S2ContainerFilter.java:79)
org.seasar.framework.container.hotdeploy.HotdeployFilter.doHotdeployFilter(HotdeployFilter.java:99)
org.seasar.framework.container.hotdeploy.HotdeployFilter.doFilter(HotdeployFilter.java:67)
org.seasar.extension.filter.EncodingFilter.doFilter(EncodingFilter.java:69)

トラックバック - http://d.hatena.ne.jp/dewa/20080131