2008-01-31
SAStruts + S2JDBC でサンプルアプリを作ってみました
Seasar | |
以前、WEB+DB PRESS vol. 41 にて特集2『つらいJavaからたのしいJavaへ Seasar2 サクサク開発 実践カリキュラム』を執筆させて頂きました。
(http://gihyo.jp/magazine/wdpress/archive/2007/vol41)
この記事では、プレゼンテーション層にTeeda、データアクセス層にDBFluteというフレームワーク使ってサンプルアプリケーションを作成しました。
今回は、前回と同じ外部仕様、同じデータベースによる
サンプルアプリケーションのSAStruts + S2JDBC版を作ってみました。
ただし、現時点では「正常系が動作した」というレベルの完成度です。
前回のサンプルアプリケーションと比較して、以下のような未実装な機能が
多く存在するので注意が必要です。
- 一部の日付書式
- バリデーション
- 二度押し防止
- 戻るボタン対策
- 共通レイアウト
- 共通エラーページ
ソースコードを見てもらう前に外部仕様を見てみましょう。
『検索条件入力』 ⇒ 『検索結果一覧 』
⇒ 『更新』 ⇒ 『更新確認』 ⇒ 『更新完了』
という典型的な流れです。
次に、データベース(ERD)を見てみましょう。
従業員(EMP)テーブルに部署(DEPT)テーブルが関連している
典型的なデータ構造です。
アプリケーション用に作成したクラスやJSPファイルは以下のとおりです。
■Actionクラス
- EmpAction.java
■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>
現時点では、非機能要件的な機能が実装されていないにも関わらず、
ファイル数やコード量がとても少ないですねぇ。
参考:


アンカータグのhrefはf:u()を使ってエンコーディングしたほうが良いです。
> アンカータグのhrefはf:u()を使ってエンコーディングしたほうが良いです。
エスケープ&エンコーディングを施すように修正しました。
社員変更確認画面で[変更]ボタンを押すと、以下の例外が発生してしまいます。
いろいろと調べてみたのですが、解決できません。
何か心当たりございますでしょうか?よろしくお願いいたします。
org.seasar.framework.exception.ParseRuntimeException: [ESSR0050]解析に失敗しました。理由はjava.text.ParseException: Unparseable date: "1990/01/01"
上手くいきました。
Emp emp = Beans.createAndCopy(Emp.class, this).dateConverter("yyyy/MM/dd").execute();
コンソールには、以下のエラーが出ているのですが、原因が分かりません。
何か心当たりがあれば、教えて頂けませんでしょうか。
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]
<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)