ヘッダーをスキップ
Oracle Call Interfaceプログラマーズ・ガイド
11g リリース1(11.1)
E05677-02
  目次
目次
索引
索引

戻る
戻る
 
次へ
次へ
 

9 OCIプログラミングの高度なトピック

この章は、次の項目で構成されています。

OCIでの接続プーリング

接続プーリングとは、ロードを均衡化するために、再使用可能な物理接続のグループ(プール)を複数のセッションで使用することです。プールの管理は、アプリケーションではなくOCIで行います。Webアプリケーション・サーバーおよび電子メール・サーバーの中間層アプリケーションでも接続プーリングを使用できます。

この機能の使用例は、バックエンドのOracleデータベースに接続しているWebアプリケーション・サーバーにあります。Webアプリケーション・サーバーで、データベース・サーバーから同時に複数のデータ要求を受けたとします。アプリケーションでは、アプリケーションの初期化を行うとき、各環境にプール(またはプール・セット)を作成できます。

OCI接続プーリングの概念

Oracleは、データベース・セッションと接続のファイングレイン管理など、いくつかのトランザクション・モニター機能を備えています。この機能は、接続(サーバー・ハンドル)からデータベース・セッション(ユーザー・ハンドル)の概念を分離することで実行されます。セッション切替えやセッション移行でOCIコールを使用すると、アプリケーション・サーバーまたはトランザクション・モニターでは、少数の物理接続で複数のセッションを多重化できます。したがって、接続およびバックエンドのOracleサーバー・プロセスをプールすることで、高度な拡張性が実現します。

接続プール自体は通常、物理接続の共有プールで構成され、同じ数の専用サーバー・プロセスを含むバックエンド・サーバー・プールに変換されます。

物理接続の数は、アプリケーションで使用するデータベース・セッションの数より少なくなります。物理接続とバックエンド・サーバー・プロセスの数も、接続プーリングを使用することで少なくなります。これによって、多重化できるデータベース・セッションの数が多くなります。

共有サーバーとの類似点と相違点

中間層での接続プーリングは、共有サーバーがバックエンドで提供する機能に似ています。セッション多重化ロジックを中間層で管理することで、接続プーリングは、専用サーバー・インスタンスを共有サーバー・インスタンスと同じように動作させます。

専用サーバー・プロセスへの着信接続要求が含まれた専用サーバー・プロセスのプーリングは、中間層の接続プールによって制御されます。接続プーリングと共有サーバーの主な相違点は、共有サーバーの場合、クライアントからの接続は、通常データベース・インスタンスのディスパッチャへの接続である点です。ディスパッチャには、クライアント要求を適切な共有サーバーに送信します。一方、接続プールからの物理接続は、中間層からバックエンドのサーバー・プールにある専用サーバー・プロセスに直接接続されます。

接続プーリングは、中間層自体がマルチスレッドである場合にのみ有効です。各スレッドでは、データベースに対するセッションをメンテナンスできます。データベースへの実際の接続は接続プールでメンテナンスされ、その接続(専用データベース・サーバー・プロセスのプールを含む)は中間層のすべてのスレッドの間で共有されます。

ステートレス・セッションとステートフル・セッション

ステートレス・セッションは、中間層スレッド間で逐次再利用可能です。3層ユーザーのかわりにスレッドでデータベース要求を処理した後で、異なる3層ユーザーのかわりに異なる要求を目的として、同じデータベース・セッションを再利用できます。

一方、ステートフル・セッションはデータベースに対して、3層スレッド間で逐次再利用はできません。これは、ステートフル・セッションが、特定の3層ユーザーに関連する特定の状態になっている場合があるためです。このような状態の例としては、オープン・トランザクション、文からのフェッチ状態、PL/SQLパッケージ状態などがあります。このような特定の状態が続く期間は、様々な要求に対してセッションを再利用できません。

注意: ステートレス・セッションも、オープン・トランザクション、オープン文のフェッチ状態などになる可能性があります。ただし、このような状態が継続するのは比較的短期間(3層スレッドによって特定の3層要求を処理する期間のみ)であるため、(これらの状態がクリーン・アップされると)セッションを様々な3層ユーザーに対して逐次再利用可能です。

注意: ステートレス・セッションは通常、文キャッシュとともに使用されます。

接続プーリングによって、ステートレス接続とステートフル・セッションが提供されます。ステートレス・セッションを操作する必要がある場合は、「OCIでのセッション・プーリング」を参照してください。

複数の接続プール

複数の接続プールの拡張概念は、異なるデータベース接続に適用できます。複数の接続プールは、異なる優先度がユーザーに割り当てられている場合にも使用できます。接続プーリングを使用すると、異なるレベルのサービス保証を実装できます。

次の図はOCI接続プーリングを示しています。

図9-1 OCI接続プーリング

図9-1の説明は次にあります
「図9-1 OCI接続プーリング」の説明

透過的アプリケーション・フェイルオーバー

接続プーリングでは、透過的アプリケーション・フェイルオーバー(TAF)が使用可能になっています。BACKUPおよびPRECONNECT句が接続文字列では使用できず接続プーリングとTAFでは機能しない点を除いて、TAFの概念は、接続プール内の各接続に等しく適用されます。

接続プールの接続がフェイルオーバーする場合、主接続文字列自体を使用して接続します。セッションは、そのインスタンスに障害が発生した後に、プールを使用してデータベースとのラウンドトリップを行うときにフェイルオーバーします。リスナーは、存在する正常なインスタンスにルーティングするよう構成されます。これは、サービス・ベースの接続文字列に関してよく行われます。


関連項目:


『Oracle Database Net Services管理者ガイド』の「透過的アプリケーション・フェイルオーバーの構成」

接続プーリングのOCIコール

アプリケーションで接続プーリングを使用する手順は、次のとおりです。

プール・ハンドルの割当て

接続プーリングでは、OCIHandleAlloc()によってプール・ハンドルOCI_HTYPE_CPOOLを割り当てる必要があります。指定された環境ハンドルに対して複数のプールを作成できます。

単一の接続プーリングの割当て例を次に示します。

OCICPool *poolhp;
OCIHandleAlloc((void *) envhp, (void **) &poolhp, OCI_HTYPE_CPOOL,
                      (size_t) 0, (void **) 0));

接続プールの作成

OCIConnectionPoolCreate()関数は、接続プール・ハンドルを初期化します。次のINパラメータがあります。

  • connMin− プールの作成時にオープンする最小接続数です。

  • connIncr− すべての接続がビジーのときにコールで接続が必要な場合に、オープンする接続の増分数です。この増分は、オープンしている接続の合計数がそのプールでオープン可能な最大接続数より少ない場合にのみ使用します。

  • connMax− プールでオープンできる最大接続数です。プールで最大数の接続がオープンするとすべての接続がビジーになるため、コールに接続が必要な場合は、取得するまで待機することになります。ただし、プールにOCI_ATTR_CONN_NOWAIT属性が設定されている場合は、エラーが戻されます。

  • poolUsernameおよびpoolPasswd− ユーザー・セッションがプールの接続間で透過的に移行できます。

  • さらに、OCI_ATTR_CONN_TIMEOUT属性によって、プールの接続のタイムアウトを設定できます。この時間を超えてアイドル状態の接続は定期的に終了し、オープン接続が最適な数に維持されます。この属性が設定されていない場合は、接続がタイムアウトになることはありません。


注意:


プールの縮小は、ネットワーク・ラウンドトリップがある場合にのみ発生します。操作がない場合、接続は継続されます。

前述の属性は、すべて動的に構成できます。したがって、アプリケーションは、現行ロード(オープン接続数とビジーの接続数)の読取りおよび属性の適切なチューニングを柔軟に行うことができます。

プール属性(connMaxconnMinconnIncr)を動的に変更する場合は、modeパラメータにOCI_CPOOL_REINITIALIZEを設定したOCIConnectionPoolCreate()をコールする必要があります。

OUTパラメータpoolNamepoolNameLenには、データベース名とその長さの引数のかわりに、後続のOCIServerAttach()OCILogon2()コールで使用する値が格納されます。

アプリケーションで作成できるプールの数に制限はありません。中間層アプリケーションでは、この機能によって複数のプールを作成して同一サーバーまたは異なるサーバーに接続し、アプリケーション固有のニーズにあわせてロードを均衡化できます。

このコールのコード例を次に示します。

OCIConnectionPoolCreate((OCIEnv *)envhp,
                   (OCIError *)errhp, (OCICPool *)poolhp,
                   &poolName, &poolNameLen,
                   (text *)database,strlen(database),
                   (ub4) conMin, (ub4) conMax, (ub4) conIncr,
                   (text *)pooluser,strlen(pooluser),
                   (text *)poolpasswd,strlen(poolpasswd),
                   OCI_DEFAULT));

データベースへのログイン

アプリケーションでは、次のいずれかのインタフェースを使用して、スレッドごとにデータベースにログインする必要があります。

  • OCILogon2()

    最も単純なインタフェースです。このインタフェースは、接続プールを使用して単純な接続を行う場合に使用します。この場合、セッション・ハンドルの属性を変更する必要はありません。このインタフェースは、データベースへのプロキシ接続を行う場合にも使用できます。

    次に、OCILogon2()の使用例を示します。

    for (i = 0; i < MAXTHREADS; ++i)
    {
       OCILogon2(envhp, errhp, &svchp[i], "hr", 2, "hr", 2, poolName,
                 poolNameLen, OCI_LOGON2_CPOOL));
    
    }
    

    このインタフェースを使用してプロキシ接続を行うには、パスワード・パラメータをNULLに設定してください。

  • OCISessionGet()

    このインタフェースの使用をお薦めします。このインタフェースを使用すると、ユーザーは、証明書、識別名などの外部認証方式も使用できます。OCISessionGet()は、セッションを取得する場合の推奨する統一ファンクション・コールです。

    次に、OCISessionGet()の使用例を示します。

    for (i = 0; i < MAXTHREADS; ++i)
    {
            OCISessionGet(envhp, errhp, &svchp, authp,
                          (OraText *) poolName,
                          strlen(poolName), NULL, 0, NULL, NULL, NULL,
                          OCI_SESSGET_CPOOL)
     }
    
  • OCIServerAttach()およびOCISessionBegin()

    アプリケーションで、ユーザー・セッション・ハンドルおよびサーバー・ハンドルに特別な属性を設定する必要がある場合は、別のインタフェースを使用できます。このような要件に対して、アプリケーションでは、すべてのハンドル(接続プール・ハンドル、サーバー・ハンドル、セッション・ハンドルおよびサービス・コンテキスト・ハンドル)を割り当てる必要があります。

    • 接続プールを作成します。

    • モードをOCI_CPOOLに設定してOCIServerAttach()をコールします。

    • モードをOCI_DEFAULTに設定してOCISessionBegin()をコールします。

仮想サーバー・ハンドルに接続プールを指定する(modeOCI_CPOOLに設定してOCIServerAttach()をコールする)場合は、OCISessionBegin()へのコールでOCI_MIGRATEフラグを設定しないでください。オラクル社では、互換性上の理由によってのみ、このフラグOCI_MIGRATEを渡すことをお薦めします。接続プールを使用するときに、ユーザーには、セッション用に独自の専用(仮想)接続が確立されているように感じられますが、この接続は実際の接続に対して透過的に多重化されているだけのため、OCI_MIGRATEフラグは使用しないでください。資格証明は、OCI_CRED_RDBMSまたはOCI_CRED_PROXYに設定できます。資格証明をOCI_CRED_PROXYに設定すると、セッション・ハンドルの設定ではユーザー名のみ必要です(1次セッションを明示的に作成したり、OCI_ATTR_MIGSESSIONを設定する必要はありません)。

接続プーリングでは、物理接続から仮想サーバー・ハンドルが透過的に多重化されるため、ユーザーがこの作業を行う必要はありません。ユーザーには、セッション用に専用(仮想)接続が確立されているように感じられます。多重化はユーザーに対して透過的に行われるため、仮想サーバー・ハンドル自体に対してセッションを多重化しないでください。ユーザー・レベルで明示的な多重化を必要とするセッション移行およびセッション切替えの概念は、接続プーリングに関しては廃止されたため、使用しないでください。

OCIプログラムでは、ユーザーは、接続プールを使用して作成されるセッションごとに一意の仮想サーバー・ハンドルを作成(モードをOCI_CPOOLに設定してOCIServerAttach()をコール)する必要があります。仮想サーバー・ハンドルとセッションは、1対1対応である必要があります。

接続プーリングでのSGA制限の処理

OCI_CPOOLモード(接続プーリング)では、バックエンド・データベースのセッション・メモリー(UGA)はSGAから取得されます。SGAで格納できるセッション・メモリー以上のメモリー量をアプリケーションで使用する場合は、バックエンド・データベースでSGAをチューニングしてSGAを拡張する必要性が生じる場合があります。バックエンド・データベースに対するメモリー・チューニングの要求は、インスタンスが専用モードである点を除けば、共有サーバーがバックエンドの場合にLARGE POOLを構成する動作に似ています。


関連項目:


詳細は、『Oracle Databaseパフォーマンス・チューニング・ガイド』で共有サーバーの構成の項を参照してください。

さらにSGA制限が発生する場合は、次の点を考慮してください。

  • セッションごとにオープンする文を少なくして、セッション・メモリーの使用量を減らします。

  • 中間層にセッションをプールするなどの方法で、バックエンドのセッション数を減らします。

  • 接続プーリングをオフにします。

アプリケーションでは、バックエンドで専用データベース・リンクを接続プーリングとともに使用しないでください。

バックエンドが専用サーバーの場合、効率的な接続プーリングは実現しません。これは、専用データベース・リンクを使用するセッションは物理接続に連結され、同じ接続が他のセッションでは使用できなくなるためです。アプリケーションで専用データベース・リンクを使用し、セッション間でバックエンド処理を効率的に共有できない場合は、共有データベース・リンクの使用を検討してください。


関連項目:


分散データベースの詳細は、『Oracle Database管理者ガイド』で共有データベース・リンクの項を参照してください。

データベースからのログオフ

接続プーリング・モードでデータベースからログオフするために、ログイン・コールに対応する次のインタフェースがあります。

  • OCILogoff()

    OCILogon2()を使用して接続を行った場合は、OCILogoff()を使用してログオフする必要があります。

  • OCISessionRelease()

    OCISessionGet()をコールして接続を行った場合は、OCISessionRelease()をコールしてログオフする必要があります。

  • OCISessionEnd()およびOCIServerDetach()

    OCIServerAttach()およびOCISessionBegin()をコールして接続を行いセッションを開始した場合は、OCISessionEnd()をコールしてセッションを終了し、OCIServerDetach()をコールして接続を解放する必要があります。

接続プールの破棄

OCIConnectionPoolDestroy()を使用して、接続プールを破棄します。

プール・ハンドルの解放

プール・ハンドルは、OCIHandleFree()を使用して解放します。

次のコード・フラグメントは、前述した3つの処理を示しています。

 for (i = 0; i < MAXTHREADS; ++i)
  {
    checkerr(errhp, OCILogoff((void *) svchp[i], errhp));
  }
  checkerr(errhp, OCIConnectionPoolDestroy(poolhp, errhp, OCI_DEFAULT));
  checkerr(errhp, OCIHandleFree((void *)poolhp, OCI_HTYPE_CPOOL));

OCI接続プーリングの例

テスト済の完了プログラムの接続プーリングの例は、ディレクトリdemo内のcdemocp.cおよびcdemocpproxy.cを参照してください。

OCIでのセッション・プーリング

セッション・プーリングとは、アプリケーションがデータベースに対するステートレス・セッションのグループを作成してメンテナンスすることを意味します。セッションは、要求に応じてThinクライアントに渡されます。使用可能なセッションがない場合は、新しいセッションが作成されます。セッションでのクライアントの処理が完了すると、クライアントはプールに対してセッションを解放します。したがって、プール内のセッション数は動的に増加する場合があります。

プール内の一部のセッションに、特定のプロパティを使用してタグを付けることができます。たとえば、ユーザーは、デフォルト・セッションを要求して特定の属性を設定し、そのセッションにラベルを付けて(タグを付けて)プールに戻すことができます。そのユーザーまたは他のユーザーは、同じ属性を持つセッションを要求できるため、同じタグが付いたセッションを要求できます。プール内に、同じタグが付いた複数のセッションが存在する場合があります。セッションのタグは、変更またはリセットすることができます。

このインタフェースを使用して、プロキシ・セッションを作成してメンテナンスすることもできます。

使用可能なセッションがなく、プールの最大サイズに達した場合のアプリケーションの動作は、指定した属性によって決まります。新しいセッションが作成されるか、エラーが戻されるか、またはスレッドがブロックしてセッションが使用可能になるまで待機する場合があります。

このタイプのプーリングの主な利点は、パフォーマンスにあります。データベースへの接続(特にデータベースがリモートの場合)は、時間がかかるアクティビティです。したがって、クライアントでは、時間をかけてサーバーに接続し、資格証明を認証して有効なセッションを受け取るかわりに、プールからセッションを取り出すことができます。

OCIセッション・プーリングの機能

セッション・プーリングの機能は、次のとおりです。

  • ステートレス・セッションのプールを透過的に作成、メンテナンスおよび管理します。

  • アプリケーションでプールを作成し、プール内のセッションの最小数、増分数および最大数を指定するインタフェースを提供します。

  • ユーザーがデフォルト・セッションまたはタグ付けされたセッションをプールとの間で取得および解放するためのインタフェースを提供します。タグ付けされたセッションとは、クライアント定義の特定のプロパティが指定されたセッションです。

  • アプリケーションで、セッションの最小数と最大数を動的に変更できます。

  • 長時間アイドル状態のセッションをクローズし、必要なときにセッションを作成することによって、常に最適な数のセッションがオープンしているようにメンテナンスするメカニズムを提供します。

  • セッション・プーリングで認証を行うことができます。

同種セッション・プールおよび異種セッション・プール

セッション・プールは、同種または異種のいずれかになります。同種セッション・プーリングとは、プール内のセッションが同じ認証方法(同じユーザー名、パスワードおよび権限)を使用することを意味します。異種セッション・プーリングとは、セキュリティ属性と権限がセッションごとに異なる場合があるため、認証情報を指定する必要があることを意味します。

セッション・プーリングでのタグの使用

ユーザーは、タグを使用して、プール内のセッションをカスタマイズできます。クライアントは、デフォルト・セッションまたはタグが付いていないセッションをプールから取得して、特定の属性(NLS設定など)をセッションに設定し、OCISessionRelease()コールでそのセッションをプールに戻すと、セッションに適切なラベル(タグ)を付けることができます。

そのユーザーまたは他のユーザーは、同じ属性が指定されたセッションを使用するために、同じタグが付いたセッションを要求できます。これを行うには、OCISessionGet()コールで同じタグを指定します。


関連項目:


セッションのタグ付けの詳細は、「OCISessionGet()」を参照してください。

セッション・プーリング用のOCIハンドル

セッション・プーリング用に、次の2タイプのハンドルが追加されています。

OCISPool

セッション・プール・ハンドルです。このハンドルは、OCIHandleAlloc()を使用して割り当てます。このハンドルは、OCISessionPoolCreate()およびOCISessionPoolDestroy()に渡す必要があります。属性の型はOCI_HTYPE_SPOOLです。

OCIHandleAlloc()コールの例を次に示します。

OCISPool *spoolhp;
OCIHandleAlloc((void *) envhp, (void **) &spoolhp, OCI_HTYPE_SPOOL,
                        (size_t) 0, (void **) 0));

環境ハンドルに対して、複数のセッション・プールを作成できます。

OCIAuthInfo

認証情報ハンドルです。このハンドルは、OCIHandleAlloc()を使用して割り当てます。このハンドルは、OCISessionGet()に渡されます。このハンドルでは、ユーザー・セッション・ハンドルでサポートされるすべての属性をサポートします。詳細は、「ユーザー・セッション・ハンドル属性」を参照してください。認証情報ハンドルの属性の型はOCI_HTYPE_AUTHINFOです。

