<?xml version="1.0" encoding="UTF-8" ?><rdf:RDF 
  xmlns="http://purl.org/rss/1.0/"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xml:lang="ja">
  <channel rdf:about="http://w.atwiki.jp/seesaa/">
    <title>Struts1.x @ ウィキ</title>
    <link>http://w.atwiki.jp/seesaa/</link>
    <atom:link href="https://w.atwiki.jp/seesaa/rss10.xml" rel="self" type="application/rss+xml" />
    <atom:link rel="hub" href="https://pubsubhubbub.appspot.com" />
    <description>Struts1.x @ ウィキ</description>

    <dc:language>ja</dc:language>
    <dc:date>2010-02-14T22:40:46+09:00</dc:date>
    <utime>1266154846</utime>

    <items>
      <rdf:Seq>
                <rdf:li rdf:resource="https://w.atwiki.jp/seesaa/pages/15.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/seesaa/pages/2.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/seesaa/pages/14.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/seesaa/pages/13.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/seesaa/pages/1.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/seesaa/pages/4.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/seesaa/pages/5.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/seesaa/pages/6.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/seesaa/pages/7.html" />
                <rdf:li rdf:resource="https://w.atwiki.jp/seesaa/pages/8.html" />
              </rdf:Seq>
    </items>
	
		
    
  </channel>
    <item rdf:about="https://w.atwiki.jp/seesaa/pages/15.html">
    <title>機能リファレンス</title>
    <link>https://w.atwiki.jp/seesaa/pages/15.html</link>
    <description>
      WebアプリケーションのStruts1.x + aspectJ + iBatisでの実装例です。
