この章では、OC4JにデプロイするためのJ2EEアプリケーションを開発する際に考慮をお薦めするベスト・プラクティスについて説明します。次の項が含まれます。
次の項では、OC4JにデプロイするためのJSPページを開発する際に考慮をお薦めするベスト・プラクティスについて説明します。
HTTPセッションを使用すると、使用されるメモリーの量によって、Webアプリケーションのパフォーマンスにオーバーヘッドが加わります。JSPでは、セッションがデフォルトで有効になっています。
必要ない場合には、HTTPセッション・オブジェクトを使用しないようにします。JSPページでHTTPセッションが必要ない場合(基本的に、セッション属性を格納または取得する必要がない場合)は、セッションを使用しないように指定できます。これを指定するには、次のようにpage
ディレクティブを使用します。
<%@ page session="false" %>
これにより、セッションの作成または取得に関するオーバーヘッドがなくなるため、ページのパフォーマンスが向上します。
サーブレットはデフォルトではセッションを使用しませんが、JSPページはデフォルトでセッションを使用することに注意してください。
ojspc
ユーティリティを使用してデプロイの前にJSPを変換することを検討します。これにより、ユーザーがJSPページに初めてアクセスしたときの変換によるパフォーマンスの低下を回避できます。事前変換の利点に関するその他の説明については、『Oracle Containers for J2EE JavaServer Pages開発者ガイド』を参照してください。
デフォルトでは、JSPページはページ・バッファと呼ばれるメモリーの領域を使用します。このバッファ(デフォルトでは8KB)は、ページが動的なグローバリゼーション・サポートのコンテンツ・タイプの設定、転送またはエラー・ページを使用する場合に必要です。これらの機能を使用しない場合は、次のように、page
ディレクティブでバッファを無効にできます。
<%@ page buffer="none" %>
これによってメモリーの使用が減少し、バッファをコピーする出力ステップが節約されるため、ページのパフォーマンスが向上します。
JSPページ間で制御を受け渡すには、<jsp:forward>
標準アクション・タグを使用する方法と、スクリプトレット内でresponse.sendRedirect()
にリダイレクトURLを渡す方法という2つのオプションがあります。
<jsp:forward>
オプションを使用する方が高速で効率的です。この標準アクションを使用すると、転送されたターゲット・ページはJSPランタイムによって内部的に起動され、リクエストの処理が続けられます。ブラウザでは転送が行われたことが一切認識されず、ユーザーからはプロセス全体がシームレスに行われているように見えます。
sendRedirect()
を使用すると、ブラウザではリダイレクトされたページに対して、実際に新しいリクエストを行う必要があります。ブラウザに表示されるURLは、リダイレクトされたページのURLに変わります。さらに、リダイレクトには新しいリクエストが含まれるため、リダイレクトされたページでは、リクエストのスコープ・オブジェクトがすべて利用できなくなります。
ユーザーがページをリロードする際に、実際に実行されているページをURLに反映させる場合にのみ、リダイレクトを使用します。
一部のJSPページへのアクセスをアプリケーションのみに限定し、ユーザーが直接起動できないようにする場合があります。特に、Model-View-Controller(MVC)のようなアーキテクチャでは、これが必要になります。
例として、フロントエンドつまり表示ページがindex.jsp
であるとします。ユーザーは、このページに直接アクセスするURLリクエストを使用して、アプリケーションを起動します。さらに、index.jsp
には2番目のページであるincluded.jsp
が含まれ、3番目のページforwarded.jsp
に対する転送を行うものとし、ユーザーがURLリクエストを使用してこれらのページを直接起動できないようにするとします。
このために、included.jsp
およびforwarded.jsp
をアプリケーションの/WEB-INF
ディレクトリに置きます。ここに置かれたページは、URLリクエストを使用して直接起動することができません。起動しようとすると、ブラウザからエラー・レポートが返されます。
index.jsp
ページでは次のような文を使用します。
<jsp:include page="WEB-INF/included.jsp"/> ... <jsp:forward page="WEB-INF/forwarded.jsp"/>
アプリケーションの構造は次のようになります。サーブレット、Enterprise JavaBeans(EJB)モジュールまたはその他のクラスに対する標準のclasses
ディレクトリと、JARまたはZIPファイル用の標準のlib
ディレクトリが含まれます。
index.jsp WEB-INF/ web.xml included.jsp forwarded.jsp classes/ lib/
<orion-web-app>
要素のjsp-timeout属性に、秒単位の時間を示す整数値を設定します。この時間が経過すると、リクエストされなかったJSPページはメモリーから削除されます。これにより、頻繁にコールされないページがある場合、リソースが解放されます。デフォルト値は0
で、タイムアウトは発生しません。<orion-web-app>
要素は、OC4Jのglobal-web-application.xml
ファイルとorion-web.xml
ファイルにあります。OC4Jインスタンス内のすべてのアプリケーションにタイムアウトを適用するには、global-web-application.xml
ファイルを変更します。特定のアプリケーションに対する構成値を設定するには、アプリケーション固有のorion-web.xml
ファイルを変更します。
J2EEのクラスのロードのベスト・プラクティスについては、「クラスのロードのベスト・プラクティス」を参照してください。
次の項では、セッションに関して考慮する必要のあるベスト・プラクティスについて説明します。
HTTPセッションは、Webブラウザとの対話状態を保持するために使用されます。そのため、保持している情報が失われると、クライアントは対話を最初から開始する必要があります。
したがって、セッションの状態をデータベースに保存しておくと安全です。ただし、このようにするとパフォーマンスに影響があります。このオーバーヘッドを許容できる場合は、セッションを保持することが最善の方法です。パフォーマンス、スケーラビリティおよび可用性に影響を与える状態の安全性を実装する場合、トレードオフがあります。状態が安全なアプリケーションを実装しない場合、次のような影響があります。
1つのJVMプロセスの障害により、多くのユーザー・セッションで障害が発生します。たとえば、ユーザーがオンライン・ショッピング、複数のページ・フォームでの入力または共有ドキュメントの編集などで行った作業が失われ、最初からやりなおすことになります。
セッション・データのデータベースからのロードとデータベースへの格納を行う必要がないため、CPUのオーバーヘッドが減り、パフォーマンスが向上します。
ユーザーが非アクティブのときにはセッション・データによってJVMヒープが阻害されるため、JVMがサポートできる同時セッションの数が減り、スケーラビリティが低下します。
これに対し、状態が安全なアプリケーションは、アクティブなリクエストに対してのみJVMヒープにセッション状態が存在するように作成できます。通常、これはアクティブなセッション数の100分の1です。
状態が安全なアプリケーションのパフォーマンスを改善するには、次のようにします。
セッションの状態を最小限にします。たとえば、セキュリティ・ロールにより、何千ものオブジェクトに詳細なパーミッションがマップされる場合があります。すべてのセキュリティ・パーミッションをセッション状態として保存するのではなく、ロールIDのみを保存します。キャッシュを維持し、多数のセッションで共有して、ロールIDを個々のパーミッションにマップします。
頻繁に変更される重要なセッション変数を識別し、このような属性をCookieに格納することで、ほとんどのリクエストでデータベースの更新が行われないようにします。
頻繁に読み取られる重要なセッション変数を識別し、HttpSession
をこのようなセッション・データに対するキャッシュとして使用することで、リクエストのたびにデータベースの読取りが行われるのを防ぎます。キャッシュを手動で同期化する必要があるため、計画的および非計画的なトランザクションのロールバックの処理に注意する必要があります。
セッション・オブジェクトに格納されたオブジェクトは、セッションがタイムアウトするまで(または無効にされるまで)解放されません。明示的にプールに解放しないかぎり再利用できない共有リソース(JDBC接続など)を保持した場合、このようなリソースはプールに適切に返されず、再利用できなくなる可能性があります。
セッション・タイムアウトが頻繁に発生したり、セッションの生存が続いてメモリーを消費したりしないように、セッションのタイムアウト(setMaxInactiveInterval()
)を適切に設定します。
セッション・オブジェクトに格納する予定のデータの、メモリーの使用状況を監視します。セッションがタイムアウトする前に作成されるセッションの数に対して、十分なメモリーがあることを確認します。
通常、Cookieはユーザー・セッションを追跡するために、(コンテナによって自動的に)Webブラウザに設定されます。場合によっては、このCookieが、単一のユーザー・セッションよりさらに長く残ることがあります。(たとえば、エンド・ユーザーの地理的な場所を決定するために、1回のみ設定する場合など。)したがって、クライアントのディスク上に存在するCookieは長期間有効な情報の保存に使用できるため、サーバー・サイドのセッションには短期間だけ有効な情報を保存するのが普通です。このような状況では、長期間保持されるCookieは、新しいセッションが確立したときに、サーバーへの最初のリクエストでのみ解析する必要があります。サーバーで作成されるセッション・オブジェクトには関連するすべての情報を含めて、リクエストごとにCookieを解析する必要がないようにする必要があります。クライアント・サイドの新しいCookieには、サーバー・サイドのセッション・オブジェクトを識別するためのIDのみを含むように設定します。この処理は、セッションを使用するすべてのJSPページに対して自動的に行われます。これにより、セッション・オブジェクトの内容を長期的なCookieから作成しなおす必要がないため、パフォーマンスが向上します。もう1つのオプションは、ユーザー設定をサーバーのデータベースに保存し、ユーザー・ログインを保持するというものです。その後は、一意のユーザーIDを使用してデータベースから内容を取得し、セッションに情報を格納できます。
Oracle Application Serverは、セッション・オブジェクトが更新されると、セッションを自動的にレプリケートします。セッション・オブジェクトに細かいオブジェクト(人の名前など)が含まれていると、アイランド内のすべてのサーバーに対して多くの更新イベントが発生します。したがって、セッション内では粗密なオブジェクト(名前属性ではなく人オブジェクトなど)を使用することをお薦めします。
Oracle Application Serverは、セッション内の一時データをアイランドのサーバー間ではレプリケートしません。これにより、レプリケーションのオーバーヘッド(およびメモリーの必要量)が減少します。そのため、一時型を多用するようにします。
アクティブなユーザーの数は、システムにログインしているユーザーの数と比較してかなり少ないのが普通です。たとえば、Webサイトにログインしている100ユーザーに対し、ある時点で実際に何かを実行しているのは10ユーザーのみという場合もあります。
通常、セッションはシステム上のユーザーごとに確立されます。当然のことながら、セッションごとにメモリーが使用されます。
ログアウト・ボタンのような簡単な方法で、すばやくセッションを非アクティブにして削除できるようにします。これにより、まだタイムアウトしていないすべてのセッションとは対照的に、システム上のセッションがアクティブなユーザーの数に近くなり、メモリー使用量の増加がなくなります。
セッションの作成状態を検証することで、セッションを軽量メカニズムとして使用します。
長期間存在するセッションに対してはCookieを使用します。
セッションが失われた場合でもリカバリできるように、リカバリ可能なデータをセッションに格納します。リカバリできないデータは、(ファイル・システムに、またはJDBCを使用してデータベースに)永続的に保管します。ただし、すべてのデータを永続的に格納すると多くのリソースを使用します。かわりに、セッションにデータを保存し、HttpSessionBindingListener
または他のイベントを使用して、セッションのクローズの間に永続的ストレージにデータをフラッシュします。
スティッキー・セッションと分散可能なセッション
分散可能なセッションのデータは、シリアライズしてフェイルオーバーに役立つようにする必要があります。ただし、データをシリアライズしてピア・プロセス間でレプリケートする必要があるため、多くのリソースを使用します。
スティッキー・セッションは、複数のJVM間でのロード・バランシングに影響しますが、状態のレプリケーションがないためリソースの使用は少なくて済みます。
この項では、Enterprise JavaBeans(EJB)のベスト・プラクティスについて説明します。次の項目が含まれています。
EJBモジュールはローカルまたはリモートにすることができます。EJBモジュールを実行するのと同じコンテナからEJBモジュールを呼び出して開始する場合は、マーシャリング、アンマーシャリングおよびネットワーク通信のオーバーヘッドが発生しないため、ローカルEJBモジュールの方が適しています。また、ローカルBeanを使用すると、オブジェクトを参照で渡すことができるので、パフォーマンスがさらに向上します。
リモートEJBモジュールを使用すると、異なるマシンまたは異なるアプリケーション・サーバー・インスタンス、もしくはその両方と対話できます。この場合は、値オブジェクト・パターンを使用し、ネットワーク・トラフィックを減らすことでパフォーマンスを向上させることが重要です。
EJBモジュールを作成する場合は、リモートEJBオブジェクトの上にローカルEJBモジュールを作成します。違いはEJBオブジェクトでの例外のみであるため、ほとんどすべての実装Beanコードは変わりません。
また、同期呼出しが必要ない場合は、メッセージドリブンBeanの方がより適切です。
EJBモジュールは、永続性、トランザクション・セキュリティ、ネーミングなどの便利なサービスを備えたコンポーネント・アーキテクチャに基づく、再使用可能なコンポーネントです。ただし、これらを追加すると重くなります。
機能の抽象化のみが必要で、EJBコンテナのサービスを利用しない場合は、単純なJavaBeanを使用するか、または必要な機能をJSPまたはサーブレットを使用して実装することを検討する必要があります。
ほとんどのJ2EEサービスおよびリソースでは、Java Naming and Directory Interface(JNDI)の最初の呼出しでハンドルを取得する必要があります。このようなリソースは、EJBホーム・オブジェクトまたはJMSトピックである場合があります。その結果、同じクライアントが別の実行スレッドでJNDIサービスにアクセスして同じデータをフェッチしていたとしても、サーバー・マシンを呼び出してJNDI参照を解決することになります。
したがって、サービス・ロケータを使用することをお薦めします。サービス・ロケータはJNDIサービスに対するローカル・プロキシのようなもので、クライアント・プログラムはローカルなサービス・ロケータと対話し、実際のJNDIサービスとの対話は必要な場合にのみサービス・ロケータが行います。
製品にバンドルされているJavaオブジェクト・キャッシュを使用して、このパターンを実装できます。
この方法を使用すると、ルックアップをキャッシュすることでバックエンド・サーバーやJNDIツリーの障害をサービス・ロケータが隠ぺいできるため、可用性が向上します。ただし、結果はやはりフェッチする必要があるため、これは一時的なものでしかありません。
また、バックエンド・アプリケーション・サーバーに対するトリップが減るため、パフォーマンスも向上します。
次のいずれかの機能が必要な場合にのみ、EJBモジュールをクラスタ化します。
ロード・バランシング: EJBクラスタ内のサーバー間でEJBクライアントをロード・バランシングします。
フォルト・トレランス: 状態(ステートフル・セッションBeanの場合)を、EJBクラスタ内のOC4Jプロセス間でレプリケートします。クライアントのプロキシ・クラスがEJBサーバーに接続できない場合には、クラスタ内の次のサーバーに接続を試みます。クライアントには障害は認識されません。
スケーラビリティ: 1つのサーバーとして動作する複数のEJBサーバーは、単一のEJBサーバーより多くのリクエストを処理できるため、クラスタ化されたEJBシステムの方がスケーラビリティが優れています。別の方法としては、スタンドアロンのEJBシステムを使用し、サーバー間でクライアントを手動でパーティション化します。この方法は構成が難しく、フォルト・トレランスの利点もありません。
EJBのクラスタ化を最大限に活用するには、リモートEJBモジュールを使用する必要があります。パフォーマンスに対する影響は、ローカルEJBモジュールよりもリモートEJBモジュールの方が大きくなります。ローカルEJBモジュールを使用し、それに対する参照をサーブレット(またはJSP)のセッションに保存した場合は、セッションがレプリケートされると、保存されている参照が無効になります。したがって、示されている機能が必要な場合にのみEJBのクラスタを使用します。
findByPrimaryKey()
およびfindAll()
以外のfinderメソッドを作成する場合、コンテナによって生成されるSQLコードの実行の最適化に役立つ適切なインデックスを作成しないと、非常に効率が悪くなる場合があります。
開発者は、EJBモジュールのライフ・サイクルを理解することが不可欠です。ライフ・サイクルおよびコール・バックの間に予想されるアクションにより密接に従うことで、多くの問題を避けることができます。
これは、エンティティBeanとステートフル・セッションBeanには特に当てはまります。たとえば、小さいテスト環境ではBeanが受動化されないことがあり、ejbPassivate()
およびejbActivate()
の実装ミス(または実装漏れ)が後になるまで見つからない場合があります。さらに、これらはステートレスBeanに対しては使用されないため、経験の浅い開発者は混乱する場合があります。
トランザクション中の短い期間では無効であっても、トランザクションの境界では有効である制約に対しては、遅延データベース制約を使用します。たとえば、ある列がejbCreate()
の間には移入されず、トランザクションが完了するまでには設定される場合は、その列に対するNOT NULL制約を遅延として設定できます。これは、EJB 2.0のEJBリレーションシップによってミラー化される外部キー制約に対しても適用されます。
データの変化が非常にゆるやかな場合、またはデータがまったく変化しない場合で、変更がEJBアプリケーションによって行われる場合には、読取り専用Beanが非常に優れたキャッシュになります。そのよい例が国のEJBモジュールです。このようなEJBモジュールは、頻繁に変更されることはなく、ある程度古いデータでも許容されます。
読取り専用EJBモジュールでキャッシュを作成する手順:
読取り専用のエンティティBeanを作成します。
exclusive-write-access="true"
と設定します。
有効性タイムアウトをデータに対して許可される最大限の期間に設定します。
EJBアプリケーションを適切に実行し、信頼性を高くするには、適切なロック計画と適切なデータベース分離モードを組み合せることが重要です。
更新が競合する可能性が低い場合は、コミット時ロックを使用します。更新が失われてもかまわない場合、またはアプリケーションの設計により更新が失われることがない場合は、コミット読取りの分離モードを使用します。更新が失われると問題である場合は、シリアライズ可能な分離モードを使用します。
更新が競合する可能性が高い場合は、即時ロックを使用します。この場合にパフォーマンスを最高にするには、コミット読取りの分離モードを使用します。EJBアプリケーションがデータを変更しない場合は、読取り専用のロックを使用します。
EJBモジュールは業界で広く採用されているため、共通して(そして一般的に)認められた問題解決方法がいくつかあります。このような方法は、書籍やディスカッション・フォーラムなどで広く公開されています。ある意味で、これらのパターンは特定の問題に対するベスト・プラクティスです。パターンを調べて利用する必要があります。
次にいくつかの例を示します。
セッション・ファサード: エンティティBeanの複数の呼出しをセッションBeanの単一の呼出しに結合し、ネットワーク・トラフィックを減らします。
値オブジェクト・パターン: 通常はまとめる必要がある複数のデータ値を結合して単一の値オブジェクトにすることで、ネットワーク・トラフィックを減らします。
このドキュメントでは、利用可能な多数のパターンについて詳細には解説しません。しかし、参照項目の項で、このテーマに関して役に立つ書籍やWebサイトを示しています。
コンテナ管理の永続性(CMP)にはいくつかの制約がありますが、多くの利点も備えています。そのような利点の1つは移植性です。CMPでは、永続性のマッピングやロック・モデルの選択のような決定が、コーディングの作業ではなくデプロイの作業になります。これにより、コードを変更せずに、同じアプリケーションを複数のコンテナにデプロイできます。Bean管理の永続性(BMP)の場合は、SQL文と並行性制御をエンティティBeanに記述する必要があり、コンテナやデータ・ストアに対して固有になるため、このような移植性は一般にありません。
もう1つの利点は、一般に、J2EEコンテナのベンダーが提供するロック・モデルのバリエーション、遅延ロードおよびパフォーマンスやスケーラビリティの拡張といったサービスのクオリティ(QoS)機能を、コードの記述ではなく、デプロイの構成で制御できる点です。Oracle Application Serverには、読取り専用エンティティBean、変更の最小限の記述、リレーションの遅延ロードなどの機能が含まれますが、これらはBMPに対するコードに組み込む必要があります。
CMPの3番目の利点は、コンテナ管理の関連です。CMPフィールドのマッピングと同様に、CMPエンティティBeanは、宣言を介してコンテナにより管理される2つのエンティティBean間の関連を保持することができます。アプリケーション開発者が実装コードを作成する必要はありません。
最後の利点は、最も小さいものですが、CMPエンティティBeanの作成を支援するツールを利用できるために、開発者が最小限の作業で永続性を実現できる点です。その結果、開発者はビジネス・ロジックに集中することができ、作業効率が向上します。このようなツールのよい例がJDeveloper9iであり、提供されているモデリング・ツールとウィザードを使用すると、ごくわずかな作業で汎用EJBディスクリプタとOracle Application Server固有のディスクリプタの作成などのCMPエンティティBeanを作成することができます。
CMPではアプリケーションの要件を満たせない場合もありますが、開発作業の減少やOC4JのようなJ2EEコンテナが提供する最適化によって、全体的にCMPにはBMPよりも魅力があります。
コンテナ管理の関連はローカル・インタフェースでのみ使用できるため、ローカル・インタフェースのみを使用してエンティティBeanを公開するのがよい方法です。また、ローカル・インタフェースを使用すると、多くのリソースが必要になるシリアライズやリモート・ネットワーク呼出しが必要ありません。
Webモジュールやクライアント・アプリケーションからエンティティBeanを直接使用しないようにし、かわりにセッションBeanファサード・レイヤーを使用します。クライアント・アプリケーションからエンティティBeanを使用すると、ドメイン・モデルがクライアントにハードコーディングされます。また、エンティティBeanに対するリモート・インタフェースとローカル・インタフェースの管理が困難になります。
すべての自然なユースケースをグループ化して、セッションBeanファサード・レイヤーを作成します。このレイヤーは、1つ以上のエンティティBeanに対する操作を公開します。エンティティBeanに対するきめ細かいアクセスを提供し、トランザクション境界として動作することでデータベースとの相互作用を減らします。また、Webサービスのエンドポイントとしてステートレス・セッションBeanを公開することで、WebサービスからエンティティBeanにアクセスできるようにします。
コンテナで余分なSQL文を実行して主キーの重複をチェックするのではなく、CMPエンティティBeanの基になっている表に対する主キー制約を強制します。orion-ejb-jar.xml
ファイルでエンティティBeanに対してdo-select-before-insert="false"
を設定することで、このチェックを切り替えることができます。
エンティティBean間の1対1または1対多のリレーションシップに対するO-Rマッピングを完成する場合は、関連表を使用するのではなく、外部キーを使用します。これにより、余分な表およびコンテナが生成する余分なSQL文を維持して、リレーションシップを維持する必要がなくなります。
findAll()
メソッドを使用すると、コンテナは表のすべての行の取得を試みます。多数のレコードを含む表が基になっているエンティティに対しては、この種の操作を実行しないようにする必要があります。データベースの操作が遅くなります。
Oracle JDBCドライバの拡張機能を使用すると、問合せ中に結果セットを移入している間に、クライアントにプリフェッチする行の数を設定できます。これを使用して、サーバーに対するラウンドトリップの回数を減らします。これにより、多数の行を返すfinderメソッドのパフォーマンスを大幅に向上させることができます。orion-ejb-jar.xml
ファイルでfinderメソッドに対するprefetch-size
属性を指定できます。
デフォルトでは無効になっている遅延ロードを有効にすると、finderで取得されたオブジェクトの主キーのみが返されます。したがって、実装内でオブジェクトにアクセスすると、OC4Jコンテナは主キーに基づいて実際のオブジェクトをアップロードします。
取得しているオブジェクトの数が多く、そのすべてをローカル・キャッシュにロードするとパフォーマンスが低下する場合には、遅延ロード機能を有効にしてもかまいません。複数のオブジェクトを取得し、その一部のみを使用する場合は、遅延ロードを有効にする必要があります。さらに、getPrimaryKey
メソッドを通してのみオブジェクトを使用する場合も、遅延ロードを有効にする必要があります。
orion-ejb-jar.xml
ファイルでCMPエンティティBeanに対してO-Rマッピングを行うのは、非常に複雑でエラーが発生しやすくなります。マッピングのエラーはデプロイ・エラーの原因になる場合があり、EJB-SQL文に対して誤ったSQLコードが生成される可能性があります。次の2つの方法をお薦めします。
JDeveloperを使用してO-Rマッピングを実行し、orion-ejb-jar.xml
ファイルにマッピング情報を生成します。
OC4Jにアプリケーションをデプロイしてマッピングを生成した後、orion-ejb-jar.xml
ファイルを修正して、正しい表名と永続名が含まれるようにします。