OCIHandleAlloc()コールの例を次に示します。

OCIAuthInfo *authp;
OCIHandleAlloc((void *) envhp, (void **) &authp, OCI_HTYPE_AUTHINFO,
                      (size_t) 0, (void **) 0));

関連項目:


OCIセッション・プーリングの使用

ユーザー名とパスワードを使用する単純なセッション・プーリングのアプリケーションは、次のステップで記述します。

  • OCISPoolハンドルに対してOCIHandleAlloc()を使用して、セッション・プール・ハンドルを割り当てます。環境ハンドルに対して複数のセッション・プールを作成できます。

  • modeOCI_DEFAULT(新規セッション・プールの場合)に設定したOCISessionPoolCreate()を使用して、セッション・プールを作成します。その他のモードについては、関数の説明を参照してください。

  • スレッドごとにループを行います。次の処理を実行する関数を使用してスレッドを作成します。

  • OCIHandleAlloc()を使用して、OCIAuthInfo型の認証情報ハンドルを割り当てます。

  • OCIAttrSet()を使用して、認証情報ハンドルにユーザー名とパスワードを設定します。

  • modeOCI_SESSGET_SPOOLに設定したOCISessionGet()を使用して、プールされたセッションを取得します。

  • トランザクションを実行します。

  • トランザクションをコミットまたはロールバックします。

  • OCISessionRelease()を使用して、セッションを解放(ログオフ)します。

  • OCIHandleFree()を使用して、認証情報ハンドルを解放します。

  • 各スレッドのループを終了します。

  • OCISessionPoolDestroy()を使用して、セッション・プールを破棄します。

セッション・プーリングのOCIコール

ここでは、セッション・プーリングのOCIコールの使用方法について説明します。

プール・ハンドルの割当て

セッション・プーリングでは、OCIHandleAlloc()をコールしてプール・ハンドルOCI_HTYPE_SPOOLを割り当てる必要があります。

指定された環境ハンドルに対して複数のプールを作成できます。単一のセッション・プーリングの割当て例を次に示します。

OCISPool *poolhp;
OCIHandleAlloc((void *) envhp, (void **) &poolhp, OCI_HTYPE_SPOOL, (size_t) 0,
               (void **) 0));

プール・セッションの作成

OCISessionPoolCreate()関数を使用して、セッション・プールを作成できます。このコールの使用例を次に示します。

OCISessionPoolCreate(envhp, errhp, poolhp, (OraText **)&poolName,
              (ub4 *)&poolNameLen, database,
              (ub4)strlen((const signed char *)database),
              sessMin, sessMax, sessIncr,
              (OraText *)appusername,
              (ub4)strlen((const signed char *)appusername),
              (OraText *)apppassword,
              (ub4)strlen((const signed char *)apppassword),
              OCI_DEFAULT);

データベースへのログイン

セッション・プーリング・モードでデータベースにログインするために、次のインタフェースが使用できます。

  • OCILogon2()

    最も単純なインタフェースです。ただし、ユーザーは、セッションのタグ付けを行うことができません。セッション・プーリング・モードでOCILogon2()を使用してデータベースにログインするコード例を、次に示します。

    for (i = 0; i < MAXTHREADS; ++i)
    {
      OCILogon2(envhp, errhp, &svchp[i], "hr", 2, "hr", 2, poolName,
                poolNameLen, OCI_LOGON2_SPOOL));
    
    }
    
  • OCISessionGet()

    このインタフェースの使用をお薦めします。このインタフェースを使用すると、ユーザーは、プール内のセッションにラベル付け(タグ付け)ができるため、特定のセッションを容易に取得できます。次に、OCISessionGet()の使用例を示します。これは、demoディレクトリのcdemosp.cから取り出しました。

    OCISessionGet(envhp, errhp, &svchp, authInfop,
                 (OraText *)database,strlen(database), tag,
                 strlen(tag), &retTag, &retTagLen, &found,
                 OCI_SESSGET_SPOOL);
    

データベースからのログオフ

セッション・プーリング・モードでデータベースからログオフするために、前述のログイン・コールに対応する次のインタフェースがあります。

  • OCILogoff()

    OCILogon2()を使用して接続を行った場合は、OCILogoff()をコールしてログオフする必要があります。

  • OCISessionRelease()

    OCISessionGet()をコールして接続を行った場合は、OCISessionRelease()をコールしてログオフする必要があります。 保留状態のトランザクションは自動的にコミットされます。

セッション・プールの破棄

セッション・プールを破棄するには、OCISessionPoolDestroy()をコールする必要があります。この関数のコール例を次に示します。

OCISessionPoolDestroy(poolhp, errhp, OCI_DEFAULT);

プール・ハンドルの解放

セッション・プール・ハンドルを解放するには、OCIHandleFree()をコールする必要があります。この関数のコール例を次に示します。

OCIHandleFree((void *)poolhp, OCI_HTYPE_SPOOL);

注意:


開発者は、接続をプールに解放する前にオープン・トランザクションをコミットまたはロールバックしてください。 そうしない場合、接続がプールに解放されるときに、Oracleでは自動的にオープン・トランザクションがコミットされます。

OCIセッション・プーリングの例

テスト済の完全なプログラムのセッション・プーリングの例を示します。


関連項目:


demoディレクトリのcdemosp.c

実行時接続ロード・バランシング

Oracle Real Application Clusters(RAC)は、単一データベースを複数ノードにある複数インスタンスで管理するデータベース・オプションです。RACの共有ディスクによるデータベース・クラスタ化方法では、現在のニーズを満たすためにノードを容易に追加または解放できるため拡張性が高まり、1つのノードに障害が発生しても別のノードがワークロードを肩代わりできるため可用性が向上します。また、すべてのインスタンスがデータベース全体にアクセスできるため、データベースに高可用性機能とフェイルオーバー機能が追加されます。

作業リクエストのバランシングは、接続時に1回と実行時に1回発生します。これを、Oracle Net Servicesで提供される接続時ロード・バランシングおよび実行時接続ロード・バランシングと呼びます。RAC環境の場合、セッション・プールでは高速アプリケーション通知(FAN)イベントによりRACロード・バランシング・アドバイザから受け取ったサービス・メトリックを使用して、アプリケーション・セッション要求を均衡化します。セッション・プールに入る作業リクエストは、現行のサービス・パフォーマンスを使用して、サービスを提供するRACのインスタンス間で分散できます。


関連項目:


接続時ロード・バランシングは、アプリケーションによりセッションが最初に作成される時点で発生します。プールの一部であるセッションも、最初の作成時にRACインスタンス間で分散する必要があります。これにより、各インスタンスでのセッションが作業を実行するチャンスを取得することになります。

実行時接続ロード・バランシングは、基本的には作業リクエストをセッション・プール内で作業の処理に最も適切なセッションにルーティングする操作です。既存のセッション・プールからセッションを選択する際に有効になるため、頻度の高いアクティビティです。単一インスタンスでのみサービスをサポートするセッション・プールの場合は、プール内で使用可能な最初のセッションで十分です。プールが複数インスタンスにまたがるサービスをサポートしている場合は、適切なサービスを提供できる、または容量の大きいインスタンスが多数のリクエストを取得するように、作業リクエストをインスタンス間で分散する必要があります。

リリース10.2以降のサーバーと通信するリリース11.1以降のクライアントでは、実行時接続ロード・バランシングはデフォルトで有効化されます。OCISessionPoolCreate()のコール時にmodeパラメータをOCI_SPC_NO_RLBに設定すると、実行時接続ロード・バランシングが無効化されます。

ロード・バランシング・アドバイザFANイベントの受信

サービス時間に基づいてサービス・メトリックを受信するための要件は、次のとおりです。

  • Oracleクラスタウェアを伴うRAC環境が設定され、有効化されていること。

  • アプリケーションがスレッド・ライブラリとリンクされていること。

  • OCI環境がOCI_EVENTSおよびOCI_THREADEDモードで作成されていること。

  • サービスが変更され、サービスの目的および接続ロード・バランシングの目的が設定されていること。

    EXEC DBMS_SERVICE.MODIFY_SERVICE("myService",
         DBMS_SERVICE.GOAL_SERVICE_TIME,
         clb_goal => dbms_service.clb_goal_short);
    

関連項目:

  • 「OCISessionPoolCreate()」

  • 『Oracle Real Application Clusters管理およびデプロイメント・ガイド』のFANイベントを受信するためのOCIクライアントの有効化に関する項

  • 『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』の「DBMS_SERVICE」


データベース常駐接続プーリング

データベース常駐接続プーリング(DRCP)は、アプリケーションがデータベース接続を取得し、この接続で比較的短い時間動作してから接続を解放する一般的なWebアプリケーションの使用シナリオに対し、データベース・サーバーの接続プールを提供します。DRCPがプールする専用サーバーは、サーバー・フォアグラウンドおよび結合済データベース・セッションと同等であり、以後プール・サーバーと呼びます。DRCPは、中間層プロセスのスレッド間で接続を共有する中間層接続プールを補充します。さらに、DRCPは同一の中間層ホスト上の中間層プロセス間、さらに中間層ホスト間でもデータベース接続の共有を可能にします。これにより、多数のクライアント接続をサポートするために必要となる主要なデータベース・リソースが大幅に削減されるため、データベース層のメモリー・フットプリントが削減され、中間層およびデータベース層の両方の拡張性が向上します。すぐに使用可能なサーバーのプールが提供されていると、クライアント接続の作成および分割にかかるコストを削減できるというメリットもあります。

DRCPは、中間層接続プーリングを実行できないマルチプロセス・シングル・スレッド・アプリケーション・サーバー(PHP/Apacheなど)を含むアーキテクチャに特に関係しています。データベースは、DRCPによる何万という同時接続に合せて拡張できます。

プール・サーバー・モデルは、デフォルトでOracleへの接続に使用される専用モデルに密接に沿っています。このモデルでは、サーバーが短期のトランザクションの実行にのみ必要である場合に、接続ごとに1つのサーバーを専用的に使用するためのオーバーヘッドが不要になります。DRCPでは、接続でプール・サーバーを取得した後、自発的に放棄することにより、他の類似した接続で使用できるようにします。プール・サーバーが接続で取得されると、解放されてプールに戻るまで、原則的にはその接続の専用サーバーに変換されます。データベース常駐接続プールから接続を取得したクライアントは、接続ブローカと呼ばれるOracleバックグラウンドに接続します。接続ブローカはプール機能を実装し、クライアント・プロセスからのインバウンド接続間でプール・サーバーを多重化します。


関連項目:

  • DRCPアーキテクチャの詳細は、『Oracle Database概要』を参照してください。

  • DRCPに対応するデータベース構成の詳細は、『Oracle Database管理者ガイド』を参照してください。


セッション純正値および接続クラス

OCISessionGet()を使用してセッションを取得する際に、2つの新しい設定を指定できます。

セッション純正値

セッション純正値は、アプリケーションで新しいセッションが必要かどうか、またはアプリケーション・ロジックがプール・セッションを再利用するよう設定されているかどうかを指定します。OCISessionGet()は、OCI_SESSGET_PURITY_NEWまたはOCI_SESSGET_PURITY_SELFの純正値設定を取り入れるよう拡張されています。または、OCISessionGet()をコールする前に、OCIAuthInfoハンドルでOCI_ATTR_PURITY_NEWまたはOCI_ATTR_PURITY_SELFを設定できます。これらのメソッドはどちらも同等です。


注意:


プールからセッションを再利用する場合、サーバーのNLS属性がクライアントのNLS属性よりも優先されます。

たとえば、クライアントでNLS_LANGfrench_france.us7asciiに設定されており、プールからドイツ語のセッションが割り当てられている場合、クライアント・セッションはドイツ語となります。

接続クラスを使用して共有を制限し、前述の問題を回避できます。


セッション純正値の設定例

接続プーリング・アプリケーションにはNEWセッションが必要です。

/* OCIAttrSet method */

ub4 purity = OCI_ATTR_PURITY_NEW;
OCIAttrSet (authInfop, OCI_HTYPE_AUTHINFO,  &purity, sizeof (purity),
            OCI_ATTR_PURITY, errhp);
OCISessionGet (envhp, errhp, &svchp, authInfop, poolName, poolNameLen, NULL, 0,
               NULL, NULL, NULL, OCI_SESSGET_SPOOL);
/* poolName is the name returned by OCISessionPoolCreate() */

/*  OCISessionGet mode method */
OCISessionGet (envhp, errhp, &svchp, authInfop, poolName, poolNameLen, NULL, 0,
               NULL, NULL, NULL, OCI_SESSGET_SPOOL | OCI_SESSGET_PURITY_NEW);
/* poolName is the name returned by OCISessionPoolCreate() */

接続クラス

接続クラスは、アプリケーションで必要とされる接続のタイプに対して論理名を定義します。OCISessionPoolのセッションは複数のユーザーが共有することはできません。ユーザーHRに対して最初に作成されたセッションは、ユーザーHRによる後続の要求にのみ割り当てられます。接続クラスの設定により、所定のユーザーのセッションをさらに分離できます。接続クラスの設定では、同一のデータベース・ユーザーとして接続している複数のアプリケーションにおいて、アプリケーションに対応する論理名を使用してセッションを識別できます。その結果OCIでは、特定の接続クラスに属するセッションが接続クラス外で共有されなくなります。

OCIでは、OCIAuthInfoハンドルに、接続クラスの設定に使用できる新規属性OCI_ATTR_CONNECTION_CLASSが提供されています。接続クラスは文字列属性です。OCIでサポートされる接続クラスの最大長は1024バイトです。「*」という文字は特殊文字であり、接続クラス名には使用できません。

接続クラスの設定例

HRMSアプリケーションでは、接続クラスHRMSで識別されたセッションが必要です。

OCISessionPoolCreate (envhp, errhp, spoolhp, &poolName, &poolNameLen, "HRDB",
    strlen("HRDB"), 0, 10, 1, "HR", strlen("HR"), "HR", strlen("HR"),
    OCI_SPC_HOMOGENEOUS);

OCIAttrSet (authInfop, OCI_HTYPE_AUTHINFO, "HRMS", strlen ("HRMS"),
    OCI_ATTR_CONNECTION_CLASS, errhp);
OCISessionGet (envhp, errhp, &svchp, authInfop, poolName, poolNameLen, NULL, 0,
    NULL, NULL, NULL, OCI_SESSGET_SPOOL);

採用アプリケーションでは、接続クラスRECMSで識別されたセッションが必要です。

OCISessionPoolCreate (envhp, errhp, spoolhp, &poolName, &poolNameLen, "HRDB",
    strlen("HRDB"), 0, 10, 1, "HR", strlen("HR"), "HR", strlen("HR"),
    OCI_SPC_HOMOGENEOUS);

OCIAttrSet (authInfop, OCI_HTYPE_AUTHINFO,  "RECMS", strlen("RECMS"),
    OCI_ATTR_CONNECTION_CLASS, errhp);
OCISessionGet (envhp, errhp, &svchp, authInfop, poolName, poolNameLen, NULL, 0,
    NULL, NULL, NULL, OCI_SESSGET_SPOOL);

セッション純正値および接続クラスのデフォルト

表9-1「各種クライアント・シナリオで使用されるデフォルト」では、各種クライアント・シナリオで使用されるデフォルトを示しています。

表9-1 各種クライアント・シナリオで使用されるデフォルト

属性または設定 アプリケーションがセッション・プールからOCISessionGet()を使用 他の接続はOCISessionPoolから取得されない

OCI_ATTR_PURITY

OCI_ATTR_PURITY_SELF

OCI_ATTR_PURITY_NEW

OCI_ATTR_CONNECTION_CLASS

OCISessionPoolのすべての接続についてデフォルトの接続クラスとして使用されるクライアント側の各セッション・プールに対する、OCIで生成されたグローバル一意名

SHARED

セッションの共有

OCISessionPoolにセッションを要求するスレッド間でのセッションの共有

デフォルトのSHARED接続クラスを使用した特定のデータベースの全接続間における共有


データベース常駐接続プールの開始

データベース管理者(DBA)はSYSDBAとしてログオンし、DBMS_CONNECTION_POOL.START_POOLをデフォルト設定で使用して、デフォルト・プールSYS_DEFAULT_CONNECTION_POOLを開始する必要があります。

プールの構成の詳細は、『Oracle Database管理者ガイド』を参照してください。

データベース常駐接続プーリングの有効化

すべてのアプリケーションにおいて、EZ接続文字列に:POOLED、またはTNS接続文字列に(SERVER=POOLED)を指定して、データベース常駐接続プールを活用できます。

DRCPを活用するためのTNS接続文字列の例

BOOKSDB = (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=oraclehost.company.com)
     (PORT=1521))(CONNECT_DATA = (SERVICE_NAME=books.company.com)(SERVER=POOLED)))

DRCPを活用するためのEZ接続文字列の例

oraclehost.company.com:1521/books.company.com:POOLED

OCIアプリケーションにおけるDRCPの拡張性の活用

ここでは、3種類のアプリケーション・シナリオについて検討し、それぞれのシナリオにDRCPがもたらすメリットについて説明します。

  1. OCISessionPool APIを使用せず、接続クラスおよび純正値設定も指定しないアプリケーション、または純正値設定NEWを指定したアプリケーションは、DRCPから新規セッションを取得します。同じトークンにより、アプリケーションが接続を解放してプールに戻した場合、デフォルトによりこのセッションは同じアプリケーションの他のインスタンスと共有されません。ただし、アプリケーションは既存のプール・サーバー・プロセスを再利用できるというメリットが得られます。

  2. OCISessionPool外のOCISessionGet()を使用し、接続クラスを指定し、さらにpurity=SELFを設定したアプリケーションは、DRCPプール・サーバー・プロセスおよびセッションの両方を再利用できます。ただし、OCISessionRelease()の後、OCIは接続ブローカへの接続を終了します。次回OCISessionGet()がコールされると、アプリケーションはブローカに再接続します。再接続すると、DRCPは指定された接続クラスに属するプール・サーバー(およびセッション)を割り当てます。ただし、再接続では接続の確立および再認証のコストが発生します。こうしたアプリケーションはDRCPリソース(プロセスおよびセッション)をより効率的に共有できますが、接続ブローカへの接続をキャッシュするというメリットは得られません。

  3. OCISessionPool APIを使用し、接続クラスを指定し、さらにpurity=SELFを設定したアプリケーションは、プール・サーバー・プロセスおよび関連付けられたセッションの両方を再利用し、接続ブローカへの接続のキャッシュを利用することにより、データベース常駐接続プール機能を十分に活用できます。キャッシュされた接続では、OCISessionGet()で再認証のコストが発生しません。

OCISessionPool APIは、データベース常駐接続プールと相互運用するため拡張されています。OCISessionGet()を使用してOCISessionPoolから取得されるセッションはDRCPから取得され、OCISessionRelease()を使用してOCISessionPoolに解放されるセッションはDRCPにセッションを解放します。また、OCISessionPoolは、パフォーマンス向上のためにキャッシュされた接続ブローカへの接続を透過的に保持します。

タグ付け、文キャッシュおよびTAF(透過的アプリケーション・フェイルオーバー)など、従来のクライアント側のOCISessionPoolにより提供される機能は、DRCPでもサポートされています。

クライアント側のセッション・プーリングAPIの詳細は、次の各項を参照してください。

DRCPの使用に関するベスト・プラクティス

DRCPを最大限に活用できるアプリケーションを設計するステップは、OCISessionPoolを使用するアプリケーションに必要なステップと極めて類似しています。

唯一の追加ステップとして、DRCPとともに動作するようデプロイされた場合、最大限のパフォーマンスを得るために、アプリケーションで明示的な接続クラス設定を指定することをお薦めします。