[[SAStruts:http://sastruts.seasar.org/featureReference.html]]の機能リファレンスを参考に書いています。


** プロジェクト構成 [#oc51dbdc]

プロジェクト構成は、[[SAStruts:http://sastruts.seasar.org/featureReference.html]]のように、下記構成でもよいですが、
（SAStruts構成でよければ、SAStrutsを採用すると高度なURLルーティング等が使用できたかと思います）

 ・ルートパッケージ.action
 ・ルートパッケージ.form
 ・ルートパッケージ.entity
 ・ルートパッケージ.service

ユースケース単位で作成してもよいです。

 ・ルートパッケージ.共通.entity（テーブルと１対１のentity）
 ・ルートパッケージ.共通.dbservice（マスタテーブルのトランザクションサービス）
 ・ルートパッケージ.共通.dao（マスタテーブルのdao）
 ・ルートパッケージ.ユースケース１.action
 ・ルートパッケージ.ユースケース１.dbservice
 ・ルートパッケージ.ユースケース１.dao
 ・ルートパッケージ.ユースケース２.action
 ・ルートパッケージ.ユースケース２.dbservice
 ・ルートパッケージ.ユースケース２.dao

この構成だと、「ルートパッケージ.共通.jar」と「ルートパッケージ.ユースケース１.jar]があればWebアプリは動作します。
機能変更の影響範囲と、ビルド単位をあわせることができます（共通.jarの影響範囲は全ユースケースですが・・・）

特別なフレームワークの使用は無いため、プロジェクト構成に制約はありません。









** アーキテクチャ [#xf43a255]
Strutsなので、MVC(Model View Controller)のアーキテクチャに基づいていて、 Modelはエンティティ、 ViewはJSP、Controllerはアクションになります。









*** トランザクション [#h8e78bd4]
トランザクションは、xxxxxDbServiceクラスのpublicメソッドの開始と終了時に開始、コミット、ロールバックされます。
マニュアルトランザクションをAspectJで差込ます。
 	
 /** トランザクション開始ポイントカット */
 pointcut atUpdateable() : 
     execution(public * *..*DbService.*(..) throws Exception) 
     &amp;&amp; !within(DaoAspect);
 
 /** トランザクションを開始するアドバイス */
 before() throws Exception : atUpdateable() {
     // サービスのdaoゲッターを呼び、SqlMapClientを取得する
     SqlMapClientWrapper sqlMap = getTargetSqlMapClient(joinPoint);
     // トランザクションを開始する
     SqlMapClientManager.start(sqlMap, joinPoint);
 }
 	
 /** 正常系でトランザクションを終了するアドバイス */
 after() returning throws Exception : atUpdateable() {
     // サービスのdaoゲッターを呼び、SqlMapClientを取得する
     SqlMapClientWrapper sqlMap = getTargetSqlMapClient(joinPoint);
     // コミット
     SqlMapClientManager.commit(sqlMap, joinPoint);
     // トランザクションを終了する
     SqlMapClientManager.end(sqlMap, joinPoint);
 }
 	
 /** 読み書き可能且つ異常系でトランザクションを終了するアドバイス */
 after() throwing(Exception e) throws Exception : atUpdateable() {
     // サービスのdaoゲッターを呼び、SqlMapClientを取得する
     SqlMapClientWrapper sqlMap = getTargetSqlMapClient(joinPoint);
     // ロールバックログを出力するためにtrueを設定
     sqlMap.setRollback(true);
     // トランザクションを終了する
     SqlMapClientManager.end(sqlMap, joinPoint);
     throw e;
 } 

xxxxxDbServiceクラスのpublicメソッド呼び出しの際、すでにトランザクションが開始されている場合、
そのトランザクションは引き継がれます（同じトランザクション内でSQLが発行されます。）
[[SAStrutsのデフォルト設定:http://sastruts.seasar.org/featureReference.html#Transaction]]と同様に、トランザクション属性はRequiredとなります。
ただし、このAOPでは、トランザクション属性はRequires_newは設定できません。

Requires_newが必要な場合は、別トランザクションで伝票番号採番等を処を行い、その結果を引数に、トランザクションを開始します。
ログ出力等も同じく、別トランザクションで出力します。

 public ActionForward execute() throws Exception {
 
     // 開始ログ出力（ログは別トランザクション）
     logDbService.writeLog();
 
     // 伝票番号採番（採番後そのトランザクションはコミットする。）
     long denpyoNo = denpyoDbService.createDenpyoNo();
 
     // 新規伝票作成（伝票作成、在庫引き当て、出荷手配を行う。）
     denpyoDbService.regist(denpyoNo, date);
 	
     // 終了ログ出力（ログは別トランザクション）
     logDbService.writeLog();
 
     return mapping.findForward(&quot;success&quot;);
 }








*** ロストアップデートを防ぐ [#vb97cea2]
同じRDBMSを共有するので、ロストアップデートが発生します。ロストアップデートは楽観ロックで行います。
各テーブルは更新日時（TimeStamp）を持ち、その値が更新されていないことを確認し、ロストアップデートを防ぎます。
すでに更新日時が更新されている場合、ロストアップデートとしてエラーとします。
以下の楽観ロックをサポートするAspectJを提供します。

 /** Dao楽観ロック更新、削除実行時のポイントカット */
 pointcut atDaoOptimisticLockExecute() : 
     execution(public * *..*DaoImpl.*WithOptimisticLock(..))
     &amp;&amp; !within(DaoAspect);
 
 after() returning(Object o) throws Exception : atDaoOptimisticLockExecute() {
     int updRecode = ((Integer)o).intValue();
     if (updRecode == 0) { // 更新件数が0件の場合
         throw new AplBusinessException(&quot;申し訳ありません。ご指定のデータは他の利用者がすでに更新しています。
                                         データ整合性を保つために、検索からやり直してください。&quot;);
     } 
 }








*** サイクルデットロックを防ぐ [#y3bc7862]
サイクルデットロックを防ぐには、ロックを取得するテーブルとレコードの順序をすべての機能で同じにすることです。
更新するbean（関連するbeanを含む）に対し、クラス名の昇順、主キーの昇順でSELECT～WITH UPDLOCKすればよいでしょうか。
このロジックは、IBatorでentityを生成した場合は可能かなと思っています（現在未実装）








*** サービス [#s0eb6528]
サービスとは、トランザクションの制御クラスとしましす。
xxxxxDbServiceクラスは、DBトランザクションを制御します。
xxxxxServiceクラスは、その他BMPを制御します。

xxxxxDbServiceクラスは、コンストラクタ呼び出し時に、使用するDaoをインジェクションします。
フィールドインジェクションではないですが（サクセサは必要）、以下のメンバ定義でインジェクションされます。

 /** 特定健康診断データDAOインスタンス */
 @DaoInjection
 private HealthDiagnosDao healthDiagnosDataDao = null;

または、別途ファイルに定義することも可能です。
ファイル定義と@DaoInjectionでは、ファイル定義が優先されます。
単体テスト時などは、ファイル定義でスタブDaoをインジェクションさせることができます。


COLOR(blue){※ &#039;&#039;ビジネスロジックはどこに実装する。&#039;&#039;}

COLOR(blue){ビジネスロジックの実装は[[PofEAA:http://www.amazon.co.jp/exec/obidos/ASIN/4798105538/hatena-ud-22/ref=nosim]]ではentityとなっています。のでentityに実装します。}
COLOR(blue){ところでビジネスロジックとはなんでしょうか？[[SAStruts:http://sastruts.seasar.org/featureReference.html#Architecture]]にまとめられていますので、それを踏まえ下記２つと定義します。}
 
-COLOR(blue){ビジネスロジック}
--COLOR(blue){導出プロパティ(メソッド)}
--COLOR(blue){判定メソッド}

COLOR(blue){導出プロパティは単価計算等。判定プロパティは、区分値を参照した判定ロジック等。}
COLOR(blue){この２つをentityに実装します。この実装方針により、ビジネスロジックは完全に共通化されます（entityは１つなので）}
COLOR(blue){また、entityは関連するリレーショナルデータをすべてもっているので、ビジネスロジックメソッドの引数は簡単なものになります。}
 
COLOR(blue){さて、上記ビジネスロジックだけではイベント処理は成立しません。これらを呼び出しならが、データの操作が必要です。}
COLOR(blue){このデータ操作処理はxxxxxDbServiceクラスに実装します。xxxxxDbServiceクラスはイベントに依存する可能性があるので、}
COLOR(blue){処理の共通化が難しいかもしれません。共通化が難しいところは、実装が簡単な方法としましょう。}
 
COLOR(blue){上記のような実装をすれば、次のテスト方法でC2に近い網羅率となり、品質が確保されると思います。}
 
-COLOR(blue){entityに実装したビジネスロジック →unitテストでC2を確保します。}
-COLOR(blue){xxxxxDbServiceクラス             →結合テストでC1を確保します。}

COLOR(blue){xxxxxDbServiceクラスがC1で良い実装であれば、多少共通化されていなくても、テスト工数が爆発することはありません。}









*** 例外のハンドリング [#cb0f1ab0]
例外はStrutsの機能でキャッチします。

 &lt;global-exceptions&gt;
    &lt;!-- 回復可能例外 --&gt;
    &lt;exception key=&quot;&quot;
               path=&quot;&quot;
               handler=&quot;exp.common.web.exception.AplExceptionHandler&quot;
               type=&quot;exp.common.exception.AbsRecoveryException&quot;&gt;
    &lt;/exception&gt;
    &lt;!-- システム例外 --&gt;
    &lt;exception key=&quot;&quot;
               path=&quot;/jsp/common/error.jsp&quot;
               handler=&quot;exp.common.web.exception.AplExceptionHandler&quot;
               type=&quot;java.lang.Exception&quot;&gt;
    &lt;/exception&gt;
 &lt;/global-exceptions&gt;

回復可能例外は、エラーを設定し遷移元画面に遷移します（戻ります）
 // 例外ハンドリングを行う
 if (ex instanceof AbsRecoveryException) {
     errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(ex.getMessage(), false));
     forward = mapping.getInputForward();// 戻り先はinput属性の指定先
 }
 ※複数行エラーに対応すると、便利かと思います。

システムエラーは、ログ出力を行いシステムエラー画面に遷移します。
その際のログ内容は自由に設定できます。エラー発生時のForm、Session、パラメータ等。








** アクション [#h65ae736]
通常のStruts1.xのアクションクラスです。
ただ、やはりアクションの開始、終了には共通的な処理を差し込みたいものです。

-ログイン済みかチェックする。
-セッション情報を、Thread Local Storageに格納し、同Thread内のどこからでも参照可能にする。
-log4j NDCにユーザー識別子を設定する。
-ログの出力。

アクションの開始、終了は、AspectJで処理を差込ます。

 /** ececuteポイントカット */
 pointcut atExecute() : 
    execution(public ActionForward *..*Action.execute(..) throws Exception) 
    &amp;&amp; !within(*..test.action.*) // テスト用Actionクラス
    &amp;&amp; !within(ActionAspect);
 
 /** ececuteを実行するbeforeアドバイス */
 before() throws Exception : atExecute() {
 
    HttpServletRequest request = (HttpServletRequest)((thisJoinPoint.getArgs())[2]);
    HttpSession session = request.getSession();
    String userId = AssertionUtil.getUserId(session);
    
    // アサーションが妥当かチェックする。
    CheackAssertion.check(session); 
    
    // プロセス毎のコンテキストを設定する。
    ContextUtil.setUserId(userId); // ユーザーID
    
    // log4j診断コンテキストの設定
    NDC.push(userId);
    
    // 操作情報ログを出力する。
    ActionMapping mapping = (ActionMapping)((thisJoinPoint.getArgs())[0]);
    ActionForm actionform = (ActionForm)((thisJoinPoint.getArgs())[1]);
    ActionLogger.write(userId, mapping, actionform);
 }
 
 /** ececuteを実行するafterアドバイス */
 after() throws Exception : atExecute() {
    
    // log4j診断コンテキストの削除
    NDC.remove();
 } 

Struts2.xのだと簡単に実装できるかと思いますが、
Struts1.x + AspectJでも十分対応できるのではと思います。








*** データ変換 [#bb877af5]
entity（Model）とform(View)のデータは異なります。
formのデータはすべてString型で、entityはLongだったり、Dateだったり。
formの日付は、西暦だったり、和暦だったり。数値の表示は3桁,区切りだったり。
entityはIDを管理しますが、formは名称のケースがほとんどです。
entityとformのデータ変換を行わないといけません。
このデータ変換は、各form単位に作成しなくてはなりません。

ただし、このデータ変換の際にDBアクセスが発生してはいけません。すでにトランザクションは終了しています。
（トランザクション範囲を広くすればよいという問題ではないと思います。トランザクションは設計されたものです。）

iBatisを使用すれば、entityは関連するデータをすべて取得する（O/Rマッピング）ので、
formへ設定する情報はすべてそろっていると思います。IDに関連する名称等。
後は、Stringへの変換を１とつ１とつ実装するとよいと思います（変換ロジックは、簡単に変換Util化できると思います）
各formには、toFrom(entity), toEntiry()メソッドがあれば、ソースの可読性は確保されるかと思います。

 ※リストボックスの値は？
   entityは関連があるのではれば、リレーショナルをたどって取得可能かと思います。
   関連性がないなら別entityで取得し、toFrom(entity, entity,・・・)のような実装が可能かと思います。
   entityは一つでなくてもよいですよね。








** JSP [#m02cff1b]
相変わらずHTMLとロジックの分離はできませんがJSPを使用します。HTMLモック作成はデザイナーが、JSPへの変換はプログラマが行います。
JSPへの変換後、複雑なjavascriptを記載します。javascriptをどれだけ書けるかが、高機能なWebページ作成の肝だと思います。
高機能なWebページ作成するには、プログラマーがページ全体を設計しなければいけません・・

taglibは「JSTL」を使います。JSTLであればJSPへの変換は簡単です。
あと、テーブル表示には[[struts-layout:http://struts.improve-technologies.com/]]を使用します。
layout:row でページング、ソートが簡単に実装できます。ページングするデータはセッションに保存されます。
layout:rowはJSTLと相性がよく下記のような実装が可能です。

例）実装例
1ページ25行、ドキュメントID列はソート可能、hrefを含む、
name=&quot;document[&lt;c:out value=&quot;${index}&quot; /&gt;]のhidden項目については、Struts側でBeanのListとして復元される（インラインエディットが可能）

 &lt;layout:row styleClass=&quot;downloadListTable&quot;&gt;
    &lt;layout:pager maxPageItems=&quot;25&quot;&gt;
        &lt;layout:collection name=&quot;documentList&quot; id=&quot;document&quot; indexId=&quot;index&quot; styleClass=&quot;dataGrid&quot; styleClass2=&quot;dataGrid2&quot;&gt;
            &lt;layout:collectionItem title=&quot;ドキュメントID&quot;  style=&quot;width:110px;text-align:center;&quot; sortable=&quot;true&quot;&gt;
 
            &lt;c:if test=&quot;${document.deleteFlg}&quot; &gt;
                &lt;c:out value=&quot;${document.did}&quot;/&gt;
            &lt;/c:if&gt;        
            &lt;c:if test=&quot;${!document.deleteFlg}&quot; &gt;
                &lt;a href=&quot;javascript:doDownload(&lt;c:out value=&quot;${invalue=&quot;${index}&quot; /&gt;;&quot; class=&quot;link&quot;&gt;&lt;c:out value=&quot;${document.did}&quot;/&gt;&lt;/a&gt;
            &lt;/c:if&gt;                                                
                &lt;input type=&quot;hidden&quot; name=&quot;document[&lt;c:out value=&quot;${index}&quot; /&gt;].docuocument.documentId}&quot; /&gt;&quot; /&gt;
                &lt;input type=&quot;hidden&quot; name=&quot;document[&lt;c:out value=&quot;${index}&quot; /&gt;].updTment.updTime}&quot; /&gt;&quot; /&gt;
                &lt;input type=&quot;hidden&quot; name=&quot;document[&lt;c:out value=&quot;${index}&quot; /&gt;].datet.date}&quot; /&gt;&quot; /&gt;
                &lt;input type=&quot;hidden&quot; name=&quot;document[&lt;c:out value=&quot;${index}&quot; /&gt;].hosp{document.hospitalName}&quot; /&gt;&quot; /&gt;
                &lt;input type=&quot;hidden&quot; name=&quot;document[&lt;c:out value=&quot;${index}&quot; /&gt;].did&quot;.did}&quot; /&gt;&quot; /&gt;
                &lt;input type=&quot;hidden&quot; name=&quot;document[&lt;c:out value=&quot;${index}&quot; /&gt;].down{document.downloadDate}&quot; /&gt;&quot; /&gt;
                &lt;input type=&quot;hidden&quot; name=&quot;document[&lt;c:out value=&quot;${index}&quot; /&gt;].delecument.deleteFlg}&quot; /&gt;&quot; /&gt;
                &lt;input type=&quot;hidden&quot; name=&quot;document[&lt;c:out value=&quot;${index}&quot; /&gt;].expiocument.expiryDate}&quot; /&gt;&quot; /&gt;
                &lt;input type=&quot;hidden&quot; name=&quot;document[&lt;c:out value=&quot;${index}&quot; /&gt;].etc&quot;.etc}&quot; /&gt;&quot; /&gt;
                
            &lt;/layout:collectionItem&gt;
            &lt;layout:collectionItem style=&quot;width:110px;text-align:center;&quot; title=&quot;検診日&quot; property=&quot;date&quot;/&gt;
            &lt;layout:collectionItem style=&quot;width:300px;text-align:center;&quot; title=&quot;病院&quot;  property=&quot;hospitalName&quot;/&gt;
            &lt;layout:collectionItem style=&quot;width:110px;text-align:center;&quot; title=&quot;ダウンロード期限&quot; property=&quot;expiryDate&quot;/&gt;
            &lt;layout:collectionItem style=&quot;width:210px;text-align:center;&quot; title=&quot;&quot; property=&quot;etc&quot;/&gt;
        &lt;/layout:collection&gt;
    &lt;/layout:pager&gt;
 &lt;/layout:row&gt;
ページング、ソートを独自実装するには、下記SortActionを拡張すればよいかと思います。

 &lt;action path=&quot;/sort&quot;
     type=&quot;fr.improve.struts.taglib.layout.sort.SortAction&quot;
     scope=&quot;request&quot;
     validate=&quot;false&quot;&gt;
  &lt;/action&gt;








** iBatis [#c30c8596]
パーシステンス層には[[iBatis:http://ibatis.apache.org/index.html]]を使用します。
lazy loadを使用したORマッピングも可能なようですが、select joinでも実装可能です。
lazy loadはトランザクション外でselectが発生する可能性があるのでselect joinで実装します。[[Lazy Loading vs. Joins:http://svn.apache.org/repos/asf/ibatis/java/ibatis-2/trunk/ibatis-2-docs/ja/iBATIS-SqlMaps-2_ja.pdf]]も参考ください。
下記はユーザーIDに関連するグループ情報を取得する定義です。

    &lt;!-- ユーザマスタ --&gt;
    &lt;resultMap id=&quot;userResult&quot; class=&quot;User&quot; groupBy = &quot;userId&quot;&gt; // ① userIdでDISTINCT
        &lt;result column=&quot;user_id&quot; jdbcType=&quot;BIGINT&quot; property=&quot;userId&quot; /&gt;
        &lt;result column=&quot;user_kind&quot; jdbcType=&quot;CHAR&quot; property=&quot;userKind&quot; /&gt;
        &lt;result column=&quot;user_login_id&quot; jdbcType=&quot;VARCHAR&quot; property=&quot;userLoginId&quot; /&gt;
        &lt;result column=&quot;user_sex_code&quot; jdbcType=&quot;CHAR&quot; property=&quot;userSexCode&quot; /&gt;
        &lt;result column=&quot;user_birthday&quot; jdbcType=&quot;DATE&quot; property=&quot;userBirthday&quot; /&gt;
        &lt;result column=&quot;user_delete_flg&quot; jdbcType=&quot;BIT&quot; property=&quot;userDeleteFlg&quot; /&gt;
        &lt;result property=&quot;userGroupList&quot; resultMap=&quot;user.userGroupResult&quot;/&gt; // ② 1対nマッピング
        &lt;result property=&quot;openRuleList&quot; resultMap=&quot;user.openRuleResult&quot;/&gt; // ③ 1対nマッピング
    &lt;/resultMap&gt;
    
    &lt;!-- ユーザグループ --&gt;
    &lt;resultMap id=&quot;userGroupResult&quot; class=&quot;UserGroup&quot; groupBy = &quot;userGroupId&quot;&gt;  // ④ userGroupIdでDISTINCT
    &lt;result column=&quot;user_group_id&quot; property=&quot;userGroupId&quot; jdbcType=&quot;BIGINT&quot; /&gt;
    &lt;result column=&quot;user_id&quot; property=&quot;userId&quot; jdbcType=&quot;BIGINT&quot; /&gt;
    &lt;result column=&quot;group_id&quot; property=&quot;groupId&quot; jdbcType=&quot;BIGINT&quot; /&gt;
        &lt;result property=&quot;userGroupAttributeList&quot; resultMap=&quot;user.userGroupAttributeResult&quot;/&gt; // ⑤ 1対nマッピング
        &lt;result property=&quot;group&quot; resultMap=&quot;user.groupResult&quot;/&gt; // ⑥ n対1マッピング
    &lt;/resultMap&gt;
    
    &lt;!-- ユーザグループ属性 --&gt;
    &lt;resultMap id=&quot;userGroupAttributeResult&quot; class=&quot;UserGroupAttribute&quot; &gt;
    &lt;result column=&quot;user_group_attribute_id&quot; property=&quot;userGroupAttributeId&quot; jdbcType=&quot;BIGINT&quot; /&gt;
    &lt;result column=&quot;user_group_id&quot; property=&quot;userGroupId&quot; jdbcType=&quot;BIGINT&quot; /&gt;
    &lt;result column=&quot;user_group_attribute_attribute&quot; property=&quot;userGroupAttributeAttribute&quot; jdbcType=&quot;VARCHAR&quot; /&gt;
    &lt;/resultMap&gt;
     
    &lt;!-- グループマスタ --&gt;
    &lt;resultMap id=&quot;groupResult&quot; class=&quot;Group&quot;&gt;
        &lt;result column=&quot;group_id&quot; property=&quot;groupId&quot; jdbcType=&quot;BIGINT&quot; /&gt;
        &lt;result column=&quot;group_name&quot; property=&quot;groupName&quot; jdbcType=&quot;VARCHAR&quot; /&gt;
    &lt;result column=&quot;group_nickname&quot; property=&quot;groupNickname&quot; jdbcType=&quot;VARCHAR&quot; /&gt;
    &lt;/resultMap&gt;
     
    &lt;!-- ユーザデータを取得するSQL --&gt;
    &lt;select id=&quot;select_by_condition&quot; parameterClass=&quot;UserCondition&quot; resultMap=&quot;userResult&quot;&gt;
        SELECT
            *
        FROM
            m_user usr // ⑦ 取得するテーブルをすべてjoinする
            LEFT JOIN
                m_user_group userGroup on usr.user_id = userGroup.user_id
        LEFT JOIN
            m_user_group_attribute attribute on userGroup.user_group_id = attribute.user_group_id
        LEFT JOIN
            m_group grp on userGroup.group_id = grp.group_id
        WHERE
            usr.delete_flg = false
            &lt;isNotEmpty property=&quot;userId&quot; prepend=&quot;AND&quot;&gt;  // ⑧ 可変条件
                usr.user_id = #userId#
            &lt;/isNotEmpty&gt;
            &lt;isNotEmpty property=&quot;groupId&quot; prepend=&quot;AND&quot;&gt; // ⑨ 可変条件
                grp.group_id = #groupId#
            &lt;/isNotEmpty&gt;
        ORDER BY
            $oderBy$  // ⑩ ソート条件
    &lt;/select&gt;

userResultを記載するのは大変です。ので、[[IBator&gt;機能リファレンス#zf865547]]でテーブルより自動生成します。
②③⑤⑥の定義は自動生成してくれません（できるかもしれません？）ので、手動で追加します。
自動生成の際にentityも生成してくれます。②③⑤⑥に対応するアクセサは自動生成してくれませんので、手動で追加します。
自動生成クラスを継承する形でentityクラスを作成し、その中で②③⑤⑥を実装すればよいかと思います。
自動生成クラスを継承したentityクラスには、[[ビジネスロジック&gt;機能リファレンス#s0eb6528]]も実装します。








*** entityの自動生成 [#zf865547]
[[iBatis&gt;機能リファレンス#c30c8596]]の定義を作成するため[[IBator:http://ibatis.apache.org/ibator.html]]を使用します。
次のファイルが自動生成されます。

 ・SqlMap　→&quot;resultMap&quot;,&quot;insert文&quot;,&quot;update文&quot;が利用可能です。
 ・entity　→entityとして利用可能です。
 ・dao　　 →利用しません。








** バリデータ [#kfe94cdc]
Strutsのvalidation.xmlを利用するのは生産性が悪いと思います。validation.xmlの一番のうりであるjavascript検証コードの自動生成も
すべてのバリデータに対して行われるわけではありません（一番使用したいvalidwhenが自動生成されないのが残念です）
javascript検証コードは別途作成すると割り切って、ValidatorForm#validateをオーバーライドするほうがよいと思います。





** ファイルアップロード [#rdb4cc22]
Strutsはマルチパートに対応しているので、特別な実装はありません。
formにbyte[]のアクセサを用意すれば、アップロードされたファイルが取得できます。







** Ajax [#x513d65d]
Ajaxも通常のhttp通信のため、特別な実装はありません。
画面遷移はしませんので、ActionForward=nullを返します。
値を返す場合は、[[PrintWriter:http://hrastaman.blog92.fc2.com/blog-entry-32.html]]で値を返します。
（PrintWriterはちょっと特別な実装なので、ライブラリ化したほうがいいでしょう。）






** ログ出力 [#d6cfb4ac]
ログの出力は、AspectJを利用すれば、actionの開始、終了時、Daoの開始、終了等に設定できます。また、それらの入力情報も出力できるかと思います。
しかし、入力情報も出力はセキュリティ上問題です。ログファイルに機微なデータが記録されてしまいます。「ログファイルは参照禁止」となると本末転倒ですね。
例えば、「いつ、だれが、何データ」を参照したかをロギングするこは、ログの監査として有効かもしれません。

Daoの親クラスを作成し、selectを発行する（queryForList）ラッパーメソッドを作成し、
「ユーザ識別子」、「発行するselect文のID」（SQLをログに出力することもNGだと思います）、「検索条件」をログ出力すればよいと思います。
「ユーザ識別子」はlog4jの[[NDC&gt;機能リファレンス#h65ae736]]を使用します。
「検索条件」に機微なデータが指定されたら？　ラッパーメソッドの機能に「検索条件」を出力制御フラグを用意すればよいでしょう。

もう面倒なので、ログ出力を暗号化しては？
ログのリアルタイム監視ができなくなります・・・

ログの設計は重要ですね。    </description>
    <dc:date>2010-02-14T22:40:46+09:00</dc:date>
    <utime>1266154846</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/seesaa/pages/2.html">
    <title>メニュー</title>
    <link>https://w.atwiki.jp/seesaa/pages/2.html</link>
    <description>
      **メニュー
[[top]]
[[機能リファレンス]]
　  ・[[プロジェクト構成&gt;機能リファレンス#oc51dbdc]]
　  ・[[アーキテクチャ&gt;機能リファレンス#xf43a255]]
　　　  [[トランザクション&gt;機能リファレンス#h8e78bd4]]
　　　  [[ロストアップデート&gt;機能リファレンス#vb97cea2]]
　　　  [[サイクルデットロック&gt;機能リファレンス#y3bc7862]]
　　　  [[サービス&gt;機能リファレンス#s0eb6528]]
　　　  [[例外のハンドリング&gt;機能リファレンス#cb0f1ab0]]
　  ・[[アクション&gt;機能リファレンス#h65ae736]]
　　　  [[データ変換&gt;機能リファレンス#bb877af5]]
　  ・[[JSP&gt;機能リファレンス#m02cff1b]]
　  ・[[iBatis&gt;機能リファレンス#c30c8596]]
　　　　[[entityの自動生成&gt;機能リファレンス#zf865547]]
　  ・[[バリデータ&gt;機能リファレンス#kfe94cdc]]
　  ・[[ファイルアップロード&gt;機能リファレンス#rdb4cc22]]
　  ・[[Ajax&gt;機能リファレンス#x513d65d]]
　  ・[[ログ出力&gt;機能リファレンス#d6cfb4ac]]
----


// リンクを張るには &quot;[&quot; 2つで文字列を括ります。
// &quot;&gt;&quot; の左側に文字、右側にURLを記述するとリンクになります


//**更新履歴
//#recent(20)

&amp;link_editmenu(text=ここを編集)    </description>
    <dc:date>2010-02-13T01:13:18+09:00</dc:date>
    <utime>1265991198</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/seesaa/pages/14.html">
    <title>右メニュー</title>
    <link>https://w.atwiki.jp/seesaa/pages/14.html</link>
    <description>
      //**更新履歴
#recent(5)

&amp;link_editmenu(text=ここを編集)    </description>
    <dc:date>2010-02-13T00:14:00+09:00</dc:date>
    <utime>1265987640</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/seesaa/pages/13.html">
    <title>top</title>
    <link>https://w.atwiki.jp/seesaa/pages/13.html</link>
    <description>
      * はじめに [#o87353e6]

Webアプリケーション構築についての備忘録です。
Struts1.x + AspectJ + iBatisを使用します。

今さら「Strtus1.x」感はあるかと思いますが。
本当は、[[SAStrtus:http://sastruts.seasar.org/index.html]]を採用したかったのですが、SAStrutsほど高機能でなくてもよいかなと思いました。

とにかく、フレームワーク（制御の逆転パターン）はStruts1.xだけにして、その他は、クラスライブラリで実装できれば思っています。
（aspectJは、クロージャーと思い込んでます。DIコンテナより理解しやすいかなと。）
フレームワークは難しいです。

** Struts1.x [#nc0a6964]
これまでに経験したWebフレームワークは次です。

 ・ASP.NET WebForm
 ・Teeda(少しだけ)
 ・MyFaces(少しだけ)
 ・Struts1.xベース独自フレームワーク（重量級）

どれも高機能なフレームワークだと思いますが、
高機能なWebページを作るための道具ではないかな、と感じていたます。
WebForm GridViewの生成するHTMLを予想して、javascriptを書くのはもうこりごりです。
このごろリリースされた「ASP.NET MVC」でも、Webページを作成するには、
 ・HTTPプロトコルの理解。
 ・HTMLの理解。
とうたっていたと思います。

上記を理解するなら、フレームワークはそれをサポートするだけで十分ではないかな、と思います。Ajax通信なんかもStruts1.xで十分です。
ASP.NET MVCが最強かもしれませんが、やっぱり、慣れ親しんだStrtus1.xが・・・
Struts1.xというよりも、BeanUtils#populateが理解しやすいかと思います。
気合入れてstrtus-config.xmlを書きます！（これは書きたくない。。）

** iBatis [#ef1d6dbc]
プロジェクトの成功のカギは「SQLの数」だと思います。
「SQLの数」が少なければ、品質のよいアプリが作成できるのではないでしょうか。
SQLは集合を操作します。集合をテストするのは手間がかかります。
SQLが多いと、テスト工数が膨大になります（テストしないと品質が低下します）
画面のイベント単位でSQLが発行されると、テスト工数は爆発します。

その解決策が(シンプル)O/Rマッピングです（ER図をbeanにマップするもの）
ユースケースに関連するテーブルデータすべてをbeanとして取得します。
しかし、O/Rマッピングのほとんどが、lazy load方式だと思います。lazy loadはまた難しい。。
そこでiBatisです。lazy load以外でO/Rマッピングを実現します（他ツールも可能と思いますが）
iBatisは、ユースケースに関連するテーブルデータをすべて結合して１SQLで取得します。
（絞り込み条件は可変長にして自由に取得します。oder byも指定可能に）
取得したデータは重複項目があるのでDISTINCTします（COLOR(red){iBatisがbeanに詰める際に！！}）
hibernateは難しかったですが、iBatisなら扱えそうです。

** AspectJ [#q05b7657]
DBを操作するので、トランザクション処理が必要です。「分散トランザクション」ではありませんが、コネクションの引き回しが不要だと助かります。
AspectJで、トランザクションの開始、コミット、ロールバックを差込ます。
また、トランザクション開始の際、すでにトランザクションが開始されている場合は、それを引き継ぐこととしてます（TransactionOption.Required）
これだけの機能であれば、DIコンテナも不要かなと思います。

あとは、アクションの開始と終了にも処理を差込みます。
例えば、log4jのNDCにユーザ識別子をセットします。
ログの出力は、このユーザ識別子も出力することとします。
また、DBの共通フッター（登録ユーザーID、更新ユーザーID）もユーザ識別子をセットします（・・・セットしたいです）

** 最後に [#t6ba9b20]
システム全体を考えると、ASP.NET MVCが安価かと思います。
ActiveDirectryと連携した「プロトコルトランジション」による、統合されたシステム構築が可能です。
「プロトコルトランジション」については、tomcatとActiveDirectryを連携させ、かつ、SQLServer用JDBCドライバを用い
Keroberos認証を行えば実現できるかもしれません。
シンプルなStruts1.xで高機能なWebアプリケーションが作成できればと思います。

 ※
 Struts1.x、iBatis - apache foundation、AspectJ - Eclipse Foundationと完全オープンソースなプロダクトです。
 企業への導入も安心です。３年目以降からサポート料発生なんてことはありません。
 （sun javaは問題ですが・・ openJDKに期待です！）    </description>
    <dc:date>2010-02-13T00:05:45+09:00</dc:date>
    <utime>1265987145</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/seesaa/pages/1.html">
    <title>トップページ</title>
    <link>https://w.atwiki.jp/seesaa/pages/1.html</link>
    <description>
      **@wikiへようこそ
-ウィキはみんなで気軽にホームページ編集できるツールです。
-このページは自由に編集することができます。
-メールで送られてきたパスワードを用いてログインすることで、各種変更（サイト名、トップページ、メンバー管理、サイドページ、デザイン、ページ管理、等）することができます

**まずはこちらをご覧ください。
-[[@wikiの基本操作&gt;http://atwiki.jp/guide/category2.html]]
-[[用途別のオススメ機能紹介&gt;http://atwiki.jp/guide/category22.html]]
-[[@wikiの設定/管理&gt;http://atwiki.jp/guide/category6.html]]

**分からないことは？
-[[@wiki ご利用ガイド&gt;http://atwiki.jp/guide/]]
-[[よくある質問&gt;http://atwiki.jp/guide/category1.html]]
-[[無料で会員登録できるSNS内の@wiki助け合いコミュニティ&gt;http://sns.atfb.jp/view_community2.php?no=112]]
-[[@wiki更新情報&gt;http://www1.atwiki.jp/guide/pages/264.html]]
-[[@wikiへのお問合せフォーム&gt;http://atwiki.jp/helpdesk]]
等をご活用ください

**@wiki助け合いコミュニティの掲示板スレッド一覧
#atfb_bbs_list(112)

**その他お勧めサービスについて
-[[大容量１Ｇ、PHP/CGI、MySQL、FTPが使える無料ホームページは@PAGES&gt;&gt;http://atpages.jp/]]
-[[無料ブログ作成は@WORDをご利用ください&gt;&gt;http://atword.jp/]]
-[[2ch型の無料掲示板は@chsをご利用ください&gt;&gt;http://atchs.jp/]]
-[[フォーラム型の無料掲示板は@bbをご利用ください&gt;&gt;http://atbb.jp/]]
-[[お絵かき掲示板は@paintをご利用ください&gt;&gt;http://atpaint.jp/]]
-[[その他の無料掲示板は@bbsをご利用ください&gt;&gt;http://atbbs.jp/]]
-[[無料ソーシャルプロフィールサービス @flabo(アットフラボ)&gt;&gt;http://sns.atfb.jp]]

**おすすめ機能
-[[気になるニュースをチェック&gt;http://atwiki.jp/guide/17_174_ja.html]]
-[[関連するブログ一覧を表示&gt;http://atwiki.jp/guide/17_161_ja.html]]

**その他にもいろいろな機能満載！！
-[[@wikiプラグイン&gt;http://atwiki.jp/guide/category17.html]]
-[[@wiki便利ツール&gt;http://atwiki.jp/guide/category32.html]]
-[[@wiki構文&gt;http://atwiki.jp/guide/category16.html]]
-[[@wikiプラグイン一覧&gt;http://www1.atwiki.jp/guide/pages/264.html]]
-[[まとめサイト作成支援ツール&gt;http://atwiki.jp/matome/]]

**バグ・不具合を見つけたら？ 要望がある場合は？
お手数ですが、メールでお問い合わせください。
    </description>
    <dc:date>2010-02-12T23:57:29+09:00</dc:date>
    <utime>1265986649</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/seesaa/pages/4.html">
    <title>プラグイン/ニュース</title>
    <link>https://w.atwiki.jp/seesaa/pages/4.html</link>
    <description>
      * ニュース
@wikiのwikiモードでは
 #news(興味のある単語)
と入力することで、あるキーワードに関連するニュース一覧を表示することができます
詳しくはこちらをご覧ください。
＝＞http://atwiki.jp/guide/17_174_ja.html


-----


たとえば、#news(wiki)と入力すると以下のように表示されます。


#news(wiki)
    </description>
    <dc:date>2010-02-12T23:57:29+09:00</dc:date>
    <utime>1265986649</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/seesaa/pages/5.html">
    <title>まとめサイト作成支援ツール</title>
    <link>https://w.atwiki.jp/seesaa/pages/5.html</link>
    <description>
      * まとめサイト作成支援ツールについて
@wikiには[[まとめサイト作成を支援するツール&gt;&gt;http://atwiki.jp/matome/]]があります。
また、
 #matome_list
と入力することで、注目の掲示板が一覧表示されます。

利用例）#matome_listと入力すると下記のように表示されます
#matome_list
    </description>
    <dc:date>2010-02-12T23:57:29+09:00</dc:date>
    <utime>1265986649</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/seesaa/pages/6.html">
    <title>プラグイン/編集履歴</title>
    <link>https://w.atwiki.jp/seesaa/pages/6.html</link>
    <description>
      * 更新履歴
@wikiのwikiモードでは
 #recent(数字)
と入力することで、wikiのページ更新履歴を表示することができます。
詳しくはこちらをご覧ください。
＝＞http://atwiki.jp/guide/17_117_ja.html


-----


たとえば、#recent(20)と入力すると以下のように表示されます。


#recent(20)
    </description>
    <dc:date>2010-02-12T23:57:29+09:00</dc:date>
    <utime>1265986649</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/seesaa/pages/7.html">
    <title>プラグイン/アーカイブ</title>
    <link>https://w.atwiki.jp/seesaa/pages/7.html</link>
    <description>
      * アーカイブ
@wikiのwikiモードでは
 #archive_log()
と入力することで、特定のウェブページを保存しておくことができます。
詳しくはこちらをご覧ください。
＝＞http://atwiki.jp/guide/25_171_ja.html


-----


たとえば、#archive_log()と入力すると以下のように表示されます。
保存したいURLとサイト名を入力して&quot;アーカイブログ&quot;をクリックしてみよう


#archive_log()
    </description>
    <dc:date>2010-02-12T23:57:29+09:00</dc:date>
    <utime>1265986649</utime>
  </item>
    <item rdf:about="https://w.atwiki.jp/seesaa/pages/8.html">
    <title>プラグイン/動画(Youtube)</title>
    <link>https://w.atwiki.jp/seesaa/pages/8.html</link>
    <description>
      * 動画(youtube)
@wikiのwikiモードでは
 #video(動画のURL)
と入力することで、動画を貼り付けることが出来ます。
詳しくはこちらをご覧ください。
＝＞http://atwiki.jp/guide/17_209_ja.html

また動画のURLはYoutubeのURLをご利用ください。
＝＞http://www.youtube.com/

-----


たとえば、#video(http://youtube.com/watch?v=kTV1CcS53JQ)と入力すると以下のように表示されます。


#video(http://youtube.com/watch?v=kTV1CcS53JQ)

    </description>
    <dc:date>2010-02-12T23:57:29+09:00</dc:date>
    <utime>1265986649</utime>
  </item>
  </rdf:RDF>
