ADFアプリのリソースチューニング
ADFアプリのリソースチューニングというと、独自のチューニングポイントとしてアプリケーション・モジュール・プールがありますが、ここでは大抵のJavaEEアプリで問題となるHTTPセッションタイムアウトとデータベース接続プールについて考えます。
HTTPセッションタイムアウト
HTTPセッションタイムアウト時間は、5分程度の短めの時間を設定しておくのがよいようです。
従来の(非AJAXな)JavaEEアプリとして業務アプリなどを作る場合は、30分や60分といった長めの時間を設定することが多かったと思います。このようなアプリケーションでは、画面遷移のタイミングでしかリクエストが発生しないために、入力画面での長考によってタイムアウトが頻発することになるからです。
また、大抵のORMフレームワークでは、HTTPリクエストからレスポンスの間にトランザクションを完結させるように作ってあるので、セッション属性に積むデータ量にさえ気をつければ、HTTPセッション残留によるリソースの専有はそれほど気をつけなくても良かったということもあります。
ADFの場合は事情が変わります。ADFアプリケーションでは、複数のリクエストを跨がってトランザクションが維持されます。これは、内部的には、HTTPセッションとDB接続が、アプリケーションモジュールを介して結び付けられることによって実現されています。このため、セッションの長時間残留はDB接続の枯渇に繋がります。
一方で、ADF Facesの画面は部分書き換えや値検証などのためにリクエストが頻発するため、作業中にセッションタイムアウトに至る確率はかなり抑えられます。また、デフォルトの設定では、2分ほどサーバとの通信がない場合はダイアログが表示され、「OK」をクリックするとサーバ通信を行うので、ここでもタイムアウトの発生を抑止できます。
このような事情から、HTTPセッションタイムアウトは短めに設定するのがよいです。
データベース接続プール
データベース接続プールについては、最大数を多めに設定しましょう。同時利用ユーザが10名程度でも、接続プールの最大値は50以上は確保するべきです。
すでに述べた通り、ADFアプリでは複数リクエストに跨がってDB接続を保持しますので、リクエスト単位で開放される従来型のフレームワークに比べて、同時利用ユーザ数に対するDB接続使用数が格段に増加します。
また、複数のアプリケーションモジュールを構成するアプリケーションの場合、トランザクション分離のために、アプリケーションモジュールごとに個別のDB接続を保持することもあります。つまり、ひとつのセッションで複数の接続を専有することもありえます。
(ちなみに、1回限りのサービス提供のためのアプリケーションモジュールの場合、サービスメソッドの最初でgetDBTransaction、最後でDBTransaction#closeTransactionを呼ぶようにしましょう。DB接続の保持期間をメソッド実行中のみに制限できます)
今時のOracle Databeseであれば、MAX_SESSIONは数百程度に設定しても割と平気なので、ケチケチせずに行きましょう。
ADF Tableの末尾に新規行を追加するには
ADFのデータコントロールで、新規行を追加するOperationBindingは、CreateInsertにしろCreateWithParamにしろ、「カーソル位置への挿入」なので、データの末尾に追加するというのが素直に行えません。しょぼい。
OTNのディスカッションでも時々話題になるようで、いくつか方法も提案されてます。翻訳しようと思ったんですが、まあソースコード見ればなんとなくわかるので、リンクだけ。
Unwinding ADF: How to add a new row at the end of the ADF Table
Luc Bors Weblog: ADF 11g : How to control where a new row is inserted
画面やデータモデルの仕様にもよりますが、選択状態にかかわらず行追加は必ず最後に、ということであれば、ViewObject#insertRowメソッドをオーバライドしてしまう前者の方法がシンプルでいいかも知れません。
連番カラムを設けて、行挿入後にADF TableにSortEventを放り込むというのも試してみましたが、これだと新規行が表示されませんでした。ViewObjectへの行追加がTableに反映される前にSortEventが処理されてしまい、その後のPartial Refreshが行われないせいかなーと推測。
それにしても、できるだけJavaやSQLを書かずに済むように、というポリシーはいいと思うんだけど、結局この程度でコード書かなきゃいけないっていう中途半端さがイケてないなあ。
ADF Table とデータコントロールの選択状態同期
だんだん内容がショボくなって参ります。
データコントロールツリーからコレクションをデザイナ上にドラッグ・ドロップしてTableを構成するとき、行選択を有効にしておくと勝手に選択状態が同期するようになるのですが、一度行選択を無効にして構成してしまうと、後で RowSelection 属性の値だけ変えても、見かけ上は行選択できるようになりますが、データコントロールまで反映されません。
で、毎回「どの属性設定するんだっけ…」と悩むことになるので、ここにメモしときます。
選択状態の同期は、af:table 要素の以下の2つの属性にEL式を設定します。
- selectionListener : #{bindings.(DC名).collectionModel.makeCurrent}
- selectedRowKeys : #{bindings.(DC名).collectionModel.selectedRow}
selectionListener属性で、Tableの選択状態の変更を拾ってデータコントロールへつなげ、selectedRowKeysでデータコントロールの選択状態をTableに反映する、という感じ。
あと、アクションメソッド内で「選択された行の項目Nを取得したい」なんてときは、無理にRichTableオブジェクトごゴニョゴニョするよりは、同期するデータコントロールの取得したい項目を個別にバインドしておいて、EL式#{bindings.(項目名).inputValue}などで取ったほうが楽です。
参考:
http://otndnld.oracle.co.jp/document/products/jdev/11/doc_cd/web.1111/B52028-01/web_tables_forms.htm
PagePhaseListener のメモ
ADFには、JSFのPhaseEventとは別に、ページライフサイクルの各フェーズの開始・終了で発火するPagePhaseEventというのがあります。
こいつのリスナ、PagePhaseListener は、呼ばれるフェーズを選択できず、毎フェーズの開始/終了で必ず呼ばれます(JSFでいうところの「ANY_PHASE」)。ページ生成時に一回だけ呼ばれる前提でリソースを確保すると、えらいことになるので注意。
フェーズの順序は、開発者ガイドの以下のページで説明されています。
http://otndnld.oracle.co.jp/document/products/jdev/11/doc_cd/web.1111/B52028-01/adf_lifecycle.htm
…とまあここまで書いておいてなんですが、大抵の用途ではJSFのPhaseListenerだけで事足りると思います(というか、PagePhaseListenerのアドバンテージが今ひとつ思い浮かばない)。
それから、JSFでも同様ですが、複数コンポーネントの書き換えが同時に発生する場合は、ひとつのスレッドで上記のライフサイクルが複数回回ったりするのでこれも注意。
DB接続のようなリソースを、1リクエストの中で確保して解放、というような時は、ここではなく、素直にServletFilterとして組み込んだほうが安全ですね。
PhaseEvent, PagePhaseEvent をそれぞれ拾って、フェーズ名をログに出力するコードを置いときます。両方組み込んで実行すると、JSF,ADFの各フェーズがどういう絡みになっているか、なんとなく読み解けるかも。
https://github.com/shout-poor/Smallcodes/tree/master/study_adf
表で合計行の表示
最近 Oracle ADF を使ってます。古き良きC/S時代のVisualBasicみたいなデータバインディング付きのUIコンポーネントなんかがあったり、コンポーネントをIDEのデザイナ上でペタペタ貼って画面を作ったりと、おじさんにはすんなり理解できそうな感じがよいです。まあ、JDeveloperが生成するXMLの山や、「動かしてみないとわからない」感が非常に危なっかしいという面もありますが。
ただ、テキトーに書くとうまく動いてくれず、情報も少ないので、ちょっとしたことでも実装方法を探すのに余計な時間を食わされるのが辛いところ。
ということで、しょーもないプラクティスをメモ代わりに書いていこうという次第。
で、一回目は、データバインディングした表(af:table)に合計行を表示する手順。VBのグリッドコントロールならプロパティ一発なんですけどもねえ。
- データ元となる View Object に属性を追加する。属性の編集ダイアログ「ビュー属性」で、「値のタイプ」を「式」にし、「object.getRowSet().sum("(集計したい属性名)")」を設定。ここの集計関数は、sum 以外に count, avg, min, max が標準で用意されてる模様。
- 表示するページのデータバインディング定義を開き、1で追加した属性をバインディングに追加する。
- 表示するページのデザイナを開き、ADF表の集計したいカラムを選択し、右クリック→「ファセット - Column」→「Footer」をクリック。すると、フッタ行が表示され、選択したカラムの下にFooterファセットが追加される。
- Footerファセットに出力テキスト(af:outputText)を追加し、value属性にEL式「#{binding.(1で追加した属性名).inputValue}」を設定する。
この際、 データコントロールから属性をドラッグ&ドロップすると、value属性が「#{row.(属性名)}」となり、フッタでは row が取れないのでうまく表示されない。
以上。
Footerファセットにはalignの設定ができないので、右寄せにしたい場合は出力テキストをJSFコンポーネントのPanel Grid(h:panelgrid)でラップして、Grid Panelのstyle属性にtext-align:right;を追加してあげましょう。
また、集計元のカラムが編集可能で、編集したら即座に再計算したい場合は、4 で追加した出力テキストのPartialTriggers属性に、集計元フィールドのidを設定してあげるとよいです。
ちなみに、View Object の属性の初期値欄に入力する式は、Oracleのドキュメントで「Groovy Expression」と説明されていて、Groovyの書式に従って解釈されるようですよ。
別解としては、集計関数込みのSQLで新たな View Object を定義して、元のView Objectとリンクするという方法も。グルーピング単位が固定になってしまうのと、DBにコミットするまで再計算されなくなりますが、件数が多い場合はこっちのほうがパフォーマンスはよいかと思います。