最大限のパフォーマンスを得て、さらにDRCPリソースの共有機能を強化するため、同じアプリケーションの複数のインスタンスは、同じ接続クラス設定を指定する必要があります。アプリケーションの複数のインスタンスでデータベース・セッションを共有できることを必ず確認してください。

データベース常駐接続プーリング・アプリケーションの例

/* Assume all necessary handles are allocated */

/*   This middle-tier uses a single database user. Create a homogeneous
     client-side SP */
OCISessionPoolCreate (envhp, errhp, spoolhp, &poolName, &poolNameLen, "BOOKSDB",
    strlen("BOOKSDB"), 0, 10, 1, "SCOTT", strlen("SCOTT"), "password",
    strlen("password"), OCI_SPC_HOMOGENEOUS);

while (1)
{
   /* Process a client request */
   WaitForClientRequest();
   /* Application function */

   /* Set the Connection Class on the OCIAuthInfo handle that is passed as
      argument to OCISessionGet*/

   OCIAttrSet (authInfop, OCI_HTYPE_AUTHINFO,  "BOOKSTORE", strlen("BOOKSTORE"),
               OCI_ATTR_CONNECTION_CLASS, errhp);

   /* Purity need not be set as default is OCI_ATTR_PURITY_SELF for OCISessionPool
       connections */

   /* We can get a SCOTT session released by Mid-tier 2 */
   OCISessionGet(envhp, errhp, &svchp, authInfop, poolName, poolNameLen, NULL, 0,
                 NULL, NULL, NULL, OCI_SESSGET_SPOOL);

   /* Database calls using the svchp obtained above  */
   OCIStmtExecute(...)

   /* This releases the pooled server on the database for reuse */
   OCISessionRelease (svchp, errhp, NULL, 0, OCI_DEFAULT);
}

/* Mid-tier is done - exiting */
OCISessionPoolDestroy (spoolhp, errhp, OCI_DEFAULT);

デプロイメント例1

直前に示したコードは、BOOKSTOREアプリケーションを提供する10の中間層ホストにデプロイされています。使用されるデータベースは、専用サーバー・モードでDRCPが使用可能でない11g(以前)のデータベースです。クライアント側には11gライブラリがあります。使用される接続文字列は次のとおりです。

BOOKSDB = (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=oraclehost.company.com)
   (PORT=1521))(CONNECT_DATA = (SERVICE_NAME=books.company.com)))

この場合、アプリケーションはデータベースから専用サーバー接続を取得します。

デプロイメント例2

前述のコードは、BOOKSTOREアプリケーションを提供する10の中間層ホストにデプロイされています。DRCPは11gデータベースで有効化されています。これで、すべての中間層プロセスがDRCPで提供されるプーリング機能を活用できます。DRCPのデータベース・リソース要件は、専用サーバー・モードでの要件より大幅に少なくなります。接続文字列を次のように変更します。

BOOKSDB = (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=oraclehost.company.com)
  (PORT=1521))(CONNECT_DATA = (SERVICE_NAME=books.company.com)(SERVER=POOLED)))

互換性および移行

11gクライアント・ライブラリにリンクされたOCIアプリケーションは、次のそれぞれに対しては変更なしで動作します。

  • 11gデータベース(DRCP無効)

  • 11gより前のデータベース・サーバー

  • 11gデータベース・サーバー(DRCP有効、DRCP接続文字列とともにデプロイされている場合)

前述のとおりにOCISessionPool APIを接続クラスおよび純正値設定とともに使用するようクライアントが適切に変更されていれば、そのクライアントはDRCPにより提供される高度な拡張性を利用できます。

データベース常駐接続プーリングの使用に対する制限

プール・サーバーでは、次を実行または使用できません。

  • データベースの停止

  • データベース常駐接続プールの停止

  • 接続されたユーザーのパスワードの変更

  • 共有データベース・リンクを使用した、異なるインスタンスにあるデータベース常駐接続プールへの接続

  • 暗号化、証明書などのアドバンスト・セキュリティ・オプション(ASO)の使用

  • OCI_MIGRATEオプションを使用して直接、またはOCIConnectionPoolを使用して間接的に、サーバー側の移行可能なセッションを使用

  • 初期クライアント・ロールの使用

  • アプリケーション・コンテキスト属性(OCI_ATTR_APPCTX_NAMEOCI_ATTR_APPCTX_VALUEなど)の使用

接続プーリングまたはセッション・プーリングの使用/不使用の判断

データベース・セッションを中間層スレッドで再利用できず(つまり、ステートフルのとき)、バックエンドのサーバー・プロセスの数によってはデータベースでスケール上の問題が発生する可能性がある場合は、OCI接続プーリングを使用します。

データベース・セッションを中間層スレッドで再利用でき(つまり、ステートレスのとき)、バックエンドのサーバー・プロセスの数によってはデータベースでスケール上の問題が発生する可能性がある場合は、OCIセッション・プーリングを使用します。

データベース・セッションを中間層スレッドで再利用できず(つまり、ステートフルのとき)、バックエンドのサーバー・プロセスの数がデータベースでスケール上の問題が発生するほど大きくない場合は、プーリングのメカニズムを使用する必要はありません。


注意:


プーリング以外のセッション/接続を使用すると、すべての中間層ユーザー要求に対してデータベース・セッション/接続を分割し、再作成することになります。これにより、データベース側でスケール上の重大な問題が発生し、要求を履行するまでの待機時間が非常に長くなる可能性があります。そのため、データベース・セッションがステートフルであるかステートレスであるかに基づいて、プーリング手法の1つを中間層アプリケーションに採用することをお薦めします。

接続プーリングではプール要素は接続であり、セッション・プーリングではプール要素はセッションとなります。

プールと同様に、スレッドがデータベースでの作業を終了し、リソースが解放されるまでは、プーリングされたリソースはある一定期間、アプリケーション・スレッドによってロックされます。使用期間中は、リソースは他のスレッドでは使用できません。したがって、アプリケーション開発者は、比較的短期のタスクで効率的に作用するプーリングの種類に注意する必要があります。たとえば、アプリケーションが長期の実行トランザクションで実行されている場合は、プーリングされたリソースを他のアプリケーションと長期間共有しないため、リソースが激減する可能性があります。このため、プーリングは短期のタスクで使用し、プールのサイズは必要なトランザクションの並列性を維持できる大きさに保つ必要があります。

また、次の点にも注意してください。

  1. OCI接続プール

    1. データベースへの接続はプーリングされます。ユーザーによって、セッションは作成および破棄されます。データベースに対する各コールにより、プールから使用できる適切な接続が取り出されます。

    2. アプリケーションは、データベースに対する少数の物理的接続で複数のセッションを多重化します。ユーザーは、プール構成をチューニングして必要な並列性を達成できます。

    3. アプリケーション・セッションの有効期間は、キャッシュされたプール接続の有効期間とは関係がありません。

  2. OCIセッション・プール

    セッションおよび接続はOCIによってプーリングされます。このアプリケーションはプールからセッションを取得し、セッションをプールに戻して解放します。

セッション作成用の関数

選択肢は次のとおりです。

  1. OCILogon()

    OCIセッションを取得する最も簡単な方法です。この方法の利点は、OCIサービス・コンテキストを簡単に取得できる点です。欠点は、セッション移行、プロキシ認証、接続プールまたはセッション・プールの使用などの、拡張OCI操作を実行できない点です。

  2. OCILogon2()

    セッションを取得するOCILogon()の機能が含まれます。このセッションは、基礎となる新規の接続があるセッション、既存の接続プールから仮想接続によって開始されたセッション、または既存のセッション・プールからのセッションである場合があります。この関数がコールされるときのmodeパラメータ値によって、動作が決まります。

    ユーザーは、OCIによって戻されるサービス・コンテキストの属性(OCI_ATTR_STMTCACHESIZEを除く)を変更できません。


    関連項目:


    「OCILogon2()」

  3. OCISessionBegin()

    プロキシ認証、接続プールまたはセッション・プールからのセッションの取得、外部資格証明および移行可能セッションなどの、OCIセッションの様々なオプションをすべてサポートします。これは、全ハンドルを明示的に割り当て、全属性を設定する必要がある、最も下位レベルのコールです。OCIServerAttach()はこのコールより前にコールされます。

  4. OCISessionGet()

    セッションを取得する方法としてお薦めします。このセッションは、基礎となる新規の接続があるセッション、既存の接続プールから仮想接続によって開始されたセッション、または既存のセッション・プールからのセッションである場合があります。この関数がコールされるときのmodeパラメータ値によって、動作が決まります。この動作はOCILogon2()に似ていますが、これを使用して、プールから特定のセッションを取得するタグを指定することもできます。


    関連項目:


    「OCISessionGet()」

様々なタイプのOCIセッションの選択

選択肢は次のとおりです。

  • 基本的なOCIセッション

    専用OCIサーバー・ハンドル上でユーザー名およびパスワードを使用するとこれは機能します。これは、プール・メカニズムではありません。使用の際は、前述の注意事項を参照してください。

    外部資格証明を使用して認証された場合、ユーザー名またはパスワードは必要ありません。

  • セッション・プール・セッション

    これらのセッションは、セッション・プール・キャッシュから取り出されます。一部のセッションはタグ付けされる場合があります。これは、ステートレス・セッションです。OCISessionGet()コールおよびOCISessionRelease()コールはそれぞれ、セッション・キャッシュからセッションを取得および解放します。これにより、セッションの作成および破棄からサーバーが保護されます。

    接続プール・セッション、接続プーリング・セッションまたはプーリング以外のセッションについては、前述の注意事項を参照してください。

  • 接続プール・セッション

    OCI接続プールからのOCISessionGet()コールおよびOCISessionBegin()コールを使用して作成されたセッションです。これはステートフル・セッションであるため、セッション・キャッシュはありません。各コールによって新規セッションが作成され、ユーザーの責任でこれらのセッションを終了します。

    セッションは、接続プールのサーバー・ハンドル間で自動的に移行できます。各セッションにユーザー名およびパスワードを含めたり、各セッションをプロキシ・セッションにすることができます。接続プール・セッション、接続プーリング・セッションまたはプーリング以外のセッションについては、前述の注意事項を参照してください。

  • サーバー・ハンドルを共有するセッション

    少数の物理接続で複数のOCIセッションを多重化できます。これらの複数のセッションに同じサーバー・ハンドルを持たせると、アプリケーションにより、手動で多重化できます。OCI接続プールのAPIを使用して、セッション多重化の詳細はOCIで行うことをお薦めします。

  • プロキシ・セッション

    クライアントのパスワードを中間層から保護する必要がある場合に、これは役立ちます。プロキシ・セッションはまた、OCI接続プールおよびOCIセッション・プールの一部に含めることができます。

  • 移行可能なセッション

OCI接続プーリングを考慮すると、移行可能なトランザクション・ハンドルを使用した場合、アプリケーションでこの旧機能を使用する必要はありません。

OCIでの文キャッシュ

文キャッシュは、セッションごとの文のキャッシュを提供および管理する機能です。文キャッシュによって、サーバーではカーソルが使用できる状態になり、文を再解析する必要はありません。文キャッシュは、接続プーリングおよびセッション・プーリングとともに使用でき、パフォーマンスおよび拡張性が向上します。また、セッション・プーリングを使用しない場合でも使用できます。文キャッシュを実装するOCIコールは次のとおりです。

OCIでの、セッション・プーリングを使用しない文キャッシュ

ユーザーは、OCIの通常の手順でログインします。セッションを取得するコールでは、セッションに対して文キャッシュが使用可能かどうかを指定するモードがあります。当初文キャッシュは空です。開発者は、文のテキストを使用して、キャッシュ内の文を検索します。文が存在すると、APIは、すでにプリコンパイルされたSQL文のハンドルを戻します。文が存在しない場合は、新規にプリコンパイルされたSQL文のハンドルを戻します。

アプリケーション開発者は、文がキャッシュに戻される前に、文のバインドと定義を実行し、その後に文の実行とフェッチを実行できます。文ハンドルが見つからない場合、開発者は、追加のステップとして、ハンドルに別の属性を設定する必要があります。

OCIStmtPrepare2()には、キャッシュ内に文が見つからない場合に、開発者がプリコンパイルされたSQL文を使用するのか、またはNULLの文ハンドルを使用するのかを指定するモードがあります。

次にコード例を示します。

OCISessionBegin( userhp, ... OCI_STMT_CACHE)  ;
OCIAttrset(svchp, userhp, ...);  /* Set the user handle in the service context */
OCIStmtPrepare2(svchp, &stmthp, stmttext, key, ...);
OCIBindByPos(stmthp, ...);
OCIDefineByPos(stmthp, ...);
OCIStmtExecute(svchp, stmthp, ...);
OCIStmtFetch(svchp, ...);
OCIStmtRelease(stmthp, ...);
...

OCIでの、セッション・プーリングを使用した文キャッシュ

前述の文キャッシュの場合と概念は同じですが、文キャッシュはセッション・レイヤーではなくセッション・プール・レイヤーで使用可能になります。

属性OCI_ATTR_SPOOL_STMTCACHESIZEは、セッション・プールに含まれる各セッションのデフォルトの文キャッシュ・サイズをこの値に設定します。これは、OCI_HTYPE_SPOOLハンドル上で設定されます。プールの特定のセッションの文キャッシュ・サイズは、そのセッションでOCI_ATTR_STMTCACHESIZEを使用することでいつでも上書きできます。OCI_ATTR_SPOOL_STMTCACHESIZEの値はいつでも変更できます。この属性を使用して、作成後に、プール・レベルで文キャッシュを使用可能または使用不可にできます。同様に、属性OCI_ATTR_STMTCACHESIZE(サービス・コンテキスト上)を使用してセッション・レベルで文キャッシュを使用可能または使用不可にできます。この変更は、プールの各セッションがユーザーに渡されると、各セッションに反映されます。タグ付けされたセッションはこの動作からは除外されます。これについては、後述します。

文キャッシュの使用可能または使用不可は、プーリング以外のセッションと同様に、各プーリング・セッションで許可されます。

OCI_SESSGET_STMTCACHEまたはOCI_LOGON2_STMTCACHEをそれぞれモード引数に指定すると、OCISessionGet()コールまたはOCILogon2()コールの文キャッシュ以外のプールから取り出されたセッションでも、ユーザーは文キャッシュを使用可能にできます。

セッション・プール内のセッションを要求する場合は、そのセッションの文キャッシュ・サイズのデフォルトはプールの文キャッシュ・サイズとなります。つまり、そのセッション内で文キャッシュを使用可能または使用不可にすることができます。たとえば、プーリング・セッション(セッションA)に使用可能な文キャッシュがあり、文キャッシュがプールでオフにされ、ユーザーがセッションを要求してセッションAが戻された場合、文キャッシュはセッションAではオフになります。また、別の例としては、プールのセッションAに使用可能な文キャッシュがなく、プール・レベルの文キャッシュがオンにされた場合、セッションAがユーザーに戻される前に、プールのセッションとサイズが等しいセッションAの文キャッシュがオンになります。

タグ付けされたセッションが要求され、取り出された場合は、異なる結果となります。この場合は、文キャッシュのサイズは変更されません。したがって、文キャッシュはオンまたはオフになりません。さらに、OCISessionGet()コールでユーザーがモードOCI_SESSGET_STMTCACHEを指定する場合、このセッションがタグ付けされていると、これは無視されます。前述の例では、セッションAがタグ付けされていた場合、ユーザーに対するセッションとして戻されます。

OCIでの文キャッシュの規則

次に、いくつかの規則を示します。

  • OCIStmtPrepare()ではなく、OCIStmtPrepare2()関数を使用します。OCIStmtPrepare()を使用する場合は、複数のサービス・コンテキスト間で1つの文ハンドルを使用しないことをお薦めします。この関数を使用した場合、OCIStmtPrepare2()を使用して文を取得するとエラーが発生します。文ハンドルを新規のサービス・コンテキストに移行するとき、実際には古いセッションに関連するカーソルがクローズされるため、文ハンドルの共有は取得されません。OCIでは、文ハンドルが移行されると、古いセッションに関連するすべてのバッファを解放するため、クライアント側での共有も取得されません。

  • セッションごとに1つのサービス・コンテキストを保持し、そのサービス・コンテキストに対してのみ文ハンドルを使用することをお薦めします。デフォルトはこの使用モデルに設定され、このモデルの使用をお薦めします。

  • セッションで文キャッシュが行われない場合でもOCIStmtPrepare2()コールで文ハンドルが割り当てられるため、OCIStmtPrepare2()のみを使用するアプリケーションでは、その文ハンドルに対してOCIHandleAlloc()をコールしないでください。

  • OCIStmtPrepare2()のコール後、ユーザーが文ハンドルでの処理を完了した後にOCIStmtRelease()をコールする必要があります。文キャッシュを使用する場合、このコールによって文はキャッシュから解放されます。文キャッシュを使用しない場合、文は割当て解除されます。OCIHandleFree()をコールしてメモリーを解放しないでください。

  • OCI_PREP2_CACHE_SEARCHONLYモードを指定してOCIStmtPrepare2()をコールし、NULLの文が戻された(文が見つからなかった)場合、後続のOCIStmtRelease()コールは必要ないため、実行しないでください。

  • OCIStmtPrepare()を使用してプリコンパイルされた文に対して、OCIStmtRelease()をコールしないでください。

  • 文キャッシュには最大サイズ(文の数)があり、サービス・コンテキストのOCI_ATTR_STMTCACHESIZE属性で変更できます。デフォルト値は20です。この属性を使用して、プールされている、またはプールされていないセッションの文キャッシュを使用可能または使用不可にすることもできます。モードをOCI_STMT_CACHEに設定しないでOCISessionBegin()をコールする場合、サービス・コンテキストのOCI_ATTR_STMTCACHESIZEを0(ゼロ)以外の属性に設定し、文キャッシュをオンにできます。セッション・プール・レベルで文キャッシュをオンにしない場合、OCISessionGet()により、文キャッシュに対応していないセッションが戻されます。OCI_ATTR_STMTCACHESIZEを使用すると、キャッシュをオンにできます。同様に、同じ属性を使用してキャッシュ・サイズを0(ゼロ)に設定することにより、文キャッシュをオフにできます。

  • 文の解放時に、文にタグを付けることができます。これによって、次回に同じタグの文を要求できます。タグは、キャッシュを検索するために使用します。タグなしの文(タグがNULL)は、タグ付きの文の特殊なケースです。2つの文がタグのみ異なる場合、または、1つの文はタグ付きでもう1つの文はタグなしの場合、その2つの文は異なる文とみなされます。


    関連項目:


    文キャッシュに関する関数については、次を参照してください。

OCIでの文キャッシュのコード例

文キャッシュのコード例は、次を参照してください。


関連項目:


文キャッシュの作業例は、ディレクトリdemocdemostc.cを参照してください。

OCIでのユーザー定義コールバック関数

Oracle Call Interfaceには、OCIコールの他にユーザー固有のコードを実行する機能があります。この機能は次の用途に使用できます。

OCIコールの実行前後にユーザー・コードをコールできる機能のサポートによって、OCIコールバック機能が増強されました。OCIコードのかわりに、ユーザー定義コードを実行できる機能も用意されています。

アプリケーションのソース・コードを修正することなく、ユーザー・コールバック・コードを動的に登録することもできます。動的な登録は、OCIEnvCreate()コール時に環境ハンドルを初期化した後、ユーザーが作成した5つ以下の動的リンク・ライブラリをロードすることによって実装されます。 ユーザーが作成したライブラリ(WindowsのDynamic Link Library(DLL)またはSolaris上の共有ライブラリなど)では、選択したOCIコールに対するユーザー・コールバックをアプリケーションに透過的に登録します。

サンプル・アプリケーション

OCIユーザー・コールバック機能を示したすべてのデモ・プログラムのリストについては、付録B「OCIデモ・プログラム」を参照してください。

OCIでのユーザー・コールバックの登録

