2008 Autumn SAStrutsの の 開 発 Tips 出 羽 健 一 2007 Autumn The Seasar Foundation and the others 2008. all rights reserved. 1
はじめに このセッションの 内 容 SAStrutsを 使 った 開 発 において 悩 みそうなトピックに 絞 って 解 説 SAStrutsと 一 緒 に 使 用 されることが 多 い S2JDBCについても 扱 う SAStrutsの 基 本 的 な 内 容 については 扱 わない 公 式 ドキュメント( 1)や 以 前 のカンファレンス 資 料 ( 2) をどうぞ! 1 SAStrutsの 公 式 ページ http://sastruts.seasar.org/ 2 StrutsからSAStrutsへ http://event.seasarfoundation.org/sc2008spring/session#s4 2
SAStrutsの の 特 徴 Strutsベースのフレームワーク ク Strutsの 利 点 を 活 かせる ノウハウ 開 発 者 人 口 の 多 さ 実 行 速 度 安 定 性 Strutsのマイナス 要 素 が 排 除 されている 設 定 ファイル 地 獄 からの 開 放 ホットデプロイ 対 応 によるサクサク 開 発 ブラウザのリロードでソースコード 修 正 が 即 反 映 される 脱 CoC CoCはマッピングなど 必 要 最 小 限 に 限 定 明 示 的 なアノテーションベースの 開 発 エンタープライズはもちろん Strutsが t が 苦 手 なアジャイルもOK! 3
SAStrutsのおさらい 足 し 算 (JSP) <html:errors/> <s:form> <html:text property="arg1"/> + <html:text property="arg2"/> = ${f:h(result)}<br /> <input type="submit" name="submit" value="サブミット"/> </s:form> 4
SAStrutsのおさらい 足 し 算 (アクションフォーム) public class AddForm { @Required @IntegerType public String arg1; } @Required @IntegerType public String arg2; 5
SAStrutsのおさらい 足 し 算 (アクション) public class AddAction { @ActionForm @Resource protected AddForm addform; public Integer result; @Execute(validator = false) public String index() { return "index.jsp"; } } @Execute(input = "index.jsp") public String submit() { result = Integer.valueOf(addForm.arg1) + Integer.valueOf(addForm.arg2); return "index.jsp"; } 6
アーキテクチャ JSP JSP 上 の 変 数 SAStruts Action Form S2BeanUtils (Beans) Entity S2JDBC N 1 1 1 プロパティ 0 1 プロパティ 1 Table フィールド 画 面 の 入 出 力 項 目 (アノテーションによる 検 証 ) 検 証 メソッド ユースケース ス 単 位 読 み 取 り 専 用 プロパティ 実 行 メソッド データ 変 換 入 れポン 出 しポン Service 呼 び 出 し 画 面 遷 移 Action << DI >> 1 << DI >> Service 導 出 項 目 0 1 区 分 値 ( 定 数 ) 拡 張 プロパティ エンティティ 単 位 S2AbstractServiceを 継 承 入 れポン 出 しポン 以 外 のロジック 7
レイヤ モデル 図 JSP 要 検 討! フォーム アクション DTO エンティティ サービス モデルの 詰 め 替 え 処 理 はアクションで 行 う 重 要 JSPにエンティティを 持 ち 込 まないアーキテクチャも 検 討 に 値 する 8
レイヤ モデルのアンチパターン1 サービスメソッドの 引 数 にアクションフォームを 渡 す 下 位 レイヤは 上 位 レイヤのモジュールに 依 存 すべき でない 検 索 条 件 はアクションフォームからDtoに 詰 め 替 えた ものをサービスへ 渡 そう 9
レイヤ モデルのアンチパターン2 アクションフォームからDtoに 詰 め 替 えたものを サービスに 渡 し サービス 内 でDtoからエンティ ティに 詰 め 替 える 画 面 入 力 値 をDBに 入 れるだけであれば INとOUT をテストすれば 良 いので 途 中 の 無 駄 な 詰 め 替 え 処 理 は 減 らしたい アクション 内 でアクションフォームからエンティティに 詰 め 替 えて それをサービスへ 渡 すようにする 10
アクションフォーム 責 務 画 面 の 入 出 力 項 目 入 力 値 の 検 証 (アノテーション メソッド) 11
アクションフォーム ポイント 入 力 チェック 対 象 プロパティの 型 : String 型 String[] 型 boolean 型 型 変 換 を 保 証 するアノテーション: @IntegerType, @DateType など プロパティをString 型 で 持 つと HTTPプロトコルとの 相 性 が 良 くてハマりにくい 12
バリデータの 全 体 像 JSP アクションフォーム アクション エラーメッセージ 出 力 <html:errors/> or <html:errors property= xxx /> <html:errors property= yyy /> <html:errors property= zzz /> フォーム <s:form> </s:form> 検 証 用 の アノテーション ターゲット 指 定 独 自 作 成 可 能 実 行 メソッド エラー 時 の 遷 移 先 指 定 検 証 メソッドの 指 定 検 証 メソッド ( 複 数 指 定 可 ) エラー 時 の 継 続 制 御 (stoponvalidationerror) applicaton_ja.properties ラベル 指 定 (labels.xxx= ) メッセージテンプレートの 変 更 エラーメッセージ 出 力 (<html:errors/>)の 書 式 カスタマイズ 13
アクション 責 務 リクエスト 処 理 画 面 系 データとDB 系 データの 変 換 入 れポン 出 しポン 系 データアクセスロジック 入 れポン 出 しポン 以 外 の 業 務 ロジック 出 力 用 プロパティ 画 面 遷 移 14
アクション: 実 行 メソッド 実 行 メソッドは2 種 類 に 分 けると 良 い 入 力 系 処 理 の 実 行 メソッド 出 力 系 処 理 の 実 行 メソッド 上 記 のように 分 けておくと ボタン 追 加 や 画 面 遷 移 などの 仕 様 変 更 時 の 修 正 コストが 少 なくなる 15
アクション: 実 行 メソッド 入 力 系 処 理 の 実 行 メソッド 呼 ばれるタイミング: サブミットが 呼 ばれた 時 主 処 理 : 画 面 入 力 項 目 をDBへ 格 納 する バリデーション: あり 粒 度 : サブミットボタンの 数 だけ 作 る 命 名 : doから 始 まる 名 前 にしておくと 分 かりやすい 戻 り 値 : 出 力 系 処 理 の 実 行 メソッドを 呼 ぶ 16
アクション: 実 行 メソッド 出 力 系 処 理 の 実 行 メソッド 呼 ばれるタイミング: 画 面 を 表 示 する 時 主 処 理 : DBのデータを 画 面 に 出 力 する バリデーション: なし 粒 度 : 1つのJSPに 付 き1メソッド 命 名 : メソッド 名 はJSP 名 と 同 じ 戻 り 値 : JSPファイル 17
アクションフォームのDI 短 いアクションフォームの 名 前 で 扱 う @Resource @ActionForm protected HogeFugaForm g hogefugaform; g name 要 素 で 短 い 名 前 を 指 定 @Resource(name = hogefugaform") @ActionForm protected HogeFugaForm form; 18
サービス S2JDBCを 使 う 場 合 は S2AbstractServiceの 使 用 を 推 奨 します 1つのエンティティに 対 し 1つのサービスを 作 成 する S2AbstractServiceを 使 うと JdbcManagerの 薄 いラッパーとして 扱 える 19
Serviceのクラス 図 << abstract >> S2AbstractService FW 開 発 者 アーキテクト << abstract >> AbstractService プログラマ EmpService DeptService Service 20
S2AbstractServiceの の 中 身 ( 抜 粋 ) public abstract S2AbstractService<T> { @Resource protected JdbcManager jdbcmanager; protected Class<T> entityclass;... public AutoSelect<T> select() { return jdbcmanager.from(entityclass); } } public int insert(t entity) { return jdbcmanager.insert(entity).execute(); }... 21
アプリケーション 側 のサービス アーキテクト public abstract class AbstractService<ENTITY> extends S2AbstractService<ENTITY> ts { } public class EmpService extends AbstractService<Employee> ts { } プログラマ この 時 点 では サービスの 中 身 は 空 っぽ 22
S2AbstractServiceの の 利 用 イメージ 呼 び 出 しイメージ: Employee employee = employeeservice.findbyid(5); employee.name = Dewa ; employeeservice.update(employee); findbyidメソッドやupdateメソッドはs2abstractserviceクラスで 定 義 されている 23
S2AbstractServiceの の 利 用 イメージ 流 れるようなインターフェース もOK @Resource protected EmployeeService employeeservice; public List<Employee> employees; @Execute(validate = false) public String list() { employees = employeeservice.select().innerjoin( innerjoin( department ).where(new SimpleWhere().ge( age, form.age_ge).le( age, form.age_le)).orderby( sort ).resultlist(); st(); } return list.jsp ; 24
AbstractServiceにはどんなメソッドを 定 義 する? 現 時 点 では S2AbstractServiceを 継 承 した AbstractServiceや 各 サービスクラスは 空 っぽ では 何 のために 存 在 するのか? どんなメソッドを 定 義 するべきか? 25
AbstractService JdbcManagerの 薄 いラッパーとして 機 能 する 利 用 例 : insertメソッドのオーバーライド 以 下 のメソッドをAbstractServiceに 定 義 しておくと DB 側 で 規 定 値 をセットするケースに 有 効 /** * エンティティを 挿 入 します * ただし MODIFY_TIMESTAMP, VERSION のフィールドについては * エンティティの 値 は 使 用 しません */ @Override public int insert(entity entity) { return jdbcmanager.insert(entity).excludes("modifytimestamp", "version").execute(); } updateメソッドにも 有 効 26
個 別 サービスクラスの 実 装 個 別 のサービスクラスにはどんなメソッドを 実 装 すべきか? 入 れポン 出 しポン 以 外 の データアクセスロジック 27
データアクセスロジック データアクセスロジックの 分 類 入 れポン 出 しポン 画 面 入 力 値 をDBへ 入 れる DBの 値 を 画 面 項 目 として 表 示 する サービスにはメソッドを 定 義 しないが サービスを 使 って アクションから 呼 び 出 す 入 れポン 出 しポン 以 外 各 種 チェックロジック 例 : ログイン 認 証 チェック サービスにメソッド 定 義 してアクションから 呼 び 出 す 28
ログイン 認 証 処 理 は 入 れポン 出 しポン 処 理 ではない 入 れポン 出 しポン 以 外 の データアクセスロジックの 例 public class LoginAuthService extends AbstractService<LoginAuth> { public boolean checklogin(string loginid, String password) { LoginAuth loginauth = select().where(new SimpleWhere().eq("loginId", userid).eq( password", password)).getsingleresult(); l return loginauth == null? false : true; } } 29
入 れポン 出 しポン 以 外 の データアクセスロジックの 例 /* ログインボタンが 押 された 時 に 呼 ばれる */ @Execute(input = login") public String dologin() { boolean isloginok = loginauthservice.checklogin (form.loginid, form.password); if (!isloginok) { // ログイン 失 敗 した 時 の 処 理 } 入 れポン 出 しポン 以 外 の // ログイン 成 功 した 時 の 処 理 データアクセス 処 理 は サービスクラスに 自 分 で } 定 義 したメソッドを 呼 び 出 す 30
入 れポン 出 しポン のまとめ Webアプリの 多 くが 入 れポン 出 しポン 系 入 れポン 出 しポンのデータアクセスロジックは サービスを 活 用 して アクションに 記 述 する 入 れポン 出 しポン 以 外 のデータアクセスロジック は サービスにメソッド 定 義 したものをアクション から 呼 び 出 す 31
テスト アクションの 実 行 メソッドに 対 して INPUTとOUTPUTを 意 識 したテストを 実 施 モックを 使 わないでDB 経 由 するテスト INPUT アクションフォームのプロパティ OUTPUT アクションフォームのプロパティ アクションのプロパティ DB 32
テスト 入 れポン 出 しポン 以 外 のデータアクセスロジック サービスクラスのpublicメソッドに 対 するテスト モックを 使 用 しないDB 経 由 のテスト 33
データ 詰 め 替 え 主 に フォーム と エンティティ の 詰 め 替 え 詰 め 替 えはアクションにて 行 う 詰 め 替 え 処 理 はメソッド 化 しておくと 単 体 テストしやすい スコープは protected convert ではじまるメソッド 名 とかに 統 一 しておくと 分 かりやすい 例 : Employee employee = convertemployee(xxxform); convert(xxxform, employee); 34
エンティティ 共 通 の 親 クラス 区 分 値 ( 定 数 ) 拡 張 プロパティ ( 導 出 項 目 ) 35
エンティティ: 共 通 の 親 クラス 全 てのエンティティに 共 通 して 保 持 する プロパティは 親 エンティティに 持 たせる 36
エンティティ: 共 通 の 親 クラス 共 通 の 親 クラス(サンプル) @MappedSuperclass public abstract class AbstractEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Integer id; @Temporal(TemporalType.TIMESTAMP) public Date createtimestamp; @Temporal(TemporalType.TIMESTAMP) public Date modifytimestamp; } @Version public Integer version; 37
エンティティ: 共 通 の 親 クラス 子 クラス(サンプル) @Entity public class User extends AbstractEntity { public String name; public Integer height; public Integer weight; } @Temporal(TemporalType.DATE) public Date birthday; 38
エンティティ: 区 分 値 ( 定 数 ) DB 上 のフィールドの 区 分 値 は エンティティに 定 数 として 定 義 しておくと 便 利 ハードコーディングを 避 けれる 汎 用 性 の 高 い 区 分 値 は enum の 使 用 も 検 討 すべ し! 39
エンティティ: 区 分 値 ( 定 数 ) サンプルコード public class User extends AbstractEntity { //=============================================== // 定 数 // ==== public static final Integer STATUS_WORKING = 1; public static final Integer STATUS_EATING = 2; public static final Integer STATUS_SLEEPING = 3; //=============================================== // プロパティ // ======= public String name; } public Integer status; 40
エンティティ: 区 分 値 ( 定 数 ) サンプルコード 寝 ているユーザーの 一 覧 を 取 得 する List<User> users = userservice.select().where(new SimpleWhere().eq("status", User.STATUS_SLEEPING)).getResultList(); userservice は 下 記 のUserServiceクラスの インスタンスであり DIされているものとする public class UserService extends AbstractService<User>{ } 41
エンティティ: 拡 張 プロパティ エンティティにDBとは 無 関 係 のプロパティ (publicフィールド)を 追 加 したい しかし 不 用 意 に 追 加 すると SQL 文 の 自 動 生 成 時 に 追 加 した 列 が 含 まれてエラーになる 解 決 案 : 永 続 化 対 象 外 であることを 指 定 する @Transient を 使 う 金 額 = 単 価 数 量 のような 導 出 項 目 は 読 み 取 り 専 用 プロパティとしてgetterで 定 義 する 42
エンティティ: 拡 張 プロパティ サンプル @Entity public class User extends AbstractEntity { } //============================================ // 拡 張 プロパティ // =========== /** * 回 答 済 かどうか * @return true 回 答 済, false 未 回 答 */ @Transient @Transient が 無 いと public boolean isresponding; S2JDBCのSQL 自 動 生 成 & 実 行 時 にエラーとなる 43
登 録 後 のF5による2 重 登 録 問 題 問 題 確 認 画 面 完 了 画 面 登 録 1 登 録 処 理 2 リロード or F5 操 作 リロード(F5) 時 に 直 前 のリクエストが 発 行 される 二 重 登 録 されてしまう! 44
登 録 後 のF5による2 重 登 録 問 題 対 策 登 録 処 理 の 後 にリダイレクトさせる 確 認 画 面 完 了 画 面 登 録 1 登 録 処 理 2リダイレクト 3 リロード or F5 F5 操 作 リロード(F5)しても 直 前 のリクエスト (=リダイレクトのURL)が) 発 行 されるため 登 録 処 理 は 行 われず 完 了 画 面 が 表 示 される 45
まとめ 46
ご 清 聴 ありがとう ございました 47