アプリケーションから、OCIUserCallbackRegister()関数を使用してユーザー・コールバック・ライブラリを登録できます。コールバックは、環境ハンドルのコンテキスト内に登録されます。アプリケーションでは、OCIUserCallbackGet()関数を使用してハンドルに登録されたコールバックについての情報を取り出すことができます。

ユーザー定義コールバックは、OCIコールおよび環境ハンドルに対して登録されたサブルーチンです。ユーザー定義コールバックには、最初のコールバック、置換コールバックまたは最後のコールバックを指定できます。

  • 最初のコールバックの場合は、プログラムからOCI関数をコールするときにコールされます。

  • 置換コールバックは、最初のコールバック実行の後に実行されます。置換コールバックからOCI_CONTINUEの値が戻されると、後続の置換コールバックまたは通常のOCIコードが実行されます。置換コールバックからOCI_CONTINUE以外の値が戻される場合は、後続の置換コールバックおよびOCIコードは実行されません。

  • 置換コールバックからOCI_CONTINUE以外の値が戻されるか、またはOCI関数が正常に実行されると、プログラムの制御は最後のコールバックに移ります(最後のコールバックが登録されている場合)。

置換コールバックまたは最後のコールバックからOCI_CONTINUE以外が戻された場合は、コールバックからのリターン・コードは、対応付けられたOCIコールから戻されます。

ユーザー・コールバックでは、無効なハンドルまたは無効なコンテキストが渡された場合に、OCI_INVALID_HANDLEを戻すことがあります。


注意:


任意のコールバックからOCI_CONTINUE以外の値が戻された場合は、リターン・コードが後続のコールバックに渡されます。置換コールバックまたは最後のコールバックからOCI_CONTINUE以外のリターン・コードが戻された場合は、最後の(OCI_CONTINUEではない)リターン・コードがOCIコールから戻されます。

OCIUserCallbackRegister

ユーザー・コールバックの登録には、OCIUserCallbackRegister()コールを使用します。

現段階では、OCIUserCallbackRegister()は環境ハンドル上でしか登録できません。typedef OCIUserCallbackのユーザー・コールバック関数は、OCI関数コードfcodeによって識別されるOCIコールのコンテキストとともに登録します。コールバックの型は、最初のコールバック、置換または最後のコールバックのいずれの場合もwhenパラメータによって指定します。

たとえば、最初のコールバック関数stmtprep_entry_dyncbk_fnとそのコンテキストdynamic_contextは、次のパラメータを使用してOCIUserCallbackRegister()関数をコールすることにより、OCIStmtPrepare()コールの環境ハンドルhndlpに対して登録されます。

OCIUserCallbackRegister( hndlp,
                         OCI_HTYPE_ENV,
                         errh,
                         stmtprep_entry_dyncbk_fn,
                         dynamic_context,
                         OCI_FNCODE_STMTPREPARE,
                         OCI_UCBTYPE_ENTRY
                         (OCIUcb*) NULL);

ユーザー・コールバック関数

ユーザー・コールバック関数は、次の構文に従う必要があります。

typedef sword (*OCIUserCallback)
     (void *ctxp,      /* context for the user callback*/
      void *hndlp,     /* handle for the callback, env handle for now */
      ub4 type,         /* type of handlp, OCI_HTYPE_ENV for this release */
      ub4 fcode,        /* function code of the OCI call */
      ub1 when,         /* type of the callback, entry or exit */
      sword returnCode, /* OCI return code */
      ub4 *errnop,      /* Oracle error number */
      va_list arglist); /* parameters of the oci call */

コールバックは、OCIUserCallbackRegister()コールで説明したパラメータの他に、リターン・コードerrnop、およびプロトタイプ定義で宣言された元のOCIのすべてのパラメータを使用してコールします。

最初のコールバックでは、リターン・コードは常にOCI_SUCCESSとして渡され、*errnopは常に0として渡されます。errnopはIN/OUTパラメータであるため、*errnoperrnopの内容を指します。

コールバックでOCIリターン・コードを変更しない場合は、コールバックからOCI_CONTINUEを戻す必要があります。*errnopで戻された値は無視されます。一方、コールバックからOCI_CONTINUE以外のリターン・コードが戻された場合は、最後に戻されたリターン・コードがそのコールのリターン・コードになります。このとき、戻された*errnopの値はエラー・ハンドル内に設定されます。OCIHandleAlloc()などの特定のOCIコールにはエラー・ハンドルがないため、エラー情報が環境ハンドルに戻された場合は、環境ハンドル内に設定されます。

置換コールバックの場合は、returnCodeは前のコールバックまたはOCIコールから戻されるOCI_CONTINUE以外のリターン・コードであり、*errnopはエラー・ハンドル内に戻されるエラー番号の値です。これによって、後続のコールバックでは、必要に応じてリターン・コードやエラー情報を変更できます。

置換コールバックからOCI_CONTINUE以外のリターン・コードが戻されると、後続の置換コールバックおよびOCIコードは無視され、最後のコールバックの処理に移るという点で、置換コールバックの処理は異なります。

置換コールバックからOCI_CONTINUEが戻されてOCIコードの処理が可能になった場合は、最初のコールバックからのリターン・コードが無視されることに注意してください。

OCIコールの元のパラメータは、すべて変数パラメータとしてコールバックに渡されます。コールバックでは、それらをva_argを使用して取り出す必要があります。コールバックのデモ・プログラムには例が提供されています。

コールバックの登録を解除するために、NULL値を登録することができます。つまり、OCIUserCallbackRegister()コールのコールバック(OCIUserCallback())の値がNULLの場合は、ユーザー・コールバックの登録は解除されます。

スレッドセーフ・モードを使用している場合、OCIプログラムでは、ユーザー・コールバックをコールする前にすべてのmutexを取得します。

UserCallbackの制御フロー

次の擬似コードは、標準的なOCIコールの処理の概要を示しています。

OCIXyzCall()
{
 Acquire mutexes on handles;
 retCode = OCI_SUCCESS;
 errno = 0;
 for all ENTRY callbacks do
  {

     EntryretCode = (*entryCallback)(..., retcode, &errno, ...);
     if (retCode != OCI_CONTINUE)
      {
         set errno in error handle or environment handle;
         retCode = EntryretCode;
       }
   }
  for all REPLACEMENT callbacks do
  {
   retCode = (*replacementCallback) (..., retcode, &errno, ...);
   if (retCode != OCI_CONTINUE)
      {
       set errno in error handle or environment handle
       goto executeEXITCallback;
       }
   }

   retCode = return code for XyzCall; /* normal processing of OCI call */

   errno = error number from error handle or env handle;

 executeExitCallback:
   for all EXIT callbacks do
   {
       exitRetCode = (*exitCallback)(..., retCode, &errno,...);
       if (exitRetCode != OCI_CONTINUE)
       {
           set errno in error handle or environment handle;
           retCode = exitRetCode;
       }
   }
    release mutexes;
    return retCode
}

OCIErrorGet()に対するUserCallback

OCIコード全体がコールバックに置き換わる場合、コールバックでは、通常、コール・コンテキストに独自のエラー情報をメンテナンスし、この情報を使用して、OCIErrorGet()コールの置換コールバックのbufpパラメータおよびerrnopパラメータにエラー情報を戻します。

一方、コールバックがOCIコードの一部に置き換わるか、なんらかの後処理のみを行う場合は、最後のコールバックを使用して、エラー・テキストやOCIErrorGet()errnopパラメータを、独自のエラー・メッセージやエラー番号に修正できます。最後のコールバックに渡される*errnopは、エラー・ハンドルまたは環境ハンドル内のエラー番号です。

最初のコールバックのエラー

最初のコールバックからOCIコールのコール元にエラーを戻す場合は、置換コールバックまたは最後のコールバックを登録する必要があります。これは、OCIコードが実行される場合、最初のコールバックのエラー・コードが無視されるためです。したがって、最初のコールバックから独自のコンテキストによって、置換コールバックまたは最後のコールバックにエラーが渡される必要があります。

動的なコールバック登録

ユーザー・コールバックは、OCI動作の監視または他のデータ・ソースへアクセスするために使用されることが多いので、コールバックの登録は、割込みが発生しないよう透過的に行われることが望まれます。これは、OCIの初期化時に、ユーザーが作成した動的リンク・ライブラリをロードすることで可能になります。これらの動的リンク・ライブラリは、パッケージと呼ばれます。ユーザーが作成したパッケージは、選択したOCIコール用のユーザー・コールバックを登録します。これらのコールバックでは、実行時に制御を受け取ったときに、必要に応じてユーザー・コールバックの追加登録または登録解除を行うことができます。

OCIデモ・プログラムとともに、パッケージを作成するためのMakefile(Solarisのociucb.mk)が用意されています。このパッケージの名前と位置は、オペレーティング・システムによって異なります。パッケージのソース・コードには、OCIの初期化時および環境作成時にコールされる特別なコールバックのコードを含める必要があります。

パッケージのロードを制御するには、オペレーティング・システムの環境変数ORA_OCI_UCBPKGを設定します。この変数によって、汎用的な方法でパッケージに名前が付けられます。パッケージは、$ORACLE_HOME/libディレクトリに格納してください。

複数のパッケージのロード

ORA_OCI_UCBPKG変数には、パッケージ名をセミコロンで区切ったリストを含めることができます。パッケージは、リストに指定されている順序でロードされます。

たとえば、以前はパッケージを次のように指定しました。

setenv ORA_OCI_UCBPKG mypkg

現在でも前述の指定はできますが、さらに複数のパッケージを次のように指定できます。

setenv ORA_OCI_UCBPKG "mypkg;yourpkg;oraclepkg;sunpkg;msoftpkg"

これらのパッケージはすべて順番にロードされます。つまり、最初にmypkgがロードされ、最後にmsoftpkgがロードされます。

最大5パッケージまで指定できます。


注意:


サンプルのMakefile ociucb.mkによって、Solarisではociucb.so.1.0、Windowsシステムではociucb.dllが作成されます。ociucbパッケージをロードするには、環境変数ORA_OCI_UCBPKGociucbを設定する必要があります。パッケージ名が.soで終わっている場合、SolarisではOCIInitialize()がエラーになります。パッケージ名は、.so.1.0で終わる必要があります。

DLLの作成の詳細は、オペレーティング・システムのデモ・ディレクトリにあるMakefileをお読みください。ユーザー定義のコールバックの詳細は、使用されているオペレーティング・システムのドキュメントでアプリケーションのコンパイルとリンクの項を参照してください。


パッケージ・フォーマット

以前は、パッケージでOCIEnvCallback()関数のソース・コードを指定する必要がありました。現在、OCIEnvCallback()関数は使用されていません。かわりに、パッケージのソース・コードに2つの関数を用意する必要があります。第1の関数の名前はpackagenameにして、Initという接尾辞を付けます。たとえば、パッケージの名前がfooである場合、ソース・ファイル(必ずしもfoo.cである必要はありません)には、OCISharedLibInit()関数のコールとともにfooInit()関数を含めてください。次に例を示します。

sword fooInit(metaCtx, libCtx, argfmt, argc, argv)
      void *     metaCtx;         /* The metacontext */
      void *     libCtx;          /* The context for this package. */
      ub4        argfmt;          /* package argument format */
      sword      argc;            /* package arg count*/
      void *     argv[];          /* package arguments */
{
  return  (OCISharedLibInit(metaCtx, libCtx, argfmt, argc, argv,
                            fooEnvCallback));
}

この場合、OCISharedLibInit()関数の最後のパラメータであるfooEnvCallback()は、第2の関数の名前になります。第2の関数の名前は通例packagenameにして、EnvCallbackという接尾辞を付けます。

この関数は、OCIEnvCallback()にかわるものです。すべての動的なユーザー・コールバックは、この関数に登録する必要があります。関数は、次のように指定されるOCIEnvCallbackType型にしてください。

typedef sword (*OCIEnvCallbackType)(OCIEnv *env, ub4 mode,
                                    size_t xtramem_sz, void *usrmemp,
                                    OCIUcb *ucbDesc);

環境ハンドルが作成されると、このコールバック関数が最後にコールされます。envパラメータは、新しく作成された環境ハンドルです。

modextramem_szおよびusrmempは、OCIEnvCreate()コールに渡されるパラメータです。最後のパラメータucbDescは、パッケージに渡される記述子です。後述するように、パッケージではこの記述子を使用して、ユーザー・コールバックを登録します。

サンプルのociucb.cファイルがデモ・ディレクトリにあります。Solarisでは、パッケージ作成用のMakefile ociucb.mkも、demoディレクトリに用意されています。これはオペレーティング・システムによって異なりますので、注意してください。さらにデモ・ディレクトリには、ユーザー・コールバックの実例を示すデモ・プログラムがすべて(cdemoucb.c、cdemoucbl.c)揃っています。

ユーザー・コールバックの連鎖

ユーザー・コールバックは、アプリケーション自体に静的に登録することも、実行時にDLL内に動的に登録することもできます。動的な登録の目的の動作を維持するには、以前に登録されていたコールバックを上書きした後に、上書きしたコールバックを新しく登録したコールバック内でコールするという、アプリケーションのメカニズムが必要です。この結果、ユーザー・コールバックが連鎖することがあります。

連鎖した場合のために、OCIコールに登録されている関数およびコンテキストを確認するためのOCIUserCallbackGet()関数が提供されています。

OCIを介した他のデータ・ソースへのアクセス

Oracleはアクセス頻度の高いデータベースであるため、アプリケーションからOracle以外のデータにアクセスする場合は、ユーザー・コールバックを使用してOCIインタフェース経由でアクセスします。これによって、OCIに記述されたアプリケーションでは、パフォーマンスを低下させずにOracleデータにアクセスすることができます。Oracle以外のデータ・ソースにアクセスするには、ユーザー・コールバック内に、Oracle以外のデータにアクセスするドライバを記述します。OCIでは豊富なインタフェースを提供しているため、通常は、OCIコールからほとんどのデータ・ソースに直接マッピングできます。この方法は、ODBCなどの他の中間層のアプリケーションを記述する方法より優れています。後者の場合、すべてのデータ・ソースのパフォーマンスが低下します。OCIを使用してOracleデータ・ソースに通常どおりアクセスした場合は、パフォーマンスが低下することはありませんが、Oracle以外のデータ・ソースにアクセスした場合は、ODBC経由と同じパフォーマンスになります。

コールバック関数の制限

OCIEnvCallback()などのコールバック関数の使用方法には、いくつかの制約があります。

  • コールバックからは、OCIUserCallbackRegister()OCIUserCallbackGet()OCIHandleAlloc()およびOCIHandleFree()以外のOCI関数をコールできません。これらの関数であっても、ユーザー・コールバックでコールされている場合、コールバックは再帰を避けるためにコールされません。たとえば、OCIHandleFree()OCILogoff()のコールバックでコールされている場合は、OCILogoff()のコールバックの実行中にOCIHandleFree()のコールバックを行うことはできません。

  • コールバックでは、環境ハンドルまたはエラー・ハンドルなどのOCIデータ構造を変更できません。

  • OCIUserCallbackRegister()コール自体または次のいずれかのコールに対して、コールバックは登録できません。

    • OCIUserCallbackGet()

    • OCIEnvCreate()

    • OCIInitialize()

    • OCIEnvInit()

OCIコールバックの例

たとえば、OCIStmtPrepare()コールに対してそれぞれ最初のコールバック、置換コールバックおよび最後のコールバックを登録している5つのパッケージがあるとします。すなわち、ORA_OCI_UCBPKG変数が次のように設定されています。

setenv ORA_OCI_UCBPKG "pkg1;pkg2;pkg3;pkg4;pkg5"

各パッケージpkgN(Nは1〜5)では、pkgNInit()関数およびPkgNEnvCallback()関数を次のように指定します。

pkgNInit(void *metaCtx, void *libCtx, ub4 argfmt, sword argc, void **argv)
{
  return OCISharedLibInit(metaCtx, libCtx, argfmt, argc, argv, pkgNEnvCallback);
}

pkgNEnvCallback()関数は、次のように最初のコールバック、置換コールバックおよび最後のコールバックを登録します。

pkgNEnvCallback(OCIEnv *env, ub4 mode, size_t xtramemsz,
                                void *usrmemp, OCIUcb *ucbDesc)
{
  OCIHandleAlloc((void *)env, (void **)&errh, OCI_HTYPE_ERROR, (size_t) 0,
        (void **)NULL);

  OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, pkgN_entry_callback_fn,
        pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_ENTRY, ucbDesc);

  OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, pkgN_replace_callback_fn,
        pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_REPLACE, ucbDesc);

  OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, pkgN_exit_callback_fn,
        pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_EXIT, ucbDesc);

  return OCI_CONTINUE;
}

最後に、アプリケーションのソース・コードでは、ユーザー・コールバックを次のようにNULL ucbDescで登録できます。

  OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, static_entry_callback_fn,
        pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_ENTRY, (OCIUcb *)NULL);

  OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, static_replace_callback_fn,
        pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_REPLACE, (OCIUcb *)NULL);

  OCIUserCallbackRegister(env, OCI_HTYPE_ENV, errh, static_exit_callback_fn,
        pkgNctx, OCI_FNCODE_STMTPREPARE, OCI_UCBTYPE_EXIT, (OCIUcb *)NULL);

OCIStmtPrepare()コールが実行される場合、コールバックは次の順にコールされます。

static_entry_callback_fn()
pkg1_entry_callback_fn()
pkg2_entry_callback_fn()
pkg3_entry_callback_fn()
pkg4_entry_callback_fn()
pkg5_entry_callback_fn()

static_replace_callback_fn()
 pkg1_replace_callback_fn()
  pkg2_replace_callback_fn()
   pkg3_replace_callback_fn()
    pkg4_replace_callback_fn()
     pkg5_replace_callback_fn()

      OCI code for OCIStmtPrepare call

pkg5_exit_callback_fn()
pkg4_exit_callback_fn()
pkg3_exit_callback_fn()
pkg2_exit_callback_fn()
pkg1_exit_callback_fn()

static_exit_callback_fn()

注意:


最後のコールバックは、最初のコールバックおよび置換コールバックとは逆の順にコールされます。

最初のコールバックと最後のコールバックからは任意のリターン・コードが戻され、続いて次のコールバックが処理されます。ただし、置換コールバックからOCI_CONTINUE以外のリターン・コードが戻された場合は、連鎖の次のコールバック(または、最後の置換コールバックの場合はOCIコード)が無視され、最後のコールバックの処理に移ります。たとえば、pkg3_replace_callback_fn()からOCI_SUCCESSが戻された場合、pkg4_replace_callback_fn()pkg5_replace_callback_fn()およびOCIStmtPrepare()コールのOCI処理は無視されます。かわりに、pkg5_exit_callback_fn()が次に実行されます。

外部プロシージャからのOCIコールバック

外部プロシージャからコールバックとして使用できるいくつかのOCI関数があります。


関連項目:


これらの関数については、第20章「OCIカートリッジ関数」のリストを参照してください。PL/SQLコードからコールできるCサブルーチンの作成、使用可能なOCIコールのリスト、およびコード例の詳細は、『Oracle Databaseアドバンスト・アプリケーション開発者ガイド』を参照してください。

OCIでの透過的アプリケーション・フェイルオーバー

透過的アプリケーション・フェイルオーバー(TAF)はクライアント側の機能であり、インスタンスまたはネットワークの障害によりデータベース接続に失敗すると発生するエンドユーザー・アプリケーションに対する障害を最小限にするために設計されています。TAFは、Real Application ClustersおよびData Guardフィジカル・スタンバイ・データベースなどの各種システム構成に実装できます。TAFは、修復時などの単一インスタンス・システムの再起動後にも使用できます。

TAFは、データベース・セッションをリストアし、オプションでオープン問合せを再実行するよう構成できます。以前のバージョンでは、SELECTフェイルオーバー・オプションを持つTAFは、障害時に使用されていた文でのみ実行されていました。たとえば、アプリケーションで10個の文ハンドルが使用中で、7番目の文が障害時の文(障害発生時に使用中の文)だった場合は、7番目の文がTAFを使用してフェイルオーバーされた後に、1番目から6番目までの文と8番目から10番目までの文を再実行する必要がありました。

10gリリース2(10.2)以降、これが改善されています。このリリースでは、アプリケーションが障害後に使用するすべての文で、フェイルオーバーが試行されます。つまり、他の文に対する実行またはフェッチ試行により、障害時文の場合と同様にTAFリカバリが実行されます。これは、後続の文が今回成功する(前回は失敗)か、または試行されたTAFリカバリに対応するエラー(ORA-25401など)をアプリケーションで受信する可能性があることを明白に意味します。


注意:


リモート・データベース・リンクまたはDML文については、TAFはサポートされていません。

透過的アプリケーション・フェイルオーバーの構成

TAFは、クライアント側とサーバー側のどちらでも構成できます。両方とも構成した場合、サーバー側の設定が優先されます。

クライアント側でTAFを構成するには、接続記述子のCONNECT_DATAの部分にFAILOVER_MODEパラメータを含めます。


関連項目:


TAF(接続データ・セクション)のクライアント側における構成の詳細は、『Oracle Database Net Servicesリファレンス・ガイド』を参照してください。

サーバー側でTAFを構成するには、DBMS_SERVICE.MODIFY_SERVICEパッケージ・プロシージャによりターゲット・サービスを変更します。

最初のフェイルオーバーは成功しないことがあります。OCIでは、失敗した後にフェイルオーバーを再試行するメカニズムを提供しています。


関連項目:


TAF(DBMS_SERVICE)のサーバー側における構成の詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』を参照してください。

OCIでの透過的アプリケーション・フェイルオーバー・コールバック

フェイルオーバー中に遅延が発生する可能性があるため、アプリケーション開発者は、フェイルオーバーが進行中であることをユーザーに知らせ、フェイルオーバーの完了通知をユーザーが待機するよう要求した方がよい場合があります。さらに、最初のインスタンスのセッションで、いくつかのALTER SESSIONコマンドを受け取る可能性もあります。これらのALTER SESSIONコマンドが、2番目のインスタンスで自動的に再実行されることはありません。したがって、開発者は、これらのコマンドを2番目のインスタンスで再実行する可能性があります。

これらの要件に対応するため、アプリケーションの開発者は、フェイルオーバー・コールバック関数を登録できます。フェイルオーバーが発生した場合、コールバック関数は、ユーザー・セッションを再確立する過程で数回呼び出されます。

コールバック関数が最初にコールされるのは、データベースがインスタンスの接続中断を最初に検出したときです。このコールバックは、アプリケーションがユーザーに対して遅延の発生を通知することを目的にしています。フェイルオーバーが正常終了すると、コールバック関数の2番目のコールは、接続が再確立して使用可能になったときに発生します。

接続が再確立されると、クライアントでALTER SESSIONコマンドが再実行され、ユーザーにフェイルオーバーが発生したことを通知する場合があります。フェイルオーバーが異常終了した場合、コールバックは、フェイルオーバーが発生しなかったことをアプリケーションに通知するためにコールされます。さらに、1次ハンドル以外のユーザー・ハンドルが新しい接続で再認証されるたびに、コールバックがコールされます。各ユーザー・ハンドルはサーバー側セッションを表すため、クライアントでは、そのセッションのためにALTER SESSIONコマンドを再実行する場合があります。


関連項目:


この場合の詳細は、「OCI_FO_ERRORの処理」を参照してください。

フェイルオーバー・コールバック構造およびパラメータ

ユーザー定義のアプリケーション・フェイルオーバー・コールバック関数の基本的な構造は次のとおりです。

sb4 appfocallback_fn ( void       * svchp,
                       void       * envhp,
                       void       * fo_ctx,
                       ub4        fo_type,
                       ub4        fo_event );

次のパラメータは、「フェイルオーバー・コールバックの例」で例を参照してください。

svchp

最初のパラメータのsvchpは、サービス・コンテキスト・ハンドルです。このパラメータはvoid *型です。

envhp

2番目のパラメータのenvhpは、OCI環境ハンドルです。このパラメータはvoid *型です。

fo_ctx

3番目のパラメータのfo_ctxは、クライアント・コンテキストです。このパラメータは、クライアントが指定するメモリーへのポインタです。この領域には、クライアントが必要な状態またはコンテキストを保管できます。このパラメータはvoid *型です。

fo_type

4番目のパラメータのfo_typeは、フェイルオーバーの種類です。このパラメータにより、クライアントが要求したフェイルオーバーの種類をコールバックが判断できます。通常の値は次のとおりです。

  • OCI_FO_SESSION− ユーザーがセッションのフェイルオーバーのみを要求したことを示します。

  • OCI_FO_SELECT− ユーザーが選択フェイルオーバーも要求したことを示します。

fo_event

最後のパラメータは、フェイルオーバーのイベントです。このパラメータは、コールバックがコールされた理由を示します。次の値をとることができます。

  • OCI_FO_BEGIN− フェイルオーバーが中断した接続を検出し、フェイルオーバーが開始することを示します。

  • OCI_FO_END− フェイルオーバーが正常終了したことを示します。

  • OCI_FO_ABORT− フェイルオーバーが失敗し、再試行できないことを示します。

  • OCI_FO_ERROR− 同様にフェイルオーバーが失敗したことを示しますが、アプリケーションでエラーを処理してフェイルオーバーを再試行する機会が与えられます。

  • OCI_FO_REAUTH− 複数の認証ハンドルがあり、最初の認証の後にフェイルオーバーが発生したことを示します。ユーザー・ハンドルが再認証されたことを示します。どのユーザー・ハンドルかを調べるために、アプリケーションで、最初のパラメータであるサービス・コンテキスト・ハンドルのOCI_ATTR_SESSION属性をチェックします。

フェイルオーバー・コールバックの登録

フェイルオーバー・コールバックを使用するには、それをサーバー・コンテキスト・ハンドルに登録する必要があります。この登録は、コールバック定義構造体を作成し、サーバー・ハンドルのOCI_ATTR_FOCBK属性にこの構造体を設定することにより行います。

コールバック定義構造体は、OCIFocbkStruct型にしてください。次の2つのフィールドがあります。1つはcallback_functionで、コールする関数のアドレスが含まれ、もう1つはfo_ctxで、クライアント・コンテキストのアドレスが含まれます。

コールバック登録の例は、次の項にある例の一部に含まれています。

フェイルオーバー・コールバックの例

次のコード例は、単純なユーザー定義コールバック関数の定義、登録および登録解除を示しています。

パート1: フェイルオーバー・コールバックの定義

sb4  callback_fn(svchp, envhp, fo_ctx, fo_type, fo_event)
void * svchp;
void * envhp;
void *fo_ctx;
ub4 fo_type;
ub4 fo_event;
{
switch (fo_event)
   {
   case OCI_FO_BEGIN:
   {
     printf(" Failing Over ... Please stand by \n");
     printf(" Failover type was found to be %s \n",
                     ((fo_type==OCI_FO_SESSION) ? "SESSION"
                     :(fo_type==OCI_FO_SELECT) ? "SELECT"
                     : "UNKNOWN!"));
     printf(" Failover Context is :%s\n",
                    (fo_ctx?(char *)fo_ctx:"NULL POINTER!"));
     break;
   }
   case OCI_FO_ABORT:
   {
     printf(" Failover stopped. Failover will not take place.\n");
     break;
   }
   case    OCI_FO_END:
   {
       printf(" Failover ended ...resuming services\n");
     break;
   }
   case OCI_FO_REAUTH:
   {
       printf(" Failed over user. Resuming services\n");
     break;
   }
   default:
   {
     printf("Bad Failover Event: %d.\n",  fo_event);
     break;
   }
   }
   return 0;
}

パート2: フェイルオーバー・コールバックの登録

int register_callback(srvh, errh)
void *srvh; /* the server handle */
OCIError *errh; /* the error handle */
{
  OCIFocbkStruct failover;                 /*  failover callback structure */
  /* allocate memory for context */
  if (!(failover.fo_ctx = (void *)malloc(strlen("my context.")+1)))
     return(1);
  /* initialize the context. */
  strcpy((char *)failover.fo_ctx, "my context.");
  failover.callback_function = &callback_fn;
  /* do the registration */
  if (OCIAttrSet(srvh, (ub4) OCI_HTYPE_SERVER,
                (void *) &failover, (ub4) 0,
                (ub4) OCI_ATTR_FOCBK, errh)  != OCI_SUCCESS)
     return(2);
  /* successful conclusion */
  return (0);
}

パート3: フェイルオーバー・コールバックの登録解除

OCIFocbkStruct failover;   /*  failover callback structure */
sword status;

  /* set the failover context to null */
  failover.fo_ctx = NULL;
  /* set the failover callback to null */
  failover.callback_function = NULL;
  /* un-register the callback */
  status = OCIAttrSet(srvhp, (ub4) OCI_HTYPE_SERVER,
                      (void *) &failover, (ub4) 0,
                      (ub4) OCI_ATTR_FOCBK, errhp);

OCI_FO_ERRORの処理

フェイルオーバーは、成功しないことがあります。失敗した場合は、コールバック関数には、fo_eventパラメータにOCI_FO_ABORTまたはOCI_FO_ERRORの値が戻されます。OCI_FO_ABORTの場合は、フェイルオーバーが失敗し、フェイルオーバーを続行できないことを示します。OCI_FO_ERRORの場合は、コールバック関数にエラーの対策を指示します。たとえば、一定時間待機してから、コールバックからOCIライブラリにフェイルオーバーの再試行を指示する必要があります。


注意:


この機能が利用できるのは、Oracleサーバーに対して実行されている、リリース8.0.5以上のOCIライブラリにリンクしているアプリケーションのみです。

LOB列が選択リストの一部である場合、フェイルオーバーは機能しません。


次の時系列でイベントが発生すると仮定します。

表9-2 時間およびイベント

時間 イベント

T0

データベースに障害が発生します(障害はT5まで続きます)。

T1

ユーザーの操作によってフェイルオーバーが動作します。

T2

ユーザーが再接続を試行します。試行が失敗します。

T3

OCI_FO_ERRORによってフェイルオーバー・コールバックがコールされます。

T4

フェイルオーバー・コールバックは事前に定義されたスリープ状態になります。

T5

データベースがリストアされます。

T6

フェイルオーバー・コールバックによって新しいフェイルオーバーが動作し、成功します。

T7

ユーザーは再接続に成功します。


コールバック関数からOCI_FO_RETRYの値が戻されると、新しいフェイルオーバーが動作します。

次のコードは、前述のシナリオと類似したフェイルオーバー手法の実装に使用するコールバック関数の例です。この場合、フェイルオーバー・コールバックはループ状態になります。その間いったんスリープ状態になってから、再試行が成功するまでフェイルオーバーを再試行します。

/*--------------------------------------------------------------------*/
/* the user defined failover callback  */
/*--------------------------------------------------------------------*/
sb4  callback_fn(svchp, envhp, fo_ctx, fo_type, fo_event )
void * svchp;
void * envhp;
void *fo_ctx;
ub4 fo_type;
ub4 fo_event;
{
   OCIError *errhp;
   OCIHandleAlloc(envhp, (void **)&errhp, (ub4) OCI_HTYPE_ERROR,
              (size_t) 0, (void **) 0);
   switch (fo_event)
   {
   case OCI_FO_BEGIN:
   {
     printf(" Failing Over ... Please stand by \n");
     printf(" Failover type was found to be %s \n",
            ((fo_type==OCI_FO_NONE) ? "NONE"
             :(fo_type==OCI_FO_SESSION) ? "SESSION"
             :(fo_type==OCI_FO_SELECT) ? "SELECT"
             :(fo_type==OCI_FO_TXNAL) ? "TRANSACTION"
             : "UNKNOWN!"));
     printf(" Failover Context is :%s\n",
            (fo_ctx?(char *)fo_ctx:"NULL POINTER!"));
     break;
   }
   case OCI_FO_ABORT:
   {
     printf(" Failover aborted. Failover will not take place.\n");
     break;
   }
   case    OCI_FO_END:
   {
       printf("\n Failover ended ...resuming services\n");
     break;
   }
   case OCI_FO_REAUTH:
   {
       printf(" Failed over user. Resuming services\n");
     break;
   }
   case OCI_FO_ERROR:
   {
     /* all invocations of this can only generate one line. The newline
      * will be put at fo_end time.
      */
     printf(" Failover error gotten. Sleeping...");
     sleep(3);
     printf("Retrying. ");
     return (OCI_FO_RETRY);
     break;
   }
   default:
   {
     printf("Bad Failover Event: %d.\n",  fo_event);
     break;
   }
   }
   return 0;
}

HAイベント通知

バックエンド・データベース・サーバーにアクセスするアプリケーション・サーバーにログインするのにWebブラウザをユーザーが使用するとします。データベース・インスタンスに障害が発生した場合、障害がユーザーに通知されるまでに最大で数分かかる場合があります。HAイベント通知には、サーバー・インスタンスの障害を迅速に検出する機能、これをクライアントに通知する機能、および接続プールのアイドル状態の接続をクリーン・アップする機能が用意されています。

Real Application Clusters(RAC)データベースに接続された高可用性のクライアントの場合、データベースに障害が発生したときにHAイベント通知を使用して、ベストエフォート型のプログラム・シグナルをクライアントに提供できます。クライアント・アプリケーションは、環境ハンドルにコールバックを登録し、この情報に興味があることを伝えます。このクライアントによって確立された接続に重大な障害イベントが発生すると、そのイベントに関する情報(イベント・ペイロード)や、障害によって切断された接続(サーバー・ハンドル)のリストとともにこのコールバックが呼び出されます。

たとえば、インスタンスAに対する2つの接続と、同じデータベースのインスタンスBに対する2つの接続を持つクライアント・アプリケーションを考えてみます。インスタンスAに障害が発生すると、イベント通知がクライアントに送信され、次に、クライアントにより、インスタンスBに対する2つの接続が切断され、かつ登録されているコールバックが呼び出されます。この場合、同じデータベースの別のインスタンスCに障害が発生しても、クライアントにはこの障害は通知されません(この障害はこのクライアントの接続に影響しないためです)。

HAイベント通知メカニズムにより、障害が発生した際のアプリケーションの応答時間が短縮されます。これまでは、障害が発生した場合、TCPタイムアウトの期限にならないと接続が切断されず、これには数分かかることがありました。HAイベント通知を使用すると、スタンドアロン、接続プールおよびセッション・プール接続はOCIによって自動的に切断されてクリーン・アップされ、障害が発生してから数秒以内にアプリケーション・コールバックが呼び出されます。これらのサーバー・ハンドルのいずれかがTAFに対応している場合は、フェイルオーバーもOCIによって自動的に実行されます。

HAイベント通知を有効化するには、アプリケーションはRACインスタンスに接続する必要があります。また、これらのアプリケーションで次を行う必要があります。

これにより、これらのアプリケーションはコールバックを登録でき、このコールバックが、HAイベントの発生のたびに呼び出されるようになります。

OCIEventハンドル

OCIEventハンドルは、イベント・ペイロードの属性をカプセル化します。OCIは、イベント・コールバックを呼び出す前にこのハンドルを暗黙的に割り当てます。このイベント・コールバックは、OCIAttrGet()を呼び出すことにより、イベントの読取り専用属性を取得できます。これらの属性に関連するメモリーが有効なのは、イベント・コールバック期間中のみです。

接続プールおよびセッション・プールのOCIフェイルオーバー

Real Application Clusters(RAC)のインスタンスの接続プールは、RACの様々なインスタンスに接続された接続のプールで構成されています。特定のインスタンスに接続された接続は、すべてノードの障害通知を受信したら、クリーン・アップする必要があります。使用中の接続については、OCIは接続をクローズする必要があります。この場合、透過的アプリケーション・フェイルオーバー(TAF)が即時に実行され、これらの接続が再確立されます。アイドル状態にありプールの空きリストに記載されている接続は削除し、無効な接続がプールからユーザーに再び戻されないようにする必要があります。

カスタム接続プールに対応するために、OCIには、環境ハンドルに登録可能なコールバック関数が用意されています。登録すると、このコールバックはHAイベントが発生したときに呼び出されます。セッション・プールは、接続プールと同じように処理されます。OCI接続プールまたはセッション・プールのサーバー・ハンドルは、コールバックには渡されません。このため、場合によっては、空の接続リストを使用してコールバックが呼び出されることがあります。

独立した接続のOCIフェイルオーバー

独立した接続の場合、特別な処理は必要ありません。障害が発生したインスタンスに接続されたこのような接続は、すべて即時切断されます。アイドル状態の接続の場合、TAFが実行されるため、次のOCIコールでこの接続が使用される際に接続が再確立されます。傷害イベントの発生時に使用中の接続は即時に切断され、これにより、TAFを開始できるようになります。これは、接続プールおよびセッション・プール内の使用中接続にも当てはまります。

イベント・コールバック

OCIEventCallback型のイベント・コールバックには、次のシグネチャがあります。

void evtcallback_fn (void     *evtctx,
                     OCIEvent  *eventhp );

この場合、evtctxはクライアントのコンテキストで、OCIEventは、OCIライブラリからは不透明なイベント・ハンドルです。もう1つの入力引数は、イベント・ハンドルであるeventhpです。これは、イベントに関連付けられている各属性を含んでいます。

この関数は、登録されると、イベントのたびに1回ずつ呼び出されます。RAC HAイベントの場合、このコールバックは、影響を受けた接続が切断された後に呼び出されます。次の環境ハンドル属性を使用して、イベントのコールバックとコンテキストをそれぞれ登録します。

  • OCI_ATTR_EVTCBKのデータ型は、OCIEventCallback *です。これは読取り専用です。

  • OCI_ATTR_EVTCTXのデータ型は、void *です。これも読取り専用です。

text *myctx = "dummy context"; /* dummy context passed to callback fn */
...
/* OCI_ATTR_EVTCBK and OCI_ATTR_EVTCTX are read-only. */
OCIAttrSet(envhp, (ub4) OCI_HTYPE_ENV, (void *) evtcallback_fn,
           (ub4) 0, (ub4) OCI_ATTR_EVTCBK, errhp);
OCIAttrSet(envhp, (ub4) OCI_HTYPE_ENV, (void *) myctx,
           (ub4) 0, (ub4) OCI_ATTR_EVTCTX, errhp);
...

OCIイベント・コールバック内では、影響を受けるサーバー・ハンドルのリストは、OCIEventハンドルにカプセル化されます。RAC HA DOWNイベントの場合、クライアント・アプリケーションは、属性タイプOCI_ATTR_HA_SVRFIRSTおよびOCI_ATTR_HA_SVRNEXTとともにOCIAttrGet()を使用することにより、イベントの影響を受けるサーバー・ハンドルのリストを反復できます。

OCIAttrGet(eventhp, OCI_HTYPE_EVENT, (void *)&srvhp, (ub4 *)0,
           OCI_ATTR_HA_SRVFIRST, errhp);
/* or, */
OCIAttrGet(eventhp, OCI_HTYPE_EVENT, (void *)&srvhp, (ub4 *)0,
           OCI_ATTR_HA_SRVNEXT, errhp);

この関数は、属性OCI_ATTR_HA_SRVFIRSTを使用して呼び出されると、影響を受けるサーバー・ハンドルのリスト内の最初のサーバー・ハンドルを取得します。この関数は、属性OCI_ATTR_HA_SRVNEXTを使用して呼び出されると、リスト内の次のサーバー・ハンドルを取得します。戻すサーバー・ハンドルがなくなると、この関数はOCI_NO_DATAを戻し、srvhpNULLポインタになります。

srvhpは、HAイベントの結果としてクローズされた接続を持つサーバー・ハンドルの出力ポインタです。errhpは、移入対象のエラー・ハンドルです。取得対象の関連サーバー・ハンドルがなくなると、アプリケーションはOCI_NO_DATAエラーを戻します。

HAイベントの影響を受けたサーバー・ハンドルのリストを取得する場合、接続はクローズ済でありかつ多くのサーバー・ハンドル属性が無効になっていることに注意してください。かわりに、イベント通知コールバックに必要な接続ごとの属性を、サーバー・ハンドルのユーザー・メモリー・セグメントに格納します。このメモリーは、サーバー・ハンドルが解放されるまで有効です。

カスタム・プーリング: タグ付けされたサーバー・ハンドル

次の機能は、カスタム・プールに適用されます。

  1. サーバー・ハンドルをカスタム・プールのかわりに作成する場合、サーバー・ハンドルに親接続オブジェクトのタグを付けることができます。OCIHandleAlloc()のユーザー・メモリー・パラメータを使用して、ユーザー・メモリー・セグメントをサーバー・ハンドルに割り当てるよう要求します。ユーザー・メモリー・セグメントのポインタは、OCIHandleAlloc()によって戻されます。

  2. HAイベントが発生し、影響を受けるサーバー・ハンドルが取得された場合、適切なクリーン・アップを実行できるようサーバー・ハンドルのタグ情報を取得するための方法があります。OCI_ATTR_USER_MEMORY属性を使用して、ハンドルのユーザー・メモリー・セグメントのポインタを取得します。OCI_ATTR_USER_MEMORYは、ユーザーが割り当てたすべてのハンドルに対して有効です。ハンドルに追加メモリーが割り当てられている場合、ユーザー・メモリーへのポインタが戻されます。追加メモリーが割り当てられていないハンドルに関しては、NULLポインタが戻されます。この属性は読取り専用で、データ型はvoid*です。


注意:


サーバー・ハンドルのユーザー・メモリー・セグメントの正確な内容は自由に定義できます。OCIは、このメモリーからは書込みまたは読取りを行わないため、これにより、HAイベント・コールバックからのクリーン・アップ・アクティビティ(必要な場合は、その他の目的)が簡単になります。ユーザー・メモリー・セグメントは、サーバー・ハンドルに対するOCIHandleFree()のコールによって解放されます。

イベント通知の例

sword retval;
OCIServer *srvhp;
struct myctx {
   void *parentConn_myctx;
   uword numval_myctx;
};
typedef struct myctx myctx;
myctx  *myctxp;
/* Allocate a server handle with user memory - pre 10.2 functionality */
if (retval = OCIHandleAlloc(envhp, (void **)&srvhp, OCI_HTYPE_SERVER,
                            (size_t)sizeof(myctx), (void **)&myctxp)
/* handle error */
myctxp->parentConn_myctx = <parent connection reference>;

/* In an event callback function, retrieve the pointer to the user memory */
evtcallback_fn(void *evtctx, OCIEvent *eventhp)
{
  myctx *ctxp = (myctx *)evtctx;
  OCIServer *srvhp;
  OCIError *errhp;
  sb4       retcode;
  retcode = OCIAttrGet(eventhp, OCI_HTYPE_SERVER, &srvhp, (ub4 *)0,
                       OCI_ATTR_HA_SRVFIRST, errhp);
  while (!retcode) /* OCIAttrGet will return OCI_NO_DATA if no more srvhp */
  {
     OCIAttrGet((void *)srvhp, OCI_HTYPE_SERVER, (void *)&ctxp,
                (ub4)0, (ub4)OCI_ATTR_USER_MEMORY, errhp);
           /* Remove the server handle from the parent connection object */
     retcode = OCIAttrGet(eventhp, OCI_HTYPE_SERVER, &srvhp, (ub4 *)0,
                          OCI_ATTR_HA_SRVNEXT, errhp);
...
  }
...
}

透過的アプリケーション・フェイルオーバー(TAF)機能の決定

接続がTAFに対応している場合もいない場合も、アプリケーションにその動作を調整させることができます。次のようにOCIAttrGet()を使用して、サーバー・ハンドルがTAFに対応しているかどうかを確認します。

boolean taf_capable;
...
OCIAttrGet(srvhp, (ub4) OCI_HTYPE_SERVER, (void *) &taf_capable,
           (ub4) sizeof(taf_capable), (ub4)OCI_ATTR_TAF_ENABLED, errhp);
...

この場合、taf_capableはブール変数で、サーバー・ハンドルがTAFに対応している場合はTRUE、対応していない場合はFALSEに設定されます。srvhpは、入力ターゲットのサーバー・ハンドルです。OCI_ATTR_TAF_ENABLEDは、ブール変数のポインタで読取り専用です。errhpは、入力エラー・ハンドルです。

OCIおよびStreamsアドバンスト・キューイング

OCIには、Streamsアドバンスト・キューイング(Streams AQ)機能に対応したインタフェースが用意されています。Streams AQは、Oracleサーバーと統合されたメッセージ・キューイングです。Streams AQのメッセージ・キューイングでは、キューイング・システムをデータベースと統合し、メッセージ対応データベースを作成する機能を提供しています。Streams AQでは統合された解決策が提供されるため、アプリケーション開発者がメッセージ・インフラストラクチャを構築する必要がなくなり、特定のビジネス・ロジックに専念できるようになりました。


注意:


Streamsアドバンスト・キューイングを使用するには、Enterprise Editionが必要です。

 

関連項目:

  • 『Oracle Streamsアドバンスト・キューイング・ユーザーズ・ガイドおよびリファレンス』

  • 『Oracle XML Developer's Kitプログラマーズ・ガイド』

  • AQをOCIとともに使用する方法を示すコード例は、「OCIAQEnq()」の説明を参照してください。


OCI Streamsアドバンスト・キューイング関数

OCIライブラリには、Streamsアドバンスト・キューイングに関して次の関数が含まれています。

  • OCIAQEnq()

  • OCIAQDeq()

  • OCIAQListen()

  • OCIAQListen2()

  • OCIAQEnqArray()

  • OCIAQDeqArray()

シングル・キューにメッセージの配列をエンキューできます。メッセージはすべて、同じエンキュー・オプションを共有しますが、配列内の各メッセージには異なるメッセージ・プロパティを含めることができます。シングル・キューからメッセージの配列をデキューすることもできます。トランザクション・グループ・キューの場合は、1回のコールでシングル・トランザクション・グループにすべてのメッセージをデキューできます。

OCI Streamsアドバンスト・キューイングの説明

次の記述子がOCI Streams AQ操作に使用されます。

  • OCIAQEnqOptions

  • OCIAQDeqOptions

  • OCIAQMsgProperties

  • OCIAQAgent

標準OCIDescriptorAlloc()コールを使用して、サービス・ハンドルに関するこれらの記述子を割り当てられます。次のコードでこの例を示します。

OCIDescriptorAlloc(svch, &enqueue_options, OCI_DTYPE_AQENQ_OPTIONS, 0, 0 );
OCIDescriptorAlloc(svch, &dequeue_options, OCI_DTYPE_AQDEQ_OPTIONS, 0, 0 );
OCIDescriptorAlloc(svch, &message_properties, OCI_DTYPE_AQMSG_PROPERTIES, 0, 0);
OCIDescriptorAlloc(svch, &agent, OCI_DTYPE_AQAGENT, 0, 0 );

各記述子には、設定または読取りができる、様々な属性が含まれます。

OCI Streamsアドバンスト・キューイングとPL/SQL

次の表は、OCI Streams AQ関数および記述子の関数、パラメータ、オプションと、DBMS_AQパッケージのPL/SQL AQ関数との比較を表します。

表9-3 AQ関数

PL/SQLファンクション OCI関数

DBMS_AQ.ENQUEUE

OCIAQEnq()

DBMS_AQ.DEQUEUE

OCIAQDeq()

DBMS_AQ.LISTEN

OCIAQListen()、OCIAQListen2()

DBMS_AQ.ENQUEUE_ARRAY

OCIAQEnqArray()

DBMS_AQ.DEQUEUE_ARRAY

OCIAQDeqArray()


次の表では、エンキュー関数のパラメータを比較します。

表9-4 エンキュー・パラメータ

DBMS_AQ.ENQUEUEパラメータ OCIAQEnq()パラメータ
queue_name
queue_name
enqueue_options
enqueue_options
message_properties
message_properties
payload
payload
msgid
msgid

-

注意: OCIAQEnq()では、さらにsvcherrhpayload_tdopayload_indおよびflagsの各パラメータが必要です。


次の表では、デキュー関数のパラメータを比較します。

表9-5 デキュー・パラメータ

DBMS_AQ.DEQUEUEパラメータ OCIAQDeq()パラメータ
queue_name
queue_name
dequeue_options
dequeue_options
message_properties
message_properties
payload
payload
msgid
msgid

-

注意: OCIAQDeq()では、さらにsvcherrhdequeue_optionsmessage_propertiespayload_tdopayloadpayload_indおよびflagsの各パラメータが必要です。


次の表では、リスニング関数のパラメータを比較します。

表9-6 リスニング・パラメータ

DBMS_AQ.LISTENパラメータ OCIAQListen2()パラメータ
agent_list
agent_list
wait
wait
agent
agent

listen_delivery_mode

lopts

-

OCIAQListen2()では、さらにsvchperrhpagent_listnum_agentsagentlmopsおよびflagsの各パラメータが必要です。


次の表では配列エンキュー関数のパラメータを比較します。

表9-7 配列エンキュー・パラメータ

DBMS_AQ.ENQUEUE_ARRAYパラメータ OCIAQEnqArray()パラメータ
queue_name
queue_name
enqueue_options
enqopt
array_size
iters
message_properties_array
msgprop
payload_array
payload
msgid_array
msgid

-

注意: OCIAQEnqArray()では、さらにsvcherrhpayload_tdopayload_indctxpenqcbfpおよびflagsの各パラメータが必要です。


次の表では配列デキュー関数のパラメータを比較します。

表9-8 配列デキュー・パラメータ

DBMS_AQ.DEQUEUE_ARRAYパラメータ OCIAQDeqArray()パラメータ
queue_name
queue_name
dequeue_options
deqopt
array_size
iters
message_properties_array
msgprop
payload_array
payload
msgid_array
msgid

-

注意: OCIAQDeqArray()では、さらにsvcherrhmsgproppayload_tdopayload_indctxpdeqcbfpおよびflagsの各パラメータが必要です。


次の表では、エージェント属性のパラメータを比較します。

表9-9 エージェント・パラメータ

PL/SQL Agentパラメータ OCIAQAgent属性
name

OCI_ATTR_AGENT_NAME

address

OCI_ATTR_AGENT_ADDRESS

protocol

OCI_ATTR_AGENT_PROTOCOL


次の表ではメッセージ・プロパティのパラメータを比較します。

表9-10 メッセージ・プロパティ

PL/SQLメッセージ・プロパティ OCIAQMsgProperties属性
priority

OCI_ATTR_PRIORITY

delay

OCI_ATTR_DELAY

expiration

OCI_ATTR_EXPIRATION

correlation

OCI_ATTR_CORRELATION

attempts

OCI_ATTR_ATTEMPTS

recipient_list

OCI_ATTR_RECIPIENT_LIST

exception_queue

OCI_ATTR_EXCEPTION_QUEUE

enqueue_time

OCI_ATTR_ENQ_TIME

state

OCI_ATTR_MSG_STATE

sender_id

OCI_ATTR_SENDER_ID

transaction_group

OCI_ATTR_TRANSACTION_NO

original_msgid

OCI_ATTR_ORIGINAL_MSGID

delivery_mode

OCI_ATTR_MSG_DELIVERY_MODE


次の表では、エンキュー・オプション属性を比較します。

表9-11 エンキュー・オプション属性

PL/SQLエンキュー・オプション OCIAQEnqOptions属性
visibility

OCI_ATTR_VISIBILITY

relative_msgid

OCI_ATTR_RELATIVE_MSGID

sequence_deviation

OCI_ATTR_SEQUENCE_DEVIATION

(非推奨)


次の表では、デキュー・オプション属性を比較します。

表9-12 デキュー・オプション属性

PL/SQLデキュー・オプション OCIAQDeqOptions属性
consumer_name

OCI_ATTR_CONSUMER_NAME

dequeue_mode

OCI_ATTR_DEQ_MODE

navigation

OCI_ATTR_NAVIGATION

visibility

OCI_ATTR_VISIBILITY

wait

OCI_ATTR_WAIT

msgid

OCI_ATTR_DEQ_MSGID

correlation

OCI_ATTR_CORRELATION

delivery_mode

OCI_ATTR_MSG_DELIVERY_MODE



注意:


OCIAQEnq()は、エンキュー・オプションOCI_ATTR_SEQUENCEOCI_ATTR_RELATIVE_MSGIDを指定すると、エラーORA-25219を戻します。このようになるのは、2つのメッセージをエンキューする場合です。2つ目のメッセージが最初のメッセージの前にデキューされるように、OCI_ATTR_SEQUENCEおよびOCI_ATTR_RELATIVE_MSGIDエンキュー・オプションが2つ目のメッセージに対して設定されるためです。順序を指定しない場合、エラーは戻されませんが、このメッセージは関連メッセージより前にデキューされません。

OCI_ATTR_SEQUENCE属性が設定されていない場合、OCIAQEnq()はエラーを戻しませんが、このメッセージは、関連メッセージIDを持つメッセージより前にデキューされません。


バッファ・メッセージ

バッファ・メッセージは、Streams AQ内の非永続型メッセージ機能で、Oracle Database 10gリリース2から使用可能になりました。バッファ・メッセージは、共有メモリーに格納され、インスタンスに障害が発生すると失われることがあります。永続型メッセージとは異なり、ディスクにはREDOが書き込まれません。永続型メッセージ操作より、バッファ・メッセージのエンキューとデキューの方がはるかに高速です。共有メモリーには制限があるため、バッファ・メッセージをディスクにオーバーフローさせる必要があることがあります。メッセージ・コンシューマが低速である場合や、なんらかの理由により停止した場合、フロー制御を使用可能にすることにより、アプリケーションによって共有メモリーがオーバーするのを阻止できます。バッファ・メッセージには、次の関数が使用されます。

エンキュー・バッファ・メッセージの例

...
OCIAQMsgProperties  *msgprop;
OCIAQEnqueueOptions *enqopt;
message              msg;    /* message is an object type */
null_message         nmsg;   /* message indicator */
...
/* Allocate descriptors */
  OCIDescriptorAlloc(envhp, (void **)&enqopt, OCI_DTYPE_AQENQ_OPTIONS, 0,
                     (void **)0));

 OCIDescriptorAlloc(envhp, (void **)&msgprop,OCI_DTYPE_AQMSG_PROPERTIES, 0,
                    (void **)0));

/* Set delivery mode to buffered */
 dlvm = OCI_MSG_BUFFERED;
 OCIAttrSet(enqopt,  OCI_DTYPE_AQENQ_OPTIONS, (void *)&dlvm, sizeof(ub2),
            OCI_ATTR_MSG_DELIVERY_MODE, errhp);
/* Set visibility to Immediate (visibility must always be immediate for buffered
   messages) */
vis = OCI_ENQ_ON_COMMIT;

OCIAttrSet(enqopt, OCI_DTYPE_AQENQ_OPTIONS,(void *)&vis, sizeof(ub4),
           OCI_ATTR_VISIBILITY, errhp)

/* Message was an object type created earlier, msg_tdo is its Type
   Descriptor Object */
OCIAQEnq(svchp, errhp, "Test_Queue", enqopt, msgprop, msg_tdo, (void **)&mesg,
         (void **)&nmesg, (OCIRaw **)0, 0));
...

デキュー・バッファ・メッセージの例

...
OCIAQMsgProperties  *msgprop;
OCIAQDequeueOptions *deqopt;
...
OCIDescriptorAlloc(envhp, (void **)&mprop, OCI_DTYPE_AQMSG_PROPERTIES, 0,
                   (void **)0));
OCIDescriptorAlloc(envhp, (void **)&deqopt, OCI_DTYPE_AQDEQ_OPTIONS, 0,
                   (void **)0);

/* Set visibility to Immediate (visibility must always be immediate for buffered
   message operations) */
vis = OCI_ENQ_ON_COMMIT;
OCIAttrSet(deqopt, OCI_DTYPE_AQDEQ_OPTIONS,(void *)&vis, sizeof(ub4),
           OCI_ATTR_VISIBILITY, errhp)
/* delivery mode is buffered */
dlvm  = OCI_MSG_BUFFERED;
OCIAttrSet(deqopt, OCI_DTYPE_AQDEQ_OPTIONS, (void *)&dlvm,  sizeof(ub2),
           OCI_ATTR_MSG_DELIVERY_MODE, errhp);
/* set the consumer for which to dequeue the message (this needs to be specified
   regardless of the type of message being dequeued.
*/
consumer = "FIRST_SUBSCRIBER";
OCIAttrSet(deqopt, OCI_DTYPE_AQDEQ_OPTIONS, (void *)consumer,
           (ub4)strlen((char*)consumer), OCI_ATTR_CONSUMER_NAME, errhp);
/* Dequeue the message but don't return the payload (to simplify the code
   snippet)
*/
OCIAQDeq(svchp, errhp,  "test_queue", deqopt, msgprop, msg_tdo, (void **)0,
         (void **)0, (OCIRaw**)0, 0);
...

注意:


配列操作は、バッファ・メッセージに対してはサポートされません。アプリケーションでは、配列サイズを1に設定してOCIAQEnqArray()およびOCIAQDeqArray()関数を使用できます。

OCIでのパブリッシュ・サブスクライブの通知

パブリッシュ・サブスクライブの通知機能により、OCIアプリケーションでは、クライアント通知の直接受信、通知を送信できる電子メール・アドレスの登録、通知を送信できるHTTP URLの登録、あるいは通知に対してコールされるPL/SQLプロシージャの登録を行うことができます。図9-2「パブリッシュ・サブスクライブのモデル」は、そのプロセスを示しています。

図9-2 パブリッシュ・サブスクライブのモデル

図9-2の説明は次にあります
「図9-2 公開サブスクライブのモデル」の説明

OCIアプリケーションでは次の処理を行います。

前述のすべての場合において、OCIアプリケーションでの通知の直接受信、事前に指定された電子メール・アドレスへの通知の送信、事前に定義されたHTTP URLへの通知の送信、または通知の結果として、事前に指定したデータベースPL/SQLプロシージャのコールが可能です。

登録されたクライアントは、イベントが発生した時、または明示的なAQエンキューにあるとき、非同期に通知を受け取ります。クライアントは、データベースに接続する必要はありません。


関連項目:

  • Streamsアドバンスト・キューイングの詳細は、「OCIおよびStreamsアドバンスト・キューイング」を参照してください。

  • キュー作成の詳細、Streams AQの概念、機能および例などの詳細は、『Oracle Streamsアドバンスト・キューイング・ユーザーズ・ガイドおよびリファレンス』を参照してください。

  • トリガーの作成の詳細は、『Oracle Database SQLリファレンス』の「CREATE TRIGGER」を参照してください。


OCIでのパブリッシュ・サブスクライブの登録関数

登録する方法には、次の2通りがあります。

  • 直接データベースに登録します。この方法は単純で、登録は即時に有効になります。

  • オープン登録します。データベースが登録要求を受け取るLDAPを使用して登録します。この方法は、クライアントがデータベースに接続できない場合(データベースの停止中にクライアントがデータベースのオープン・イベントに登録する場合)、またはクライアントが複数のデータベース内の同じイベントまたは複数のイベントに一度に登録する場合に役立ちます。

次の項では、これらの登録方法について説明します。

データベースへのパブリッシュ・サブスクライブの直接登録

イベント通知の登録および受信を行うには、OCIアプリケーションで次のステップを実行する必要があります。適切なイベント・トリガーまたはAQエンキューがセットアップされていることを前提としています。初期化パラメータCOMPATIBLEはリリース8.1以上に設定されている必要があります。


関連項目:


 

注意:


パブリッシュ・サブスクライブ機能は、マルチスレッド・オペレーティング・システム上でのみ使用できます。




  1. OCI_EVENTSモードでOCIInitialize()をコールし、アプリケーションで通知の登録および受信を行うことを指定します。クライアント上で、通知専用のリスニング・スレッドが起動します。

  2. ハンドル・タイプをOCI_HTYPE_SUBSCRIPTIONに指定してOCIHandleAlloc()をコールし、サブスクリプション・ハンドルを割り当てます。

  3. OCIAttrSet()をコールし、次の属性にサブスクリプション・ハンドル属性を設定します。

    • OCI_ATTR_SUBSCR_NAME− サブスクリプション名。

    • OCI_ATTR_SUBSCR_NAMESPACE− サブスクリプションのネームスペース。

    • OCI_ATTR_SUBSCR_CALLBACK− 通知コールバック。

    • OCI_ATTR_SUBSCR_CTX− コールバック・コンテキスト。

    • OCI_ATTR_SUBSCR_PAYLOAD− 送信用ペイロード・バッファ。

    • OCI_ATTR_SUBSCR_RECPT− 受信者名。

    • OCI_ATTR_SUBSCR_RECPTPROTO− 通知を受信するプロトコル。

    • OCI_ATTR_SUBSCR_RECPTPRES− 通知を受信するための表示。

    • OCI_ATTR_SUBSCR_QOSFLAGS− QOS(サービス品質)レベル。

    • OCI_ATTR_SUBSCR_TIMEOUT− 登録のタイムアウト間隔(秒単位)。タイムアウトが設定されていない場合、デフォルトは0(ゼロ)です。

    • OCI_ATTR_SUBSCR_NTFN_GROUPING_CLASS− 通知グループ化クラス。

      次の定数でNTFNグループ化オプションを使用することで、通知の間隔を置くことができます。通知グループ化クラスでサポートされる値は、次のとおりです。

      #define OCI_SUBSCR_NTFN_GROUPING_CLASS_TIME   1 /* time  */
      
    • OCI_ATTR_SUBSCR_NTFN_GROUPING_VALUE− 通知グループ化の値(秒数)。

    • OCI_ATTR_SUBSCR_NTFN_GROUPING_TYPE− 通知グループ化タイプ。

      通知グループ化タイプでサポートされる値は、次のとおりです。

      #define OCI_SUBSCR_NTFN_GROUPING_TYPE_SUMMARY 1  /* summary */
      #define OCI_SUBSCR_NTFN_GROUPING_TYPE_LAST    2  /* last */
      
    • OCI_ATTR_SUBSCR_NTFN_GROUPING_START_TIME− 通知グループ化開始時間。

    • OCI_ATTR_SUBSCR_NTFN_GROUPING_REPEAT_COUNT− 通知グループ化繰返し回数。

    • OCI_SUBSCR_QOS_PURGE_ON_NTFNを設定すると、最初の通知で登録が削除されます。

    • OCI_SUBSCR_QOS_RELIABLEを設定すると、通知は永続的になります。ノードに障害が発生した後でも、残存しているRACのインスタンスを使用して、変更通知メッセージを送受信できます。これは、この登録に関連付けられた、無効化されたメッセージが、データベースに永続的にキューとして残っているためです。FALSEである場合、無効化されたメッセージは高速メモリー内キューにエンキューされます。このオプションは、登録の永続性ではなく通知の永続性を指定するものです。デフォルトでは、登録は自動的に永続的になります。

    サブスクリプションを登録する前に、OCI_ATTR_SUBSCR_NAMEOCI_ATTR_SUBSCR_NAMESPACEおよびOCI_ATTR_SUBSCR_RECPTPROTOを設定しておく必要があります。

    OCI_ATTR_SUBSCR_RECPTPROTOOCI_SUBSCR_PROTO_OCIに設定した場合は、OCI_ATTR_SUBSCR_CALLBACKおよびOCI_ATTR_SUBSCR_CTXも設定する必要があります。

    OCI_ATTR_SUBSCR_RECPTPROTOOCI_SUBSCR_PROTO_MAILOCI_SUBSCR_PROTO_SERVERまたはOCI_SUBSCR_PROTO_HTTPに設定した場合は、OCI_ATTR_SUBSCR_RECPTも設定する必要があります。

    OCI_ATTR_SUBSCR_CALLBACKOCI_ATTR_SUBSCR_RECPTを同時に設定すると、アプリケーション・エラーが発生します。

    OCI_ATTR_SUBSCR_PAYLOADは、サブスクリプションへの送信前に設定する必要があります。


    関連項目:


    mode = OCI_EVENTS | OCI_OBJECTによる環境の設定については、「サブスクリプション・ハンドル属性」および「OCI環境の作成」を参照してください。OCI_OBJECTは通知のグループ化に必須です。

  4. QOS、タイムアウト間隔、ネームスペースおよびポートの値を設定します。

  5. OCI_ATTR_SUBSCR_RECPTPROTOOCI_SUBSCR_PROTO_OCIに設定した場合は、サブスクリプション・ハンドルで使用するコールバック・ルーチンを定義します。

  6. OCI_ATTR_SUBSCR_RECPTPROTOOCI_SUBSCR_PROTO_SERVERに設定した場合は、通知に対してコールするPL/SQLプロシージャをデータベースに定義します。

  7. OCISubscriptionRegister()をコールし、サブスクリプションを登録します。このコールにより、複数のサブスクリプションを同時に登録できます。

QOSレベルの設定例

/* Set QOS levels */
ub4 qosflags = OCI_SUBSCR_QOS_PAYLOAD;

/* Set QOS flags in subscription handle */
(void) OCIAttrSet((dvoid *) subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
                 (dvoid *) &qosflags, (ub4) 0,
                 (ub4) OCI_ATTR_SUBSCR_QOSFLAGS, errhp);

/* Set notification grouping class */
ub4 ntfn_grouping_class = OCI_SUBSCR_NTFN_GROUPING_CLASS_TIME;
(void) OCIAttrSet((dvoid *) subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
                 (dvoid *) &ntfn_grouping_class, (ub4) 0,
                 (ub4) OCI_ATTR_SUBSCR_NTFN_GROUPING_CLASS, errhp);

/* Set notification grouping value of 10 minutes */
ub4 ntfn_grouping_value = 600;
(void) OCIAttrSet((dvoid *) subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
                 (dvoid *) &ntfn_grouping_value, (ub4) 0,
                 (ub4) OCI_ATTR_SUBSCR_NTFN_GROUPING_VALUE, errhp);

/* Set notification grouping type */
ub4 ntfn_grouping_type = OCI_SUBSCR_NTFN_GROUPING_TYPE_SUMMARY;

/* Set notification grouping type in subscription handle */
(void) OCIAttrSet((dvoid *) subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
                 (dvoid *) &ntfn_grouping_type, (ub4) 0,
                 (ub4) OCI_ATTR_SUBSCR_NTFN_GROUPING_TYPE, errhp);

/* Set namespace specific context */
(void) OCIAttrSet((dvoid *) subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
                 (dvoid *) NULL, (ub4) 0,
                 (ub4) OCI_ATTR_SUBSCR_NAMESPACE_CTX, errhp);

パブリッシュ・サブスクライブのオープン登録

オープン登録の前提条件は、次のとおりです。

  • LDAP(オープン登録)を使用する登録では、クライアントがエンタープライズ・ユーザーであることが必要です。


    関連項目:


    『Oracle Database Advanced Security管理者ガイド』のエンタープライズ・ユーザー・セキュリティの管理に関する項を参照してください。

  • データベースの互換性は、リリース1(9.0.1)以上であることが必要です。

  • LDAP_REGISTRATION_ENABLEDをTRUEに設定する必要があります。次のように設定します。

     ALTER SYSTEM SET LDAP_REGISTRATION_ENABLED=TRUE
    

    デフォルトはFALSEです。

  • LDAP_REG_SYNC_INTERVALをLDAPからの登録をリフレッシュする時間隔(秒単位)に設定します。

     ALTER SYSTEM SET LDAP_REG_SYNC_INTERVAL =  time_interval
    

    デフォルトは0(ゼロ)で、この場合リフレッシュは行われません。

  • データベースでLDAPの登録情報を即時にリフレッシュするには、次のように設定します。

    ALTER SYSTEM REFRESH LDAP_REGISTRATION
    

Oracle Enterprise Security Manager(OESM)を使用したオープン登録の手順は、次のとおりです。

  1. 各エンタープライズ・ドメインで、エンタープライズ・ロールのENTERPRISE_AQ_USER_ROLEを作成します。

  2. エンタープライズ・ドメイン内の各データベースについて、グローバル・ロールのGLOBAL_AQ_USER_ROLEをエンタープライズ・ロールのENTERPRISE_AQ_USER_ROLEに追加します。

  3. 各エンタープライズ・ドメインについて、エンタープライズ・ロールのENTERPRISE_AQ_USER_ROLEを、管理コンテキストの下のcn=oraclecontextの下にある権限グループのcn=OracleDBAQUsersに追加します。

  4. データベース内のイベントへの登録が承認される各エンタープライズ・ユーザーに対して、エンタープライズ・ロールのENTERPRISE_AQ_USER_ROLEを付与します。

OCIを使用したLDAPのオープン登録

  1. modeパラメータをOCI_EVENTS | OCI_USE_LDAPに設定して、OCIInitialize()をコールします。

  2. OCIAttrSet()をコールし、次の環境ハンドル属性を設定してLDAPにアクセスします。

    • OCI_ATTR_LDAP_HOST− LDAPサーバーが常駐しているホスト名。

    • OCI_ATTR_LDAP_PORT− LDAPサーバーがリスニングしているポート。

    • OCI_ATTR_BIND_DN− LDAPサーバーにログインするための識別名。通常はエンタープライズ・ユーザーのDNです。

    • OCI_ATTR_LDAP_CRED− クライアントの認証に使用する資格証明。簡易認証(ユーザー名およびパスワード)の場合のパスワードなどです。

    • OCI_ATTR_WALL_LOC− SSL認証の場合のクライアントWalletの場所。

    • OCI_ATTR_LDAP_AUTH− 認証方式コード。


      関連項目:


      認証モードの完全なリストは、「環境ハンドル属性」を参照してください。

    • OCI_ATTR_LDAP_CTX− LDAPサーバーのOracleに対する管理コンテキスト。

  3. ハンドル・タイプをOCI_HTYPE_SUBSCRIPTIONに指定してOCIHandleAlloc()をコールし、サブスクリプション・ハンドルを割り当てます。

  4. 記述子タイプをOCI_DTYPE_SRVDNに指定してOCIDescriptorAlloc()をコールし、サーバーのDN記述子を割り当てます。

  5. OCIAttrSet()をコールし、サーバーのDN記述子属性をOCI_ATTR_SERVER_DNに設定します。これは、クライアントが通知を受信するデータベースの識別名です。OCIAttrSet()は、この属性について複数回コールできるため、登録には複数のデータベース・サーバーが含まれます。

  6. OCIAttrSet()をコールし、次の属性にサブスクリプション・ハンドル属性を設定します。

    • OCI_ATTR_SUBSCR_NAME− サブスクリプション名。

    • OCI_ATTR_SUBSCR_NAMESPACE− サブスクリプションのネームスペース。

    • OCI_ATTR_SUBSCR_CALLBACK− 通知コールバック。

    • OCI_ATTR_SUBSCR_CTX− コールバック・コンテキスト。

    • OCI_ATTR_SUBSCR_PAYLOAD− 送信用ペイロード・バッファ。

    • OCI_ATTR_SUBSCR_RECPT− 受信者名。

    • OCI_ATTR_SUBSCR_RECPTPROTO− 通知を受信するプロトコル。

    • OCI_ATTR_SUBSCR_RECPTPRES− 通知を受信するための表示。

    • OCI_ATTR_SUBSCR_QOSFLAGS− QOS(サービス品質)レベル。

    • OCI_ATTR_SUBSCR_TIMEOUT− 登録のタイムアウト間隔(秒単位)。タイムアウトが設定されていない場合、デフォルトは0(ゼロ)です。

    • OCI_ATTR_SUBSCR_SERVER_DN− ステップ5で設定した記述子ハンドル。

  7. QOS、タイムアウト間隔、ネームスペースおよびポートの値を設定します。

  8. OCISubscriptionRegister()をコールし、サブスクリプションを登録します。データベースがLDAPにアクセスして新規登録を取り出すと、登録が有効になります。取り出す頻度は、REG_SYNC_INTERVALの値によって決定されます。

QOS、タイムアウト間隔、ネームスペースおよびポート番号の設定

OCIAttrSet()を使用して、QOSFLAGSを次のQOSレベルに設定できます。

  • OCI_SUBSCR_QOS_RELIABLE− 信頼性のある通知は、インスタンスやデータベースが再起動しても持続します。信頼性とは、サーバーの信頼性のことであり、それも永続的なキューやバッファ・メッセージについての信頼性です。このオプションは、通知の永続性を記述します。デフォルトでは、登録は永続的です。

  • OCI_SUBSCR_QOS_PURGE_ON_NTFN− 受信すると、最初の通知で登録を削除します。(サブスクリプションの登録は解除されます。)

/* Set QOS levels */
ub4 qosflags = OCI_SUBSCR_QOS_RELIABLE | OCI_SUBSCR_QOS_PURGE_ON_NTFN;

/* Set flags in subscription handle */
(void)OCIAttrSet((void *)subscrhp, (ub4)OCI_HTYPE_SUBSCRIPTION,
               (void *)&qosflags, (ub4)0, (ub4)OCI_ATTR_SUBSCR_QOSFLAGS, errhp);

/* Set auto-expiration after 30 seconds */
ub4 timeout = 30;
(void)OCIAttrSet((void *)subscrhp, (ub4)OCI_HTYPE_SUBSCRIPTION,
                 (void *)&timeout, (ub4)0, (ub4)OCI_ATTR_SUBSCR_TIMEOUT, errhp);

タイムアウトを過ぎると、登録が削除され、通知がクライアントに送信されるため、クライアントはコールバックを呼び出し、必要なアクションを実行できるようになります。タイムアウトの前にクライアントに障害が発生した場合、登録は削除されます。

環境ハンドルにはポート番号を設定できます。クライアントがファイアウォールの背後のシステム上にあり、特定のポートの通知のみを受信できる場合、この番号が重要になります。クライアントは、環境ハンドルの属性を使用して、最初の登録の前にリスナー・スレッドのポートを指定できます。このスレッドは、OCISubscriptionRegister()の初回のコール時に開始されます。指定されたこのポート番号が使用可能な場合、この番号が使用されます。クライアントが異なる環境ハンドルを使用して異なるポートで別のスレッドを開始しようとすると、エラーが戻されます。

ub4 port = 1581;
(void)OCIAttrSet((void *)envhp, (ub4)OCI_HTYPE_ENV, (void *)&port, (ub4)0,
                 (ub4)OCI_ATTR_SUBSCR_PORTNO, errhp);

これとは異なりポートが自動的に決定される場合は、環境ハンドルから属性を取得することにより、クライアント・スレッドが通知をリスニングしているポート番号を取得できます。

(void)OCIAttrGet((void *)subhp, (ub4)OCI_HTYPE_ENV, (void *)&port, (ub4)0,
                 (ub4)OCI_ATTR_SUBSCR_PORTNO, errhp);

パブリッシュ・サブスクライブ通知の管理に使用するOCI関数

パブリッシュ・サブスクライブ通知の管理には、次の関数を使用します。

表9-13 パブリッシュ・サブスクライブの関数

関数 用途

OCISubscriptionDisable()


サブスクリプションを無効にします。

OCISubscriptionEnable()


サブスクリプションを有効にします。

OCISubscriptionPost()


サブスクリプションを送信します。

OCISubscriptionRegister()


サブスクリプションを登録します。

OCISubscriptionUnRegister()


サブスクリプションの登録を解除します。


OCIでの通知コールバック

クライアントは、登録したサブスクリプションに対してアクティビティが発生したときにコールされる、通知コールバックを登録する必要があります。たとえば、AQネームスペースでは、目的のメッセージがエンキューされたときに通知コールバックが発生します。

このコールバックは通常、サブスクリプション・ハンドルのOCI_ATTR_SUBSCR_CALLBACK属性によって設定されます。

通知コールバックからは、OCI_CONTINUE値が戻される必要があります。また、次の仕様に準拠している必要があります。

typedef ub4 (*OCISubscriptionNotify) ( void            *pCtx,
                                       OCISubscription *pSubscrHp,
                                       void            *pPayload,
                                       ub4             iPayloadLen,
                                       void            *pDescriptor,
                                       ub4             iMode);

パラメータは次のように記述します。

pCtx (IN)

コールバックが登録されたときに指定されたユーザー定義コンテキスト。

pSubscrHp (IN)

コールバックが登録されたときに指定されたサブスクリプション・ハンドル。

pPayload (IN)

この通知のペイロード。このリリースでは、ペイロードのub1 *(バイト・シーケンス)のみがサポートされています。

iPayloadLen (IN)

この通知のペイロードの長さ。

pDescriptor (IN)

ネームスペース固有の記述子。この記述子からネームスペース指定のパラメータを抽出できます。この記述子の構造はユーザーには不透明で、型はネームスペースによって異なります。

記述子の属性は、ネームスペース固有です。アドバンスト・キューイング(AQ)の場合、記述子はOCI_DTYPE_AQNFYです。AQネームスペースの場合、グループで受信する通知の数は通知記述子に提供されます。pDescriptorの属性は次のとおりです。

  • 通知フラグ(通常=0、タイムアウト=1、通知のグループ化=2)− OCI_ATTR_NFY_FLAGS

  • キュー名− OCI_ATTR_QUEUE_NAME

  • コンシューマ名 − OCI_ATTR_CONSUMER_NAME

  • メッセージID− OCI_ATTR_NFY_MSGID

  • メッセージ・プロパティ− OCI_ATTR_MSG_PROP

  • グループで受信する通知の数− OCI_ATTR_AQ_NTFN_GROUPING_COUNT

  • グループ(OCIコレクション)− OCI_ATTR_AQ_NTFN_GROUPING_MSGID_ARRAY

iMode (IN)

コール固有モードです。有効な値は次のとおりです。

  • OCI_DEFAULT− デフォルトのコールを実行します。

AQグループ化に関するOCI通知コールバックの例

通知コールバックにおける新規AQグループ化通知属性の使用方法の例を次に示します。

ub4 notifyCB1(void *ctx, OCISubscription *subscrhp, void *pay, ub4 payl,
              void *desc, ub4 mode)
{
 oratext            *subname;
 ub4                 size;
 OCIColl            *msgid_array = (OCIColl *)0;
 ub4                 msgid_cnt = 0;
 OCIRaw             *msgid;
 void              **msgid_ptr;
 sb4                 num_msgid = 0;
 void               *elemind = (void *)0;
 boolean             exist;
 ub2                 flags;
 oratext            *hexit = (oratext *)"0123456789ABCDEF";
 ub4                 i, j;

 /* get subscription name */
 OCIAttrGet(subscrhp, OCI_HTYPE_SUBSCRIPTION, (void *)&subname, &size,
            OCI_ATTR_SUBSCR_NAME,ctxptr->errhp);

 /* print subscripton name */
 printf("Got notification for %.*s\n", size, subname);
 fflush((FILE *)stdout);

 /* get the #ntfns received in this group */
 OCIAttrGet(desc, OCI_DTYPE_AQNFY, (void *)&msgid_cnt, &size,
            OCI_ATTR_AQ_NTFN_GROUPING_COUNT, ctxptr->errhp);

 /* get the group - collection of msgids */
 OCIAttrGet(desc, OCI_DTYPE_AQNFY, (void *)&msgid_array, &size,
            OCI_ATTR_AQ_NTFN_GROUPING_MSGID_ARRAY, ctxptr->errhp);

 /* get notification flag - regular, timeout, or grouping notification? */
 OCIAttrGet(desc, OCI_DTYPE_AQNFY, (void *)&flags, &size,
            OCI_ATTR_NFY_FLAGS, ctxptr->errhp);

 /* print notification flag */
 printf("Flag: %d\n", (int)flags);

 /* get group (collection) size */
 if (msgid_array)
   checkerr(ctxptr->errhp,
        OCICollSize(ctxptr->envhp, ctxptr->errhp,
        CONST OCIColl *) msgid_array, &num_msgid),
        "Inside notifyCB1-OCICollSize");
 else
   num_msgid =0;

 /* print group size */
 printf("Collection size: %d\n", num_msgid);

 /* print all msgids in the group */
 for(i = 0; i < num_msgid; i++)
 {
   ub4  rawSize;                                             /* raw size    */
   ub1 *rawPtr;                                              /* raw pointer */
     /* get msgid from group */
   checkerr(ctxptr->errhp,
        OCICollGetElem(ctxptr->envhp, ctxptr->errhp,
               (OCIColl *) msgid_array, i, &exist,
               (void **)(&msgid_ptr), &elemind),
        "Inside notifyCB1-OCICollGetElem");
   msgid = *msgid_ptr;
   rawSize = OCIRawSize(ctxptr->envhp, msgid);
   rawPtr = OCIRawPtr(ctxptr->envhp, msgid);

   /* print msgid size */
   printf("Msgid size: %d\n", rawSize);

   /* print msgid in hex format */
   for (j = 0; j < rawSize; j++)
   {                                           /* for each byte in the raw */
     printf("%c", hexit[(rawPtr[j] & 0xf0) >> 4]);
     printf("%c", hexit[(rawPtr[j] & 0x0f)]);
   }
   printf("\n");
 }

 /* print #ntfns received in group */
 printf("Notification Count: %d\n", msgid_cnt);
 printf("\n");
 printf("***********************************************************\n");
 fflush((FILE *)stdout);
 return 1;
}

通知プロシージャ

実行が登録されているサブスクリプションでなんらかのアクティビティが発生したときにコールするPL/SQLプロシージャは、データベース内に作成する必要があります。

このプロシージャは通常、サブスクリプション・ハンドルのOCI_ATTR_SUBSCR_RECPT属性によって設定されます。


関連項目:

  • 詳細は、「サブスクリプション・ハンドル属性」を参照してください。

  • PL/SQLプロシージャの指定の詳細は、『Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス』の「Oracle Streams AQ PL/SQLコールバック」を参照してください。


パブリッシュ・サブスクライブの直接登録の例

この例では、システム・イベント、クライアント通知およびアドバンスト・キューイングが連動して、パブリッシュ・サブスクライブ通知を実装する方法を示します。

次のPL/SQLコードでは、ユーザー・スキーマpubsub環境で公開サブスクライブ・メカニズムをサポートするために必要なすべてのオブジェクトを作成します。このコードでは、エージェントsnoopが、ログイン・イベント時にパブリッシュされるメッセージをサブスクライブします。ユーザーpubsubには、アドバンスト・キューイング機能を使用するためにAQ_ADMINISTRATOR_ROLEおよびAQ_USER_ROLEの権限が必要です。 システム・イベントのトリガーを有効にするには、初期化パラメータ_SYSTEM_TRIG_ENABLEDTRUE(デフォルト)に設定する必要があります。 pubsubとして接続してから、次のコードを実行します。

----------------------------------------------------------
----create queue table for persistent multiple consumers
----------------------------------------------------------
---- Create or replace a queue table
begin
  DBMS_AQADM.CREATE_QUEUE_TABLE(
  QUEUE_TABLE=>'pubsub.raw_msg_table',
  MULTIPLE_CONSUMERS => TRUE,
  QUEUE_PAYLOAD_TYPE =>'RAW',
  COMPATIBLE => '8.1.5');
end;
/
----------------------------------------------------------
---- Create a persistent queue for publishing messages
----------------------------------------------------------
---- Create a queue for logon events
begin
  DBMS_AQADM.CREATE_QUEUE(QUEUE_NAME=>'pubsub.logon',
  QUEUE_TABLE=>'pubsub.raw_msg_table',
  COMMENT=>'Q for error triggers');
end;
/
----------------------------------------------------------
---- Start the queue
----------------------------------------------------------
begin
  DBMS_AQADM.START_QUEUE('pubsub.logon');
end;
/
----------------------------------------------------------
---- define new_enqueue for convenience
----------------------------------------------------------
create or replace procedure new_enqueue(queue_name  in varchar2,
                                        payload  in raw ,
correlation in varchar2 := NULL,
exception_queue in varchar2 := NULL)
as
  enq_ct     dbms_aq.enqueue_options_t;
  msg_prop   dbms_aq.message_properties_t;
  enq_msgid  raw(16);
  userdata   raw(1000);
begin
  msg_prop.exception_queue := exception_queue;
  msg_prop.correlation := correlation;
  userdata := payload;
  DBMS_AQ.ENQUEUE(queue_name,enq_ct, msg_prop,userdata,enq_msgid);
end;
/
----------------------------------------------------------
---- add subscriber with rule based on current user name,
---- using correlation_id
----------------------------------------------------------
declare
subscriber sys.aq$_agent;
begin
  subscriber := sys.aq$_agent('SNOOP', null, null);
  dbms_aqadm.add_subscriber(queue_name => 'pubsub.logon',
                            subscriber => subscriber,
                            rule => 'CORRID = ''ix'' ');
end;
/
----------------------------------------------------------
---- create a trigger on logon on database
----------------------------------------------------------
---- create trigger on after logon
create or replace trigger systrig2
   AFTER LOGON
   ON DATABASE
   begin
     new_enqueue('pubsub.logon', hextoraw('9999'), dbms_standard.login_user);
   end;
/

----------------------------------------------------------
---- create a PL/SQL callback for notification of logon
---- of user 'ix' on database
----------------------------------------------------------
----
create or replace procedure plsqlnotifySnoop(
  context raw, reginfo sys.aq$_reg_info, descr sys.aq$_descriptor,
  payload raw, payloadl number)
as
begin
 dbms_output.put_line('Notification : User ix Logged on\n');
end;
/

サブスクリプションが作成された後、クライアントはコールバック関数を使用して通知を登録する必要があります。次のコード例では、登録に必要なステップを実行します。ここでは、わかりやすくするために、セッション・ハンドルの割当ておよび初期化のステップを省略しています。

...
static ub4 namespace = OCI_SUBSCR_NAMESPACE_AQ;

static OCISubscription *subscrhpSnoop = (OCISubscription *)0;
static OCISubscription *subscrhpSnoopMail = (OCISubscription *)0;
static OCISubscription *subscrhpSnoopServer = (OCISubscription *)0;

/* callback function for notification of logon of user 'ix' on database */

static ub4 notifySnoop(ctx, subscrhp, pay, payl, desc, mode)
    void *ctx;
    OCISubscription *subscrhp;
    void *pay;
    ub4 payl;
    void *desc;
    ub4 mode;
{
    printf("Notification : User ix Logged on\n");
  (void)OCIHandleFree((void *)subscrhpSnoop,
            (ub4) OCI_HTYPE_SUBSCRIPTION);
    return 1;
}

static void checkerr(errhp, status)
OCIError *errhp;
sword status;
{
  text errbuf[512];
  ub4 buflen;
  sb4 errcode;

  if (status == OCI_SUCCESS) return;

  switch (status)
  {
  case OCI_SUCCESS_WITH_INFO:
    printf("Error - OCI_SUCCESS_WITH_INFO\n");
    break;
  case OCI_NEED_DATA:
    printf("Error - OCI_NEED_DATA\n");
    break;
  case OCI_NO_DATA:
    printf("Error - OCI_NO_DATA\n");
    break;
  case OCI_ERROR:
    OCIErrorGet ((void *) errhp, (ub4) 1, (text *) NULL, &errcode,
            errbuf, (ub4) sizeof(errbuf), (ub4) OCI_HTYPE_ERROR);
    printf("Error - %s\n", errbuf);
    break;
  case OCI_INVALID_HANDLE:
    printf("Error - OCI_INVALID_HANDLE\n");
    break;
  case OCI_STILL_EXECUTING:
    printf("Error - OCI_STILL_EXECUTING\n");
    break;
  case OCI_CONTINUE:
    printf("Error - OCI_CONTINUE\n");
    break;
  default:
    printf("Error - %d\n", status);
    break;
  }
}

static void initSubscriptionHn (subscrhp,
                         subscriptionName,
                         func,
                         recpproto,
                         recpaddr,
                         recppres)
OCISubscription **subscrhp;
  char * subscriptionName;
  void * func;
  ub4 recpproto;
  char * recpaddr;
  ub4 recppres;
{
    /* allocate subscription handle */
    (void) OCIHandleAlloc((void *) envhp, (void **)subscrhp,
        (ub4) OCI_HTYPE_SUBSCRIPTION,
        (size_t) 0, (void **) 0);

    /* set subscription name in handle */
    (void) OCIAttrSet((void *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
        (void *) subscriptionName,
        (ub4) strlen((char *)subscriptionName),
        (ub4) OCI_ATTR_SUBSCR_NAME, errhp);

    /* set callback function in handle */
    if (func)
      (void) OCIAttrSet((void *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
          (void *) func, (ub4) 0,
          (ub4) OCI_ATTR_SUBSCR_CALLBACK, errhp);

    /* set context in handle */
    (void) OCIAttrSet((void *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
        (void *) 0, (ub4) 0,
       (ub4) OCI_ATTR_SUBSCR_CTX, errhp);

    /* set namespace in handle */
    (void) OCIAttrSet((void *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
        (void *) &namespace, (ub4) 0,
        (ub4) OCI_ATTR_SUBSCR_NAMESPACE, errhp);

    /* set receive with protocol in handle */
    (void) OCIAttrSet((void *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
        (void *) &recpproto, (ub4) 0,
        (ub4) OCI_ATTR_SUBSCR_RECPTPROTO, errhp);

    /* set recipient address in handle */
    if (recpaddr)
      (void) OCIAttrSet((void *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
          (void *) recpaddr, (ub4) strlen(recpaddr),
          (ub4) OCI_ATTR_SUBSCR_RECPT, errhp);

    /* set receive with presentation in handle */
    (void) OCIAttrSet((void *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
        (void *) &recppres, (ub4) 0,
        (ub4) OCI_ATTR_SUBSCR_RECPTPRES, errhp);

    printf("Begining Registration for subscription %s\n", subscriptionName);
    checkerr(errhp, OCISubscriptionRegister(svchp, subscrhp, 1, errhp,
        OCI_DEFAULT));
   printf("done\n");

}


int main( argc, argv)
int    argc;
char * argv[];
{
    OCISession *authp = (OCISession *) 0;

/*****************************************************
Initialize OCI Process/Environment
Initialize Server Contexts
Connect to Server
Set Service Context
******************************************************/

/* Registration Code Begins */
/* Each call to initSubscriptionHn allocates
   and Initialises a Registration Handle */

/* Register for OCI notification */
    initSubscriptionHn(    &subscrhpSnoop,    /* subscription handle*/
    (char*) "PUBSUB.LOGON:SNOOP", /* subscription name */
                                  /*<queue_name>:<agent_name> */
        (void*)notifySnoop,  /* callback function */
        OCI_SUBSCR_PROTO_OCI, /* receive with protocol */
        (char *)0, /* recipient address */
        OCI_SUBSCR_PRES_DEFAULT); /* receive with presentation */

/* Register for email notification */
    initSubscriptionHn(    &subscrhpSnoopMail,  /* subscription handle */
     (char*) "PUBSUB.LOGON:SNOOP",              /* subscription name */
                                                /* <queue_name>:<agent_name> */
        (void*)0, /* callback function */
        OCI_SUBSCR_PROTO_MAIL, /* receive with protocol */
        (char*)  "longying.zhao@oracle.com", /* recipient address */
        OCI_SUBSCR_PRES_DEFAULT); /* receive with presentation */

/* Register for server to server notification */
    initSubscriptionHn(    &subscrhpSnoopServer, /* subscription handle */
       (char*)  "PUBSUB.LOGON:SNOOP",            /* subscription name */
                                                 /* <queue_name>:<agent_name> */
        (void*)0, /* callback function */
        OCI_SUBSCR_PROTO_SERVER, /* receive with protocol */
         (char*) "pubsub.plsqlnotifySnoop", /* recipient address */
        OCI_SUBSCR_PRES_DEFAULT); /* receive with presentation */

    checkerr(errhp, OCITransCommit(svchp, errhp, (ub4) OCI_DEFAULT));

/*****************************************************
The Client Process does not need a live Session for Callbacks
End Session and Detach from Server
******************************************************/

    OCISessionEnd ( svchp,  errhp, authp, (ub4) OCI_DEFAULT);

    /* detach from server */
    OCIServerDetach( srvhp, errhp, OCI_DEFAULT);

   while (1)    /* wait for callback */
    sleep(1);
}

ユーザーIXがデータベースにログインした場合、クライアントは電子メールで通知を受け取り、コールバック関数notifySnoopがコールされます。電子メール通知は、アドレスxyz@company.comに送信され、PL/SQLプロシージャplsqlnotifySnoopもデータベースでコールされます。

パブリッシュ・サブスクライブのLDAP登録の例

次のコード・フラグメントでは、LDAP登録の方法を示します。プログラム・コメントはすべて目を通してください。

...

  /* TO use LDAP registration feature, OCI_EVENTS | OCI_EVENTS |OCI_USE_LDAP must
  /*   be set in OCIInitialize: */
  (void) OCIInitialize((ub4) OCI_EVENTS|OCI_OBJECT|OCI_USE_LDAP, (void *)0,
                       (void * (*)(void *, size_t)) 0,
                       (void * (*)(void *, void *, size_t))0,
                       (void (*)(void *, void *)) 0 );

...

  /* set LDAP attributes in the environment handle */

  /* LDAP host name */
  (void) OCIAttrSet((void *)envhp, OCI_HTYPE_ENV, (void *)"yow", 3,
                    OCI_ATTR_LDAP_HOST, (OCIError *)errhp);

  /* LDAP server port */
  ldap_port = 389;
  (void) OCIAttrSet((void *)envhp, OCI_HTYPE_ENV, (void *)&ldap_port,
                    (ub4)0, OCI_ATTR_LDAP_PORT, (OCIError *)errhp);

  /* bind DN of the client, normally the enterprise user name */
  (void) OCIAttrSet((void *)envhp, OCI_HTYPE_ENV, (void *)"cn=orcladmin",
                    12, OCI_ATTR_BIND_DN, (OCIError *)errhp);

  /* password of the client */
  (void) OCIAttrSet((void *)envhp, OCI_HTYPE_ENV, (void *)"welcome",
                    7, OCI_ATTR_LDAP_CRED, (OCIError *)errhp);

  /* authentication method is "simple", username/password authentication */
  ldap_auth = 0x01;
  (void) OCIAttrSet((void *)envhp, OCI_HTYPE_ENV, (void *)&ldap_auth,
                    (ub4)0, OCI_ATTR_LDAP_AUTH, (OCIError *)errhp);

  /* adminstrative context: this is the DN above cn=oraclecontext */
  (void) OCIAttrSet((void *)envhp, OCI_HTYPE_ENV, (void *)"cn=acme,cn=com",
                    14, OCI_ATTR_LDAP_CTX, (OCIError *)errhp);

...

  /* retrieve the LDAP attributes from the environment handle */

  /* LDAP host */
  (void) OCIAttrGet((void *)envhp, OCI_HTYPE_ENV, (void *)&buf,
                    &szp,  OCI_ATTR_LDAP_HOST,  (OCIError *)errhp);

  /* LDAP server port */
  (void) OCIAttrGet((void *)envhp, OCI_HTYPE_ENV, (void *)&intval,
                    0,  OCI_ATTR_LDAP_PORT,  (OCIError *)errhp);

  /* client binding DN */
  (void) OCIAttrGet((void *)envhp, OCI_HTYPE_ENV, (void *)&buf,
                    &szp,  OCI_ATTR_BIND_DN,  (OCIError *)errhp);

  /* client password */
  (void) OCIAttrGet((void *)envhp, OCI_HTYPE_ENV, (void *)&buf,
                    &szp,  OCI_ATTR_LDAP_CRED,  (OCIError *)errhp);

  /* adminstrative context */
  (void) OCIAttrGet((void *)envhp, OCI_HTYPE_ENV, (void *)&buf,
                    &szp,  OCI_ATTR_LDAP_CTX,  (OCIError *)errhp);

  /* client authentication method */
  (void) OCIAttrGet((void *)envhp, OCI_HTYPE_ENV, (void *)&intval,
                    0,  OCI_ATTR_LDAP_AUTH,  (OCIError *)errhp);

  ...

  /* to set up the server DN descriptor in the subscription handle */

  /* allocate a server DN descriptor, dn is of type "OCIServerDNs **",
     subhp is of type "OCISubscription **" */
  (void) OCIDescriptorAlloc((void *)envhp, (void **)dn,
                         (ub4) OCI_DTYPE_SRVDN, (size_t)0, (void **)0);

  /* now *dn is the server DN descriptor, add the DN of the first database
     that we want to register */
  (void) OCIAttrSet((void *)*dn, (ub4) OCI_DTYPE_SRVDN,
                    (void *)"cn=server1,cn=oraclecontext,cn=acme,cn=com",
                    42, (ub4)OCI_ATTR_SERVER_DN, errhp);
  /* add the DN of another database in the descriptor */
  (void) OCIAttrSet((void *)*dn, (ub4) OCI_DTYPE_SRVDN,
                    (void *)"cn=server2,cn=oraclecontext,cn=acme,cn=com",
                    42, (ub4)OCI_ATTR_SERVER_DN, errhp);

  /* set the server DN descriptor into the subscription handle */
  (void) OCIAttrSet((void *) *subhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
                 (void *) *dn, (ub4)0, (ub4) OCI_ATTR_SERVER_DNS, errhp);

  ...

  /* now we will try to get the server DN information from the subscription
     handle */

  /* first, get the server DN descriptor out */
  (void) OCIAttrGet((void *) *subhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
                    (void *)dn, &szp, OCI_ATTR_SERVER_DNS, errhp);

  /* then, get the number of server DNs in the descriptor */
  (void) OCIAttrGet((void *) *dn, (ub4)OCI_DTYPE_SRVDN, (void *)&intval,
                    &szp, (ub4)OCI_ATTR_DN_COUNT, errhp);

  /* allocate an array of char * to hold server DN pointers returned by
     oracle */
    if (intval)
    {
      arr = (char **)malloc(intval*sizeof(char *));
      (void) OCIAttrGet((void *)*dn, (ub4)OCI_DTYPE_SRVDN, (void *)arr,
                        &intval, (ub4)OCI_ATTR_SERVER_DN, errhp);
    }

  /* OCISubscriptionRegister() calls have two modes: OCI_DEFAULT and
     OCI_REG_LDAPONLY. If OCI_DEFAULT is used, there should be only one
     server DN in the server DN descriptor. The registration request will
     be sent to the database. If a database connection is not available,
     the registration request will be detoured to the LDAP server. On the
     other hand, if mode OCI_REG_LDAPONLY is used the registration request
     will be directly sent to LDAP. This mode should be used when there are
     more than one server DNs in the server DN descriptor, or we are sure
     that a database connection is not available.

     In this example, two DNs are entered; so we should use mode
     OCI_REG_LDAPONLY in register. */
  OCISubscriptionRegister(svchp, subhp, 1, errhp, OCI_REG_LDAPONLY);

  ...

  /* as OCISubscriptionRegister(), OCISubscriptionUnregister() also has
     mode OCI_DEFAULT and OCI_REG_LDAPONLY. The usage is the same. */

  OCISubscriptionUnRegister(svchp, *subhp, errhp, OCI_REG_LDAPONLY);
}
...