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

高度なOCIプログラミング機能について説明します。

この章には次のトピックが含まれます:

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

セッション・プーリングとは、アプリケーションがデータベースに対するステートレス・セッションのグループを作成してメンテナンスすることを意味します。

これらのセッションは、要求に応じてThinクライアントに提供されます。使用可能なセッションがない場合は、新しいセッションが作成されます。セッションでのクライアントの処理が完了すると、クライアントはプールに対してセッションを解放します。したがって、プール内のセッション数は動的に増加する場合があります。

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

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

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

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

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

セッション・プーリングを実行できるタスクについて説明します。

セッション・プーリングにより次のタスクを実行できます。

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

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

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

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

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

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

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

セッション・プールは、同種または異種のいずれかになります。

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

セッション・プーリングでのタグの使用について

ユーザーは、タグを使用して、プール内のセッションをカスタマイズできます。

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

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

この項には次のトピックが含まれます: 複数プロパティ・タグ

複数プロパティ・タグ

12cリリース2 (12.2)以上では、タグに複数のプロパティを含めることができます。これは、複数プロパティ・タグと呼ばれます。

複数プロパティ・タグは、セミコロンで区切られた<property-name>=<property-value>の1つ以上のペアで構成されます(<property-name>=<property-value>は両方とも文字列です)。

OCISessionGet()コールの実行中に、taginfoパラメータで最初に出現するプロパティ名は、一致を検索するための最も高い優先順位を付与され、最後に出現するプロパティ名は、最も低い優先順位を付与されます。したがって、文字列内のプロパティの順序は、プール内の一致するセッションを決定する際に重要です。制限のリストの次に続く例は、この注意点を示しています。

この機能は、DRCPでも動作します。

複数プロパティ・タグで渡すことのできるプロパティ名およびプロパティ値には、次の制限が適用されます。
  • プロパティ名とプロパティ値は、どちらも大/小文字が区別されます。

  • プロパティ名は、タグ内で1回のみ使用できます。同じプロパティ名が2回以上指定されると、エラーがスローされます。

  • プロパティ名と値には、どちらも空以外の文字列を指定する必要があります。

  • プロパティ名の前後にある先頭および末尾の空白と、プロパティ値の前後にある先頭および末尾の空白は、切り捨てられます。たとえば、"PDB = PDB1"は"PDB=PDB1"として処理されます。

  • プロパティ名とプロパティ値に空白を挿入することはできません。たとえば、NLS <空白> LANGUAGE=Frenchは、NLSとLANGUAGEの間に空白があるため、エラーとなります。

複数のプロパティの概念を説明するため、CDB環境にデプロイされたアプリケーションで、セッション取得要求を可能なかぎり同じプラガブル・データベース(pdb1など)からのセッションで満足させる必要があるとします。さらに、セッションは同じ言語(FRENCHなど)に属している必要がありますが、セッションに対するより高い優先順位をpdb1に付与します。アプリケーションでは、次のように複数プロパティ・タグを指定できます。

char *props  = “PDB=pdb1;LANGUAGE=FRENCH”

ここで、次のようなプロパティを持つ2つのセッションがプール内にあるとします。

Session 1 = >  “PDB=pdb1;LANGUAGE=CHINESE”
Session 2 = >  “PDB=pdb2;LANGUAGE=FRENCH” 

この場合、PDBプロパティはLANGUAGEプロパティより前に配置されることで、暗黙的により高い優先順位となるため、セッション取得要求(OCISessionGet())は、セッション1を戻します。

関連項目:

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

この項には次のトピックが含まれます: セッション状態の修正のためのPL/SQLコールバック

セッション状態の修正のためのPL/SQLコールバック

複数プロパティ・タグを使用する場合、セッション状態のPL/SQLベースの修正コールバックをサーバーで提供できます。

このアプリケーションで提供されたコールバックは、複数プロパティ・タグで指定されたとおりに、プールからチェックアウトされたセッションをアプリケーションで要求された必要な状態に変換します。このコールバックは、データベース常駐接続プーリング(DRCP)があってもなくても動作します。

このコールバックを使用すると、修正ロジックがサーバー上のセッション状態に対して実行されるため、アプリケーションのパフォーマンスを改善できます。したがって、修正ロジックのために、この機能によりデータベースへのアプリケーションのラウンドトリップがなくなります。コールバックは、OCISessionGet()を使用して接続しているユーザーによって提供されます。コールバックは、OCISessionPoolを使用しないアプリケーションまたはカスタム・プールを使用するアプリケーションのOCISessionGet()に渡される認証ハンドルの属性OCI_ATTR_FIXUP_CALLBACKとして指定する必要があります。OCISessionPoolを使用するアプリケーションでは、この属性は、認証ハンドルで設定し、次にそれを属性OCI_ATTR_SPOOL_AUTHとしてセッション・プール・ハンドルで設定する必要があります。

例10-1 PL/SQLの修正コールバックの例

次に、セッション・プロパティSCHEMAおよびCURRENCYを修正するためのPL/SQL修正コールバックの実装例を示します。

CREATE OR REPLACE PACKAGE mycb_pack AS
  TYPE prop_t IS TABLE OF varchar2(64) index by varchar2(64);
PROCEDURE mycallback (
  desired_props IN VARCHAR2,
  actual_props IN VARCHAR2
);
PROCEDURE buildTab(propTab in out prop_t, props in varchar2);
END;
/

CREATE OR REPLACE PACKAGE BODY mycb_pack AS
  procedure buildTab(propTab in out prop_t, props in varchar2 ) is
    property VARCHAR2(64);
    key VARCHAR2(64);
    value VARCHAR2(64);
    pos number;
    pos2 number;
    pos3 number;
    idx1 number;
  begin
    idx1:=1;

    pos:=1;
    pos2:=1;
    pos3:=1;
    property := 'tmp';

    while (pos > 0 and length(props)>pos)
    loop
      pos := instr (props, ';', 1, idx1);
      if (pos=0)
      then
        property := substr (props, pos2);
      else
        property := substr (props, pos2, pos-pos2);
      end if
      pos2 := pos+1;
      pos3 := instr (property, '=', 1, 1);
      key := substr (property, 1, pos3-1);
      value := substr (property, pos3+1);
      propTab(key) := value;
      idx1 := idx1+1;
    end loop;
  end;

  PROCEDURE mycallback (
  desired_props IN VARCHAR2,
  actual_props IN VARCHAR2
  ) IS

  actPropTab prop_t;
  desPropTab prop_t;
  idx varchar2(64);
  begin
    -- iterate through the desired props to set the session state appropriately
idx := desPropTab.first;
    while (idx is not null)
    loop
      if (idx = 'CURRENCY') then
         EXECUTE IMMEDIATE 'ALTER SESSION SET NLS_CURRENCY=''' || desPropTab(idx) || '''';
      elsif (idx = 'SCHEMA') then
         EXECUTE IMMEDIATE 'ALTER SESSION SET CURRENT_SCHEMA=''' || desPropTab(idx) || '''';
      end if;
      idx := desPropTab.next(idx);
    end loop;
    -- may iterate over the actual props to set any extra props to a default state
  end;
end mycb_pack;
/

関連項目:

OCI_ATTR_FIXUP_CALLBACKの詳細は、「認証情報ハンドル属性」を参照してください

OCI_ATTR_SPOOL_AUTHの詳細は、「セッション・プール・ハンドル属性」を参照してください

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

セッション・プーリング用のハンドル・タイプについて説明します。

セッション・プーリング用のハンドル・タイプは次のとおりです。
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です(表3-1を参照)。

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

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

関連項目:

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

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

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

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

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

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

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

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

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

    4. トランザクションを実行します

    5. ハンドルを割り当てます

    6. 文を準備します

      ノート:

      OCIセッション・プールから取得したサービス・コンテキストを使用する場合、OCISessionGet() (またはOCILogon2())によって戻されたサービス・コンテキストを使用する必要があり、これらのコールの外で他のサービス・コンテキストを作成する必要はありません。

      サービス・コンテキストを指定したOCIStmtPrepare2()を使用して取得した文ハンドルは、後で同じサービス・コンテキストとの組合せでのみ使用でき、異なるサービス・コンテキストとは使用できません。

    7. 文を実行します

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

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

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

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

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

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

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

OCIには、次のタスクを実行するためにセッション・プーリングのコールが用意されています。

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

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

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

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

関連項目:

OCIHandleAlloc()

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

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);

関連項目:

OCISessionPoolCreate()

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

次のコールを使用して、セッション・プーリング・モードでデータベースにログインできます。

  • 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);
    

    OCIセッション・プールから取得したサービス・コンテキストを使用する場合、OCISessionGet() (またはOCILogon2())によって戻されたサービス・コンテキストを使用する必要があり、これらのコールの外で他のサービス・コンテキストを作成する必要はありません。

    サービス・コンテキストを指定したOCIStmtPrepare2()を使用して取得した文ハンドルは、後で同じサービス・コンテキストとの組合せでのみ使用でき、異なるサービス・コンテキストとは使用できません。

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

セッション・プーリング・モードでデータベースからログオフする際に使用する2通りの方法を示します。いずれの方法を使用するかはログオン・コールによって決まります。

次のコールから、ログイン・コールに対応するものを選択し、それを使用してセッション・プーリング・モードでデータベースからログオフします。

  • OCILogoff()

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

  • OCISessionRelease()

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

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

OCISessionPoolDestroy()をコールして、セッション・プールを破棄できます。

これを次の例に示します。

OCISessionPoolDestroy(poolhp, errhp, OCI_DEFAULT);

関連項目:

OCISessionPoolDestroy()

プール・ハンドルの解放

OCIHandleFree()をコールして、セッション・プール・ハンドルを解放できます。

これを次の例に示します。

OCIHandleFree((void *)poolhp, OCI_HTYPE_SPOOL);

ノート:

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

セッション・プールの使用中にインスタンス障害が検出されると、OCIではそのインスタンスへのセッションのクリーン・アップを試みます。

関連項目:

OCIHandleFree()

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

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

テスト済の完成プログラムでのセッション・プーリングの例は、demoディレクトリのcdemosp.cを参照してください。

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

ランタイム接続ロード・バランシングは、セッション・プール内で作業の処理に最も適切なセッションに作業リクエストをルーティングします。

これは既存のセッション・プールからセッションを選択する際に発生するため、頻度の高いアクティビティです。単一インスタンスでのみサービスをサポートするセッション・プールの場合は、プール内で使用可能な最初のセッションで十分です。プールが複数インスタンスにまたがるサービスをサポートしている場合は、適切なサービスを提供できる、または容量の大きいインスタンスが多数のリクエストを取得するように、作業リクエストをインスタンス間で分散する必要があります。

実行時接続ロード・バランシングを有効化するには、アプリケーションはOracle RACインスタンスに接続する必要があります。また、これらのアプリケーションで次を行う必要があります。

  • OCI_EVENTSモードでのOCI環境の初期化

  • 実行時接続ロード・バランシングが有効なサービスに接続します(DBMS_SERVICE.MODIFY_SERVICEプロシージャを使用してGOALおよびCLB_GOALの適切な値を設定します)

  • スレッド・ライブラリとのリンク

関連項目:

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

データベース常駐接続プーリング(DRCP)は、アプリケーションがデータベース接続を取得し、この接続で比較的短い時間動作してから接続を解放する一般的なWebアプリケーションの使用シナリオに対し、データベース・サーバーの接続プールを提供します。

DRCPではサーバー・プロセスをプーリングされ、そのそれぞれは、専用のサーバー・プロセスとデータベース・セッションの組合せと同等のものです。(これからは、これら「専用」サーバー・プロセスをプール・サーバーと呼びます。)

DRCPは、中間層プロセス内のスレッド間で接続を共有する中間層接続プールを補完します。また、DRCPを使用すると、同じ中間層ホスト上の中間層プロセス間、および異なる中間層ホスト上の中間層プロセス間でもデータベース接続を共有できます。この結果、大量のクライアント接続をサポートするために必要となる基本データベース・リソースが大幅に減少するため、データベース層のメモリー・フットプリントが縮小し、中間層とデータベース層の両方のスケーラビリティが向上します。すぐに使用可能なサーバーのプールが提供されていると、クライアント接続の作成および分割にかかるコストを削減できるというメリットがあります。

DRCPは、中間層接続プーリングを実行できないマルチプロセス・シングル・スレッド・アプリケーション・サーバー(PHP/Apacheなど)を含むアーキテクチャに特に適しています。DRCPを使用すると、データベースは同時接続数を数万にまで増やすことができます。

関連項目:

DRCPの詳細は、『Oracle Database開発ガイド』を参照してください

OCIでの接続プーリング

接続プーリングとは、ロードを均衡化するために、再使用可能な物理接続のグループ(プール)を複数のセッションで使用することです。

プールは、アプリケーションではなく、OCIにより管理されます。Webアプリケーション・サーバーおよび電子メール・サーバーの中間層アプリケーションでも接続プーリングを使用できます。

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

OCI接続プーリングの概念

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

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

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

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

中間層での接続プーリングは、共有サーバーがバックエンドで提供する機能に似ています。

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

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

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

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

ステートレス・セッションは、中間層スレッド間で逐次再利用可能です。

ある3層ユーザーのためにスレッドでデータベース要求を処理した後で、異なる3層ユーザーの異なる要求のために同じデータベース・セッションを再利用できます。

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

ノート:

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

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

接続プーリングによって、ステートレス接続とステートフル・セッションが提供されます。

関連項目:

ステートレス・セッションを操作する必要がある場合は、「OCIでのセッション・プーリング」を参照してください

複数の接続プール

複数の接続プールの拡張概念を、様々なデータベース接続に使用できます。

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

図10-1は、OCI接続プーリングを示しています。

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

図10-1の説明が続きます
「図10-1 OCI接続プーリング」の説明
透過的アプリケーション・フェイルオーバー

接続プーリングでは、透過的アプリケーション・フェイルオーバー(TAF)が使用可能になっています。

BACKUPおよびPRECONNECT句が接続文字列では使用できず接続プーリングとTAFでは機能しない点を除いて、TAFの概念は、接続プール内の各接続に等しく適用されます。

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

関連項目:

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

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

アプリケーションで接続プーリングを使用する場合に実行する必要があるステップを示しています。

アプリケーションで接続プーリングを使用するには、次のことが必要です。

  1. プール・ハンドルの割当て
  2. 接続プールの作成
  3. データベースへのログイン
  4. 接続プーリングでのSGA制限の処理
  5. データベースからのログオフ
  6. 接続プールの破棄
  7. プール・ハンドルの解放
プール・ハンドルの割当て

接続プーリングでは、OCIHandleAlloc()によってプール・ハンドルOCI_HTYPE_CPOOLを割り当てる必要があります。

指定された環境ハンドルに対して複数のプールを作成できます。

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

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

関連項目:

OCIHandleAlloc()

接続プールの作成

OCIConnectionPoolCreate()関数は、接続プール・ハンドルを初期化します。

次のINパラメータがあります。

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

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

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

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

  • さらに、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) connMin, (ub4) connMax, (ub4) connIncr,
                   (text *)poolUsername,strlen(poolUserLen),
                   (text *)poolPassword,strlen(poolPassLen),
                   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()

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

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

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

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

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

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

      資格証明は、OCISessionBegin()を使用してOCI_CRED_RDBMSOCI_CRED_EXTまたはOCI_CRED_PROXYに設定できます。資格証明をOCI_CRED_EXTに設定すると、セッション・ハンドルの設定ではユーザー名もパスワードも必要ありません。資格証明をOCI_CRED_PROXYに設定すると、セッション・ハンドルの設定ではユーザー名のみ必要です (1次セッションを明示的に作成したり、OCI_ATTR_MIGSESSIONを設定する必要はありません)。

      仮想サーバー・ハンドルに接続プールを指定する(modeOCI_CPOOLに設定してOCIServerAttach()をコールする)場合は、OCISessionBegin()へのコールでOCI_MIGRATEフラグを設定しないでください。互換性上の理由でのみ、このOCI_MIGRATEフラグを渡すことをお薦めします。接続プールを使用するときに、ユーザーには、セッションに独自の専用(仮想)接続が確立されているように感じられますが、この接続は実際の接続に対して透過的に多重化されているだけのため、OCI_MIGRATEフラグは使用しないでください。

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

OCI_CPOOLモード(接続プーリング)では、バックエンド・データベースのセッション・メモリー(UGA)はSGAから取得されます。

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

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

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

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

  • それ以外の場合は、接続プーリングをオフにします。

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

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

関連項目:

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

適切なコールを選択して、接続プーリング・モードでデータベースからログオフします。

次のコールから、ログイン・コールに対応するものを選択し、それを使用して接続プーリング・モードでデータベースからログオフします。

  • OCILogoff():

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

  • OCISessionRelease()

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

  • OCISessionEnd()およびOCIServerDetach()

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

接続プールの破棄

OCIConnectionPoolDestroy()によって破棄します。

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接続プーリングの例

テスト済の完全なプログラムでの接続プーリングの例を参照できる場所を示します。

テスト済の完全なプログラムでの接続プーリングの例は、ディレクトリdemocdemocp.cおよびcdemocpproxy.cにあります。

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

これらを使用する場合と使用しない場合を示します。

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

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

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

ノート:

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

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

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

接続プーリングおよびセッション・プーリングに関する次の追加情報に注意してください。

  • OCI接続プーリング

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

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

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

  • OCIセッション・プーリング

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

セッション作成用の関数

セッションの作成には、様々な機能を使用した、いくつかの方法があります。

OCIでは、セッション作成用の次の関数を提供しています。

  • OCILogin

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

  • OCILogon2()

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

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

  • OCISessionBegin()

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

  • OCISessionGet()

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

様々なタイプのOCIセッションの選択について

使用するセッションのタイプを選択する方法について説明します。

OCIには、次の種類のセッションがあります。

  • 基本的なOCIセッション

    基本的なOCIセッションは、専用OCIサーバー・ハンドル上でユーザー名およびパスワードを使用することで機能します。これは、プール・メカニズムではありません。使用するタイミングの詳細は、「接続プーリングまたはセッション・プーリングの使用/不使用の判断」を参照してください。

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

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

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

    接続プール・セッション、セッション・プーリング・セッション、非プーリング・セッションの比較については、「接続プーリングまたはセッション・プーリングの使用/不使用の判断」を参照してください。

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

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

    セッションは、接続プールのサーバー・ハンドル間で自動的に移行できます。各セッションにユーザー名およびパスワードを含めたり、各セッションをプロキシ・セッションにすることができます。接続プール・セッション、セッション・プーリング・セッション、非プーリング・セッションの比較については、「接続プーリングまたはセッション・プーリングの使用/不使用の判断」を参照してください。

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

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

  • プロキシ・セッション

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

  • 移行可能なセッション

移行可能なトランザクション・ハンドルを使用している場合、アプリケーションで移行可能なセッションを使用する必要はなく、かわりにOCI接続プーリングを使用します。

関連項目:

OCIでの文キャッシュ

文キャッシュは、セッションごとの文のキャッシュを提供および管理する機能です。

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

  • OCIStmtPrepare2()

  • OCIStmtRelease()

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

セッション・プーリングを使用せずに文のキャッシングを実行する場合、ユーザーは通常のOCIステップを実行してログオンします。

セッションを取得するコールには、セッションにおいて文のキャッシングが有効であるかどうかを指定するモードがあります。最初、文キャッシュは空です。開発者は、文テキストを使用してキャッシュ内で文を見つけようとします。文が存在する場合、APIは以前に準備した文ハンドルを戻しますが、存在しない場合は、新しく準備した文ハンドルを戻します。

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

OCIStmtPrepare2()には、キャッシュ内に文が見つからない場合に、開発者がプリペアド文を使用するのか、または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, ...);
OCIStmtFetch2(svchp, ...);
OCIStmtRelease(stmthp, ...);
...

関連項目:

OCIStmtPrepare2()

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

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

属性OCI_ATTR_SPOOL_STMTCACHESIZEにより、セッション・プール内のセッションごとにデフォルトの文キャッシュ・サイズが設定されます。これは、OCI_HTYPE_SPOOLハンドルで設定されます。プールの特定のセッションの文キャッシュ・サイズは、そのセッションでOCI_ATTR_STMTCACHESIZEを使用することでいつでも上書きできます。OCI_ATTR_SPOOL_STMTCACHESIZEの値は、いつでも変更できます。この属性を使用して、プール・レベルで文のキャッシングを有効または無効にできます。作成後、それと同時に、サービス・コンテキストで属性OCI_ATTR_STMTCACHESIZEを使用して、セッション・レベルで文のキャッシングを有効または無効にできます。この変更内容は、プール内の個別のセッションがユーザーに提供された際に反映されます。タグ付きセッションでは、この動作は当てはまりません。これについては、項の後半で説明します。

ノート:

属性は、セッションの取得後に変更できます。ただし、いったん属性を変更すると、基本の物理セッションで設定されたままになります。この値は、セッションが解放されてセッション・プールに戻される間、暗黙的に元の値にリセットされることはありません。したがって、OCIStmtRelease()を使用してセッションを解放するまで、セッションの状態を維持するのは開発者の責任です。

文キャッシュの有効化または無効化は、プーリングされていないセッションと同様に、各プーリング・セッションで許可されます。

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

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

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

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

文キャッシュを使用する場合は、これらの規則に従います。

OCIでの文キャッシュには、守るべきいくつかの規則があります。

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

  • セッションごとに1つのサービス・コンテキストを保持する必要があります。サービス・コンテキストを指定したOCIStmtPrepare2()を使用して取得した文ハンドルは、後で同じサービス・コンテキストとの組合せでのみ使用でき、異なるサービス・コンテキストとは使用できません。

  • 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つの文がタグのみ異なる場合、または、一方がタグ付きでもう一方がタグなしの場合、これらの文は異なる文とみなされます。

文キャッシュでのバインドおよび定義の最適化

アプリケーションがキャッシュ内の文でバインドおよび定義操作を繰り返すのを回避するために、アプリケーションは、文キャッシュから取得した文で不透明なコンテキストを登録し、サービス・コンテキストでコールバック関数を登録できます。

バインドおよび定義バッファなどのアプリケーション・データは、不透明なコンテキストに包含することができます。このコンテキストは、最初にキャッシュから取得された文で登録されます。文が2回目以降キャッシュから取得された際には、アプリケーションはバインドおよび定義バッファを再利用でき、コンテキストはその文で登録されます。アプリケーションにはまだ、バインドおよび定義を管理する責任があります。さらに、バインドと定義両方のデータとバッファを再利用するか、データのみを変更してバッファを再利用するか、または現在のサイズが十分でない場合は、バッファを解放して再割当てできます。最後のケースでは、再バインドと再定義を行う必要があります。これらのバインドおよび定義バッファに対してアプリケーションが割り当てたメモリーをクリーンアップするには、セッションを閉じる一環として文をエージ・アウトするかまたは全キャッシュを消去する際に、コールバック関数をコールします。消去するすべての文に対してコールバック関数をコールします。コールバック関数の内部で、アプリケーションはメモリーを解放して、必要なその他のクリーンアップを実行します。例10-2に疑似コードを示します。

例10-2 キャッシュ内の文に対するバインドおよび定義操作の最適化

Get the statement using OCIStmtPrepare2(...)
 
Get the opaque context from the statement if it exists
 
If opaque context does not exist
 
{
 
  Allocate fetch buffers, do the OCIBindByPos, OCIDefineByPos, and so forth
 
  Enclose the buffer addresses inside a context and set the context and
  callback function on the statement
 
}
Execute/Fetch using the statement, and process the data in the fetch buffers. 
 
OCIStmtRelease() that statement
 
Next OCIStmtPrepare2()
 
OCIAttrGet() opaque application context from statement handle
 
Execute/Fetch using the statement and process the data in the fetch buffers.
 
OCIStmtRelease()
 
. . .
 
void callback_fn (context, statement, mode)
 
{
 
   /* mode= OCI_CBK_STMTCACHE_STMTPURGE means this was called when statement was
      aging out of the statement cache or if the session is ended */
 
  <free the buffers in the context.>
 
}

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

文キャッシュの作業例を参照できる場所を示します。

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

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

Oracle Call Interfaceでは、OCIコールの他に、ユーザー固有のコードを実行できます。

この機能は次のことに使用できます。

  • ユーザーがアプリケーションをチューニングできるように、トレースやパフォーマンス測定のためのコードを追加します。

  • 特定のOCIコールに対するコードの事前処理または事後処理を実行します。

  • Oracle Database固有のOCIインタフェースを使用して、OCI付きの他のデータ・ソースにアクセスし、OCIコールがOracle以外のデータ・ソースへのユーザー・コールバックを使用するようにします。

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

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

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

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

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()関数をコールすることにより、OCIStmtPrepare2()コールの環境ハンドル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を取得します。

ユーザー・コールバックの制御フロー

ユーザー・コールバックの制御フローを示します。

例10-3では、典型的なOCIコールの全体的な処理を説明する擬似コードを示しています。

例10-3 典型的な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()のユーザー・コールバック

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

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

関連項目:

OCIErrorGet()

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

最初のコールバックから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ではOCIEnvCreate()または OCIEnvNlsCreate()がエラーになります。パッケージ名は、.so.1.0で終わる必要があります。

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

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

パッケージ・ソースにより2つの関数を指定する必要があります。

以前は、パッケージによりOCIEnvCallback()関数のソース・コードを指定する必要がありました。ただし、OCIEnvCallback()関数は廃止されています。かわりに、パッケージ・ソースにより2つの関数を指定する必要があります。最初の関数の名前は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の関数の名前になります。任意の名前にできますが、通常はpackagenameにして、EnvCallbackという接尾辞を付けます。

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

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

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

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

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

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

ユーザー・コールバックは、アプリケーション自体に静的に登録することも、実行時にDLL内に動的に登録することもできます。

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

OCIUserCallbackGet()関数により、どの関数およびコンテキストをOCIコール用に登録するかが決まります。

関連項目:

OCIUserCallbackGet()

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

Oracle Databaseはアクセス対象となる最も重要なデータベース・ソフトウェアであるため、アプリケーションはOCIインタフェースを有効に利用し、ユーザー・コールバックを使用してOracle以外のデータにアクセスできます。

これにより、OCIで記述されたアプリケーションは、パフォーマンスを低下させずにOracleデータにアクセスできます。ユーザー・コールバックでは、Oracle以外のデータにアクセスするためのドライバを記述できます。OCIには非常に強力なインタフェースが備えられているため、通常、大部分のデータ・ソースに対してOCIコールを直接マッピングできます。このソリューションは、すべてのデータ・ソースに対してパフォーマンスの低下を引き起こす、ODBCのような他の中間層用のアプリケーションを記述することより優れています。OCIを使用するとOracleデータ・ソースにアクセスするのに特に問題は発生しませんが、ODBCがOracle以外のデータ・ソースにアクセスする場合と同じ問題が発生します。

コールバック関数の制限

コールバック関数の制限事項を詳しく説明します。

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

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

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

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

    • OCIUserCallbackGet()

    • OCIEnvCreate()

    • OCIInitialize() (非推奨)

    • OCIEnvNlsCreate()

OCIコールバックの例

OCIコールバックの使用例を示します。

OCIStmtPrepare2()コールに対してそれぞれ最初のコールバック、置換コールバックおよび最後のコールバックを登録している5つのパッケージがあるとします。つまり、ORA_OCI_UCBPKG変数が、例10-4のように設定されています。

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

例10-6では、pkgNEnvCallback()関数での最初、置換および最後のコールバックの登録方法を示しています。

最後に、例10-7では、アプリケーションのソース・コードで、ユーザー・コールバックをNULL ucbDescに登録する方法を示しています。

例10-8では、OCIStmtPrepare2()コールが実行されると、コールバックが次の順にコールされることを示しています。

ノート:

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

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

例10-4 ORA_OCI_UCBPKG変数に対する環境変数の設定

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

例10-5 pkgNInit()関数とPkgNEnvCallback()関数の指定

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

例10-6 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;
}
 

例10-7 ユーザー・コールバックの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);
 

例10-8 コールバックを順にコールするための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()

関連項目:

OCIStmtPrepare2()

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

外部プロシージャからOCIコールバックを使用する方法に関する追加のリファレンス情報を提供します。

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

関連項目:

  • 外部プロシージャからのコールバックとして使用できる関数のリストは、「OCIカートリッジ関数」を参照してください

  • PL/SQLコードからコールできるCサブルーチンの作成方法の詳細は、使用可能なOCIコールのリストおよびコード例も含め、『Oracle Database開発ガイド』を参照してください

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

透過的アプリケーション・フェイルオーバー(TAF)はクライアント側の機能であり、インスタンスまたはネットワークの障害によりデータベース接続に失敗すると発生するエンドユーザー・アプリケーションに対する障害を最小限にするために設計されています。

TAFは、Oracle Real Application Clusters (Oracle RAC)およびOracle Data Guardフィジカル・スタンバイ・データベースなどの各種システム構成に実装できます。TAFは、単一インスタンス・システムの再起動後(修復時など)にも使用できます。

TAFは、データベース・セッションをリストアし、オプションでオープン問合せを再実行するよう構成できます。Oracle Database 10gリリース2 (10.2)以上では、フェイルオーバーの失敗試行後に、該当するすべての文の使用が試みられます。つまり、他の文に対する実行またはフェッチの試行により、障害時文の場合と同様にTAFリカバリが実行されます。後続の文が今回成功する(前回は失敗)か、または試行されたTAFリカバリに対応するエラー(ORA-25401など)をアプリケーションで受信する可能性があります。

ノート:

フェイルオーバーの発生時にコールバックを使用してセッションを必要な状態にリストアできるように、アプリケーションでコールバックを登録することをお薦めします。

ノート:

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

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

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

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

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

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

関連項目:

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

フェイルオーバー中に遅延が発生する可能性があるため、アプリケーション開発者は、フェイルオーバーが進行中であることをユーザーに知らせ、フェイルオーバーの完了通知をユーザーが待機するよう要求した方がよい場合があります。

さらに、最初のインスタンスのセッションで、いくつかのALTER SESSIONコマンドを受け取る可能性もあります。これらのALTER SESSIONコマンドが、2番目のインスタンスで自動的に再実行されることはありません。したがって、開発者は、これらを2番目のインスタンスで再実行する可能性があります。セッションに影響を与えるOCIAttrSet()コールも再実行する必要があります。

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

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

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

関連項目:

透過的アプリケーション・フェイルオーバー・コールバック構造およびパラメータ

TAFコールバック構造およびパラメータを説明します。

透過的アプリケーション・フェイルオーバー(TAF)・コールバック関数の基本的な構造は次のとおりです。

sb4  TAFcbk_fn(OCISvcCtx *svchp, 
               OCIEnv    *envhp, 
               void      *fo_ctx, 
               ub4        fo_type, 
               ub4        fo_event);
svchp

サービス・コンテキスト・ハンドルです。

envhp

OCI環境ハンドルです。

fo_ctx

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

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- 複数の認証ハンドルがあり、最初の認証の後にフェイルオーバーが発生したことを示します。ユーザー・ハンドルが再認証されたことを示します。どのユーザー・ハンドルかを調べるために、アプリケーションで、サービス・コンテキスト・ハンドルsvchpOCI_ATTR_SESSION属性をチェックします。

アプリケーション・コンティニュイティが構成されている場合、TAFコールバックは、再接続、再認証、および処理中のトランザクションのステータスの判別を正常に行った後に、OCI_FO_ENDとともにコールされます。

TAFコールバックの完了時に、オープン・トランザクションが存在し、OCIのアプリケーション・コンティニュイティが有効になっている場合、OCIからエラーが戻されます。

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

ユーザー定義のアプリケーション・フェイルオーバー・コールバック関数の基本的な構造を示し、それについて説明します。

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

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

次のパラメータの例は、「フェイルオーバー・コールバックの例」(9‐31ページ)にあります。

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つのフィールドがあり、フィールドcallback_functionにはコールする関数のアドレスが含まれており、フィールドfo_ctxにはクライアント・コンテキストのアドレスが含まれています。

関連項目:

コールバック登録の例は、例10-10を参照してください

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

いくつかのフェイルオーバー・コールバックの例を示します。

この項では、簡単なユーザー定義コールバック関数の定義の例(例10-9を参照)、フェイルオーバー・コールバックの登録の例(例10-10を参照)およびフェイルオーバー・コールバックの登録解除の例(例10-11を参照)を示しています。

例10-9 ユーザー定義フェイルオーバー・コールバック関数の定義

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 occur.\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;
}

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

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);
}

例10-11 フェイルオーバー・コールバックの登録解除

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; 
  /* unregister 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 Databaseサーバーに対して実行されている、リリース8.0.5以上のOCIライブラリにリンクしているアプリケーションのみです。

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

表10-1で提示されているイベントの時系列について考えます。

表10-1 時間およびイベント

時間 イベント

T0

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

T1

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

T2

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

T3

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

T4

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

T5

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

T6

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

T7

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

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

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

例10-12 フェイルオーバー手法を実装するコールバック関数

/*--------------------------------------------------------------------*/
/* 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 occur.\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イベント通知

Oracle RACデータベースに接続された高可用性のクライアントにデータベース障害が発生したときに、HAイベント通知を使用して、ベストエフォート型プログラム・シグナルをクライアントに提供します。

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

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

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

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

現在のリリースでは、この機能はOracle Notification Service (ONS)に応じて異なります。クライアントがONSを介してHA通知を受け取ることができるように、データベース・サーバー上にOracle Clusterwareをインストールして構成する必要があります。すべてのClusterwareインストール(たとえば、Oracle Data Guard)では、同じONSポートを指定する必要があります。ONSではクライアント構成は必要ありません。

ノート:

クライアントでは、接続されているデータベースからONSサーバー情報が透過的に取得されます。アプリケーションの管理者は、デプロイメント構成ファイルoraaccess.xmlを使用してこの情報を拡張するか、またはオーバーライドできます。

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

  • OCI_EVENTSモードでのOCI環境の初期化

  • 通知が有効化されたサービスへの接続(DBMS_SERVICE.MODIFY_SERVICEプロシージャを使用してAQ_HA_NOTIFICATIONSTRUEに設定する)

  • スレッド・ライブラリとのリンク

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

関連項目:

oraaccess.xmlの詳細は「oraaccess.xmlで指定されるクライアント側デプロイメント・パラメータについて」と、<events><fan>および<ons>のパラメータの詳細を参照してください

OCIEventハンドル

OCIEventハンドルは、イベント・ペイロードの属性をカプセル化します。

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

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

Oracle RACのインスタンス内の接続プールは、Oracle RACの別のインスタンスに接続されている接続のプールから構成されます。

ノード障害の通知を受け取った場合は、特定のインスタンスに接続されているすべての接続をクリーンアップする必要があります。接続が使用されている場合、OCIで接続を閉じる必要があります。これにより、即座に透過的アプリケーション・フェイルオーバー(TAF)が発生し、これらの接続が再確立されます。アイドル状態でプールの空きリストに入っている接続は消去する必要があるため、不適切な接続はプールからユーザーには戻されません。

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

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

独立した接続に対しては特別な処理は必要なく、障害の発生したインスタンスに接続されている接続はすべて即座に切断されます。

アイドル状態の接続に対しては、TAFにより、この接続が後続のOCIコールで使用された際に再確立されます。接続が失敗イベントで使用されている場合は即座に切断されるため、TAFを開始できます。これは、接続の「使用中」の接続およびセッション・プールにも当てはまります。

イベント・コールバック

OCIEventCallback型のイベント・コールバックのシグネチャを示します。

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

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

このシグネチャでは、evtctxはクライアントのコンテキストで、OCIEventは、OCIライブラリに対しては不透明なイベント・ハンドルです。もう1つの入力引数は、イベント・ハンドルであるeventhpです(イベントに関連付けられている属性)。

この関数は、登録されると、イベントのたびに1回ずつ呼び出されます。Oracle 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ハンドルにカプセル化されます。Oracle RAC HA DOWNイベントの場合、クライアント・アプリケーションは、属性タイプOCI_ATTR_HA_SRVFIRSTおよびOCI_ATTR_HA_SRVNEXTとともに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イベントの影響を受けたサーバー・ハンドルのリストを取得する場合、接続はクローズ済でありかつ多くのサーバー・ハンドル属性が無効になっていることに注意してください。かわりに、イベント通知コールバックに必要な接続ごとの属性を、サーバー・ハンドルのユーザー・メモリー・セグメントに格納します。このメモリーは、サーバー・ハンドルが解放されるまで有効です。

関連項目:

OCIAttrGet()

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

適切なクリーン・アップを実行できるように、カスタム・プールを使用してサーバー・ハンドルのタグ情報を取得できます。

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

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

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

ノート:

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

例10-13では、イベント通知の例を示しています。

例10-13 イベント通知

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とトランザクション・ガード

トランザクション・ガードでは、計画または未計画の停止があった場合に、アプリケーションがフェイルオーバー時に元の発行の重複発行を行わないように最大1回のトランザクションの実行をするという概念を紹介します。

アプリケーションでこのサービスを使用してデータベースへの接続がオープンされると、論理トランザクションID (LTXID)が認証時に生成され、セッション・ハンドルに格納されます。これはデータベース・トランザクションをアプリケーションの観点から識別する、グローバルに一意なIDです。停止したとき、トランザクション・ガードを使用しているアプリケーションは以前に失敗したセッションのハンドルからLTXIDを取得し、セッション障害が起こる前にアクティブだったトランザクションの結果をそれによって判定できます。LTXIDが使用されていないと判断される場合、アプリケーションでは、取得されたLTXIDを使用して最初に元の発行をブロックすることによりコミットされていないトランザクションを再実行できます。LTXIDが使用されていると判断される場合は、トランザクションがコミットされ、結果がアプリケーションに戻されます。

トランザクション・ガードは、JDBC Type 4 (Oracle Thin)、OCI、OCCIおよびOracle Data Provider for .NET (ODP.NET)ドライバにサポートされている開発者APIです。OCIの場合、トランザクション・ガードをサポートするようにアプリケーションが作成されていれば、停止時にOCIクライアント・ドライバでOCI_ATTR_LTXIDセッション・ハンドル属性を使用してOCI_ATTR_GET()をコールすることにより、以前に失敗したセッションのハンドルからLTXIDが取得されます。

この項には次のトピックが含まれます。「トランザクション・ガードと使用するアプリケーションの開発」

関連項目:

トランザクション・ガードの概要、サポートされているトランザクションの型、サポートされていないトランザクションの型、およびトランザクション・ガードの使用に関するデータベース構成情報は、『Oracle Database開発ガイド』のトランザクション・ガードの使用に関する章を参照してください。

トランザクション・ガードを使用するアプリケーションの開発

この項では、トランザクション・ガードを使用するOCIユーザー・アプリケーションの開発について説明します。

トランザクション・ガードを使用するアプリケーションの開発の詳細は、『Oracle Database開発ガイド』のトランザクション・ガードの使用に関する章を参照してください。

OCIでセッションのフェイルオーバーを可能にするためにサードパーティまたはユーザー・アプリケーションでトランザクション・ガードを使用するようにするには、次の主要なステップを含める必要があります。

  1. 接続に対して透過的アプリケーション・フェイルオーバー(TAF)が有効になっているかどうかを確認します。TAFでは自動的にLTXIDがチェックされるため、TAFに対応している接続でトランザクション・ガードを明示的に使用しないでください。
  2. エラー受信時に、OCI_ERRORハンドルのOCI_ATTR_ERROR_IS_RECOVERABLEで、エラーがリカバリ可能なエラーかどうかを判断します。エラーがリカバリ可能である場合は、ステップ3に進みます。

    ノート:

    接続がリカバリ可能なエラーの影響を受けていない場合は、LTXIDを使用してトランザクションの結果をチェックしないようにしてください。

  3. OCI_ATTR_GET()を使用してユーザー・セッション・ハンドルからOCI_ATTR_LTXIDを取得し、失敗したセッションと関連付けられているLTXIDを取り出します。
  4. データベースに再接続します。

    ノート:

    新しいセッションには新しいLTXIDが付きますが、元のセッションの状態をチェックする間、これは必要ありません。

  5. DBMS_APP_CONT.GET_LTXID_OUTCOME PL/SQLプロシージャをOCI_ATTR_GET()コールから取得したLTXIDを使用して起動します。フェイルオーバー・セッションの元のLTXIDが使用されていない場合、このLTXIDが強制済としてマークされます。戻り状態により、ドライバは最後のトランザクションがCOMMITTED (TRUE/FALSE)およびUSER_CALL_COMPLETED (TRUE/FALSE)であったことを確認します。
  6. アプリケーションで、コミットされていないトランザクションが再実行されるか、またはユーザーに結果が戻されます。リプレイ自体によって停止する場合は、リプレイ・セッションのLTXIDがDBMS_APP_CONT.GET_LTXID_OUTCOMEプロシージャに使用されます。
トランザクション・ガードの使用方法および例については、次の各項を参照してください。
典型的なトランザクション・ガードの使用例

疑似コードを使用したトランザクション・ガードの標準使用例を示します。

次の疑似コードは、トランザクション・ガードの標準使用例です。

  1. FAN DOWNイベント(またはリカバリ可能エラー)を受信します。

  2. FANで停止したセッションが異常終了されます。

  3. サーバー・ハンドルのOCI_ATTR_TAF_ENABLED属性を使用してOCIAttrGet()をコールします。値がTRUEの場合、停止します。値がFALSEの場合、次のステップに進みます。

  4. リカバリ可能なエラーであれば、OCI (OCI_ERRORハンドルのOCI_ATTR_ERROR_IS_RECOVERABLE)の場合、次のようにします。

    1. OCI_ATTR_LTXIDセッション・ハンドル属性を使用してOCIAttrGet()をコールすることにより停止したセッションから最後のLTXIDを取得し、このセッションのハンドルに関連付けられているLTXIDを取り出します。

    2. 新しいセッションを取得します。

    3. 最後のLTXIDを使用してDBMS_APP_CONT.GET_LTXID_OUTCOMEをコールし、戻り状態を取得します。

  5. 戻り状態が次の場合、

    1. COMMITTEDおよびUSER_CALL_COMPLETED

      結果が戻されます。

    2. ELSEIF COMMITTEDおよびNOT USER_CALL_COMPLETED

      結果が警告とともに戻されます(OUTバインドまたは行カウントが戻されなかった、などの詳細も含む)。

    3. ELSEIF NOT COMMITTED

      トランザクションまたは一連のコール、あるいは両方を再発行するか、ユーザーにエラーを戻します。

関連項目:

OCIAttrGet()

トランザクション・ガードの例

トランザクション・ガードのデモ・プログラムを示します。

例10-14は、次を示すOCIトランザクション・ガード・デモ・プログラム(cdemotg.c)です。

  • OCI_ATTR_ERROR_IS_RECOVERABLE属性の使用。エラー発生時に、エラーがリカバリ可能かどうかプログラムでチェックします。

  • DBMS_APP_CONT.GET_LTXID_OUTCOMEパッケージ・プロシージャの使用。エラーがリカバリ可能な場合、プログラムでDBMS_APP_CONT.GET_LTXID_OUTCOMEがコールされ、アクティブ・トランザクションの状態が判断されます。

トランザクションがコミットされていない場合、失敗したトランザクションがプログラムで再実行されます。

ノート:

このプログラムでは、NLSパラメータなどのセッション状態は修正されません。そのような操作を実行するプログラムでは、エラーに続き、プールから新しいセッションを取得した後でこれらのコマンドを再実行する必要がある場合があります。

例10-14 トランザクション・ガード・デモ・プログラム

*/
 
#ifndef OCISP_ORACLE
# include <cdemosp.h> 
#endif
 
/* Maximum Number of threads  */ 
#define MAXTHREAD 1
static ub4 sessMin = 1;
static ub4 sessMax = 9;
static ub4 sessIncr = 2;
 
static OCIError   *errhp;
static OCIEnv     *envhp;
static OCISPool   *poolhp=(OCISPool *) 0;
static int employeeNum[MAXTHREAD];
 
static OraText *poolName;
static ub4 poolNameLen;
static CONST OraText *database = (text *)"ltxid_service";
static CONST OraText *appusername =(text *)"scott";
static CONST OraText *apppassword =(text *)"tiger";
 
static CONST char getLtxid[]= 
  ("BEGIN DBMS_APP_CONT.GET_LTXID_OUTCOME ("
   ":ltxid,:committed,:callComplete); END;");
 
static CONST char insertst1[] = 
  ("INSERT INTO EMP(ENAME, EMPNO) values ('NAME1', 1000)");
 
static void checkerr (OCIError *errhp, sword status);
static void threadFunction (dvoid *arg);
 
int main(void)
{
  int i = 0;
  sword lstat;
  int timeout =1;
  OCIEnvCreate (&envhp, OCI_THREADED, (dvoid *)0,  NULL,
                NULL, NULL, 0, (dvoid *)0);
 
  (void) OCIHandleAlloc((dvoid *) envhp, (dvoid **) &errhp, OCI_HTYPE_ERROR,
                        (size_t) 0, (dvoid **) 0);
 
  (void) OCIHandleAlloc((dvoid *) envhp, (dvoid **) &poolhp, OCI_HTYPE_SPOOL,
                        (size_t) 0, (dvoid **) 0);
 
  /* Create the session pool */
  checkerr(errhp, OCIAttrSet((dvoid *) poolhp,
           (ub4) OCI_HTYPE_SPOOL, (dvoid *) &timeout, (ub4)0, 
           OCI_ATTR_SPOOL_TIMEOUT, errhp));
 
  if (lstat = OCISessionPoolCreate(envhp, errhp,poolhp, (OraText **)&poolName, 
              (ub4 *)&poolNameLen, database, 
              (ub4)strlen((const char *)database),
              sessMin, sessMax, sessIncr,
              (OraText *)appusername,
              (ub4)strlen((const char *)appusername),
              (OraText *)apppassword,
              (ub4)strlen((const char *)apppassword),
              OCI_SPC_STMTCACHE|OCI_SPC_HOMOGENEOUS))
  {
    checkerr(errhp,lstat);
  }
 
  printf("Session Pool Created \n");
 
  /* Multiple threads using the session pool */
  {
    OCIThreadId *thrid[MAXTHREAD];
    OCIThreadHandle *thrhp[MAXTHREAD];
 
    OCIThreadProcessInit ();
    checkerr (errhp, OCIThreadInit (envhp, errhp));
    for (i = 0; i < MAXTHREAD; ++i)
    {
      checkerr (errhp, OCIThreadIdInit (envhp, errhp, &thrid[i]));
      checkerr (errhp, OCIThreadHndInit (envhp, errhp, &thrhp[i]));
    }
    for (i = 0; i < MAXTHREAD; ++i)
    {
      employeeNum[i]=i;
      /* Inserting into EMP table */
      checkerr (errhp, OCIThreadCreate (envhp, errhp, threadFunction,
                (dvoid *) &employeeNum[i], thrid[i], thrhp[i]));
    }
    for (i = 0; i < MAXTHREAD; ++i)
    {
      checkerr (errhp, OCIThreadJoin (envhp, errhp, thrhp[i]));
      checkerr (errhp, OCIThreadClose (envhp, errhp, thrhp[i]));
      checkerr (errhp, OCIThreadIdDestroy (envhp, errhp, &(thrid[i])));
      checkerr (errhp, OCIThreadHndDestroy (envhp, errhp, &(thrhp[i])));
    }
    checkerr (errhp, OCIThreadTerm (envhp, errhp));
  } /* ALL THE THREADS ARE COMPLETE */
  lstat =  OCISessionPoolDestroy(poolhp, errhp, OCI_DEFAULT);
 
  printf("Session Pool Destroyed \n");
  
  if (lstat != OCI_SUCCESS)
    checkerr(errhp, lstat);
    
  checkerr(errhp, OCIHandleFree((dvoid *)poolhp, OCI_HTYPE_SPOOL));
    
  checkerr(errhp, OCIHandleFree((dvoid *)errhp, OCI_HTYPE_ERROR));
  return 0;
 
} /* end of main () */
 
/* Inserts records into EMP table */ 
static void threadFunction (dvoid *arg)
{
  int empno = *(int *)arg;
  OCISvcCtx *svchp = (OCISvcCtx *) 0;
  OCISvcCtx *svchp2 = (OCISvcCtx *) 0;
  OCISession *embUsrhp = (OCISession *)0;
  OCIBind *bnd1p, *bnd2p, *bnd3p;
 
  OCIStmt *stmthp = (OCIStmt *)0;
  OCIStmt *getLtxidStm = (OCIStmt *)0;
  OCIError  *errhp2 = (OCIError *) 0;
  OCIAuthInfo *authp = (OCIAuthInfo *)0;
  sword lstat;
  text name[10];
 
  boolean callCompl, committed, isRecoverable;
  ub1 *myLtxid;
  ub4  myLtxidLen;
 
  ub4 numAttempts = 0;
 
  (void) OCIHandleAlloc((dvoid *) envhp, (dvoid **) &errhp2, OCI_HTYPE_ERROR,
                     (size_t) 0, (dvoid **) 0);
 
  lstat =  OCIHandleAlloc((dvoid *) envhp,
                          (dvoid **)&authp, (ub4) OCI_HTYPE_AUTHINFO,
                          (size_t) 0, (dvoid **) 0);
  if (lstat)
    checkerr(errhp2, lstat);
 
  checkerr(errhp2, OCIAttrSet((dvoid *) authp,(ub4) OCI_HTYPE_AUTHINFO, 
           (dvoid *) appusername, (ub4) strlen((char *)appusername),
           (ub4) OCI_ATTR_USERNAME, errhp2));
 
  checkerr(errhp2,OCIAttrSet((dvoid *) authp,(ub4) OCI_HTYPE_AUTHINFO, 
           (dvoid *) apppassword, (ub4) strlen((char *)apppassword),
           (ub4) OCI_ATTR_PASSWORD, errhp2));
 
restart:
  if  (lstat = OCISessionGet(envhp, errhp2, &svchp, authp,
               (OraText *)poolName, (ub4)strlen((char *)poolName), NULL, 
               0, NULL, NULL, NULL, OCI_SESSGET_SPOOL))
  {
    checkerr(errhp2,lstat);
  } 
  
  /* save the ltxid from the session in case we need to call
   *  get_ltxid_outcome to determine the transaction status.
   */
  checkerr(errhp2, OCIAttrGet(svchp, OCI_HTYPE_SVCCTX,
                              (dvoid *)&embUsrhp, (ub4 *)0,
                              (ub4)OCI_ATTR_SESSION, errhp2));
  checkerr(errhp2, OCIAttrGet(embUsrhp, OCI_HTYPE_SESSION,
                              (dvoid *)&myLtxid, (ub4 *)&myLtxidLen,
                              (ub4)OCI_ATTR_LTXID, errhp2));
  
 
  /* */
  checkerr(errhp2, OCIStmtPrepare2(svchp, &stmthp, errhp2, 
                                   (CONST OraText *)insertst1, 
                                   (ub4)sizeof(insertst1),
                                   (const oratext *)0, (ub4)0,
                                   OCI_NTV_SYNTAX, OCI_DEFAULT));
 
  if (!numAttempts)
  {
    char input[1];
 
    printf("Kill SCOTT's session now. Press ENTER when complete\n");
    gets(input);
  }
  lstat = OCIStmtExecute (svchp, stmthp, errhp2, (ub4)1, (ub4)0,
                          (OCISnapshot *)0, (OCISnapshot *)0, 
                          OCI_DEFAULT );
  if (lstat == OCI_ERROR)
  {
    checkerr(errhp2, OCIAttrGet(errhp2, OCI_HTYPE_ERROR,
                                (dvoid *)&isRecoverable, (ub4 *)0,
                                (ub4)OCI_ATTR_ERROR_IS_RECOVERABLE, errhp2));
    if (isRecoverable)
    {
 
      printf("Recoverable error occurred; checking transaction status.\n");
      /* get another session to use for the get_ltxid_outcome call */
      if  (lstat = OCISessionGet(envhp, errhp2, &svchp2, authp,
                                 (OraText *)poolName, 
                                 (ub4)strlen((char *)poolName), NULL, 
                                 0, NULL, NULL, NULL, OCI_SESSGET_SPOOL))
      {
        checkerr(errhp2,lstat);
      } 
      
      checkerr(errhp2,OCIStmtPrepare2(svchp2,&getLtxidStm, errhp2, 
                                      (CONST OraText *)getLtxid,
                                      (ub4)sizeof(getLtxid),
                                      (const oratext *)0, (ub4)0,
                                      OCI_NTV_SYNTAX, OCI_DEFAULT));
      checkerr(errhp, OCIBindByPos(getLtxidStm, &bnd1p, errhp, 1,
                                   (dvoid *) myLtxid, (sword)myLtxidLen,
                                   SQLT_BIN, (dvoid *)0,
                                   (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0,
                                   OCI_DEFAULT));
      checkerr(errhp, OCIBindByPos(getLtxidStm, &bnd2p, errhp, 2,
                                   (dvoid *) &committed, 
                                   (sword)sizeof(committed),
                                   SQLT_BOL, (dvoid *)0,
                                   (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0,
                                   OCI_DEFAULT));
      checkerr(errhp, OCIBindByPos(getLtxidStm, &bnd3p, errhp, 3,
                                   (dvoid *) &callCompl, 
                                   (sword)sizeof(callCompl),
                                   SQLT_BOL, (dvoid *)0,
                                   (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0,
                                   OCI_DEFAULT));
      
      checkerr(errhp2,OCIStmtExecute(svchp2, getLtxidStm, errhp2, 
                                     (ub4)1, (ub4)0,
                                     (OCISnapshot *)0, (OCISnapshot *)0, 
                                     OCI_DEFAULT ));
      checkerr(errhp2, OCISessionRelease(svchp2, errhp2, 
                                         NULL, 0, OCI_DEFAULT));
      if (committed && callCompl)
        printf("Insert successfully commited \n");
      else if (!committed)
      {
        printf("Transaction did not commit; re-executing last transaction\n");
        numAttempts++;
 
        /* As there was an outage, do not return this session to the pool */
        checkerr(errhp2, 
                 OCISessionRelease(svchp, errhp2, 
                                   NULL, 0, OCI_SESSRLS_DROPSESS));
        svchp = (OCISvcCtx *)0;
        goto restart;
      }
    }
  }
  else
  {
    checkerr(errhp2, OCITransCommit(svchp,errhp2,(ub4)0));
    printf("Transaction committed successfully\n");
  }
  if (stmthp)
    checkerr(errhp2, OCIStmtRelease((dvoid *) stmthp, errhp2,
                                    (void *)0, 0, OCI_DEFAULT));
  if (getLtxidStm)
    checkerr(errhp2, OCIStmtRelease((dvoid *) getLtxidStm, errhp2,
                                    (void *)0, 0, OCI_DEFAULT));
 
  if (svchp)
    checkerr(errhp2, OCISessionRelease(svchp, errhp2, NULL, 0, OCI_DEFAULT));
  OCIHandleFree((dvoid *)authp, OCI_HTYPE_AUTHINFO);
  OCIHandleFree((dvoid *)errhp2, OCI_HTYPE_ERROR);
 
} /* end of threadFunction (dvoid *) */
 
/* This function prints the error */
void checkerr(errhp, status)
OCIError *errhp;
sword status;
{
  text errbuf[512];
  sb4 errcode = 0;
 
  switch (status)
  {
  case OCI_SUCCESS:
    break;
  case OCI_SUCCESS_WITH_INFO:
    (void) printf("Error - OCI_SUCCESS_WITH_INFO\n");
    break;
  case OCI_NEED_DATA:
    (void) printf("Error - OCI_NEED_DATA\n");
    break;
  case OCI_NO_DATA:
    (void) printf("Error - OCI_NODATA\n");
    break;
  case OCI_ERROR:
    (void) OCIErrorGet((dvoid *)errhp, (ub4) 1, (text *) NULL, &errcode,
                       errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
    (void) printf("Error - %.*s\n", 512, errbuf);
    break;
  case OCI_INVALID_HANDLE:
    (void) printf("Error - OCI_INVALID_HANDLE\n");
    break;
  case OCI_STILL_EXECUTING:
    (void) printf("Error - OCI_STILL_EXECUTE\n");
    break;
  case OCI_CONTINUE:
    (void) printf("Error - OCI_CONTINUE\n");
    break;
  default:
    break;
  }
}

OCIとアプリケーション・コンティニュイティ

アプリケーション・コンティニュイティ(AC)では、Oracle Database 12cリリース2 (12.2)以上で導入された計画済または計画外の停止に対するサポートとしてOCIがサポートされます。

ACは、Oracle RAC、Oracle RAC One、またはActive Data Guardをインスタンスまたはサイトのフェイルオーバー用に実行している高可用性(HA)環境でハードウェア、ソフトウェア、ネットワーク、ストレージのエラーとタイムアウトをマスキングします。ACでは、SQL*Plus、Tuxedo、WebLogic Serverに加え、JDBC Type 4 (Oracle Thin)、OCIおよびOracle Data Provider for .NET (ODP.NET)ドライバに対するサポートが提供されます。

OCIセッション・プールを使用するアプリケーションの計画済停止では、OCIセッション・プールは、接続がPLANNED DOWNイベントによって影響を受けた時点を検出し、接続がプールに戻されると同時に接続を終了します。OCIセッション・プールを使用しないアプリケーションの計画済停止では、OCIアプリケーションは、接続が計画済停止イベントによって影響を受けた時点を検出します。どちらの場合でも、OCIは、DMLリプレイが安全である時点を暗黙的に判断するため、停止イベント後のエラーの発生数は抑制されます。

計画外停止では、OCIは、トランザクション・ガードを使用して、リカバリ可能なエラーの発生後に処理中のトランザクションをリカバリすることで、OCIアプリケーションがトランザクションの結果を確実に判別できるようにします。つまり、このサポートによって、停止中のアプリケーション要求の完了時に発生するデータベース接続性とセッション状態のリストアによる遅延はごくわずかとなります。ACは、当初の実行の最中にトランザクションがコミットしていなかったことを判別できる場合にのみ、処理中のトランザクションのリプレイを試みます。

OCIのACサポートでは、OCIセッション・プールまたはTuxedoを使用することをお薦めします。

関連項目:

リカバリ可能なエラーの後に発生すること

リカバリ可能なエラーの発生後、データベース・セッションは、あるデータベース・インスタンスから別のデータベース・インスタンスにフェイルオーバーされます。

新しいインスタンスは、同じOracle RACクラスタの一部である場合も、サイトの障害後にプライマリ・データベースとして起動されたOracle Data Guardスタンバイ・データベースである場合もあります。透過的アプリケーション・フェイルオーバー(TAF)が正常に再接続および再認証を行った後、OCIのアプリケーション・コンティニュイティによって、すべてのSQLおよびPL/SQL文を含め、失敗したセッションに関連するコール履歴がリプレイされます。リプレイは、単一セッション上で動作し、再発行アクティビティを他のデータベース・セッションと同期することはありません。リプレイが成功するのは、トランザクション・リプレイについてクライアントが認識する結果が、元の発行と同一である場合のみです。

正常なリプレイのための基準

ドライバのリプレイが成功するには、フェイルオーバー後のトランザクションについてクライアントが認識する結果が、最初の発行と同一である必要があります。

成功の基準は次のとおりです。

  • リターン・コードとエラー・メッセージ・テキストは、同一である必要があります。

  • 結果セットは、同一である必要があります。定義データは同一で、行は同じ順序で戻される必要があります。

  • 処理された行数は、同一である必要があります。たとえば、フェイルオーバー後の更新文では、元の更新文と同じ数の行を更新する必要があります。

  • 新規接続のセッション状態は、元の接続のセッション状態と一致します。

これらの基準の詳細は、『Oracle Real Application Clusters管理およびデプロイメント・ガイド』を参照してください。

可変データおよびアプリケーション・コンティニュイティの安定性

可変オブジェクトの値が、ある実行から次の実行までに変化した場合、そのデータは、可変であるとみなされるため、リプレイ不可であることが保証されます。順序は、この可変データの例です。

DMLリプレイの成功率を向上するには、最初の発行で使用された値を持つ可変データを含むDMLをリプレイする必要があります。元の値が保持されておらず、これらの可変オブジェクトの別の値がクライアントに戻される場合、クライアントが認識する値が異なるため、リプレイは拒否されます。

可変オブジェクト値を保持するためのサポートは現在、SYSDATESYSTIMESTAMPSYS_GUIDおよびsequence.NEXTVALに対して提供されています。

関連項目:

可変オブジェクトおよびアプリケーション・コンティニュイティの詳細は、『Oracle Real Application Clusters管理およびデプロイメント・ガイド』を参照してください。

OCIのアプリケーション・コンティニュイティを無効化する要因

その後のアプリケーション要求が開始するまで、OCIのアプリケーション・コンティニュイティが暗黙的に無効化される要因をリストします。

次の状況では、その後のアプリケーション要求が開始するまで、OCIのアプリケーション・コンティニュイティは暗黙的に無効化されます。

  • リプレイと一貫性のない状況をサーバーが検出した場合。たとえば、SESSION_STATE_CONSISTENCY=DYNAMICの状態で、PL/SQL無名ブロックに埋込みのトップレベルCOMMIT文が含まれる場合(自律型トランザクションはトップレベルとはみなされません)、ドライバは、OCIのアプリケーション・コンティニュイティを暗黙的に無効化します。

  • OCIのアプリケーション・コンティニュイティでサポートされないOCI関数をアプリケーションがコールした場合。

アプリケーションでは、OCIRequestDisableReplay()をコールすることで、OCIのアプリケーション・コンティニュイティを明示的に無効化できます。

リプレイの失敗

リプレイが失敗する原因について説明します。

OCIのアプリケーション・コンティニュイティがトランザクションをリプレイする場合、次の状況ではリプレイに失敗します。

  • リプレイ時にCOMMIT文が実行された場合

  • リプレイ結果がトランザクションの最初の発行と一貫性がない場合

  • 最初のリプレイの再試行制限を超えている状態で、リプレイ中にリカバリ可能なエラーが発生した場合

  • OCIStmtPrepare()を使用するアプリケーションが「エラー - ORA-25412: OCIStmtPrepareへのコールにより、トランザクションの再実行が無効化されています」というエラーを戻した場合。HAインフラストラクチャでアプリケーション・コンティニュイティの使用をサポートするには、OCIStmtPrepare2()コールを使用します。

アプリケーション・コンティニュイティは、失敗したトランザクションを正常にリプレイできない場合、エラーを戻します。リプレイの失敗の理由を示すため、クライアント側のトレース・ファイルに追加の診断情報が記録されます。

アプリケーション・コンティニュイティが最も効果的な状況

OCIのアプリケーション・コンティニュイティの有効性を決める条件について説明します。

OCIのアプリケーション・コンティニュイティは、次の状況で最も効果的です。

  • データベース・サービスでCOMMIT_OUTCOME属性が指定され、透過的アプリケーション・フェイルオーバー(TAF)が構成されています。

  • 明示的に(OCIRequestBegin()およびOCIRequestEnd()をコールして)、またはOCIセッション・プールの使用を通じて暗黙的に、アプリケーションでアプリケーション要求の開始と終了をマークできます。

  • アプリケーション要求に、その要求の最後にコミットされる1つ以下のデータベース・トランザクションが含まれます。

  • アプリケーションがサーバーでPL/SQLまたはJavaを実行する場合、そのPL/SQLまたはJavaの動作が次のとおりです。
    • 埋込みCOMMIT文が含まれません

    • PL/SQLまたはJavaの完了後に永続化される必要のある状態(パッケージ変数など)を設定しません。

  • TAFコールバックがオープン・データベース・トランザクションを後に残しません。

OCIのアプリケーション・コンティニュイティがフェイルオーバーできる場合

停止が発生した場合にOCIのアプリケーション・コンティニュイティがフェイルオーバーできる関数について説明します。

OCIのアプリケーション・コンティニュイティは、次のいずれかの関数の実行時に停止が発生した場合にフェイルオーバーできます。
  • OCIStmtExecute()

  • OCIStmtFetch()またはOCIStmtFetch2()

  • OCISessionEnd()

  • OCITransCommit()

  • OCITransRollback()

アプリケーション・コンティニュイティでサポートされるLOB関数

OCIのアプリケーション・コンティニュイティでサポートされるLOB関数について説明します。

OCIのアプリケーション・コンティニュイティでは、次のLOB関数を使用するアプリケーションがサポートされます(ただし、いずれかのコールの実行中に接続に障害が発生すると、フェイルオーバーは不完全な状態となります)。

  • OCILobOpen()

  • OCILobClose()

  • OCILobIsOpen()

  • OCILobRead()

  • OCILobRead2()

  • OCILobArrayRead()

  • OCILobWriteAppend()

  • OCILobWriteAppend2()

  • OCILobWrite()

  • OCILobWrite2()

  • OCILobArrayWrite()

  • OCILobCopy2()

  • OCILobAppend()

  • OCILobLoadFromFile()

  • OCILobLoadFromFile2()

  • OCILobGetLength() (Microsoft Windowsのみ)

  • OCILobGetLength2() (Microsoft Windowsのみ)

  • OCILobGetChunkSize()

  • OCILobTrim()

  • OCILobTrim2()

  • OCILobGetStorageLimit()

  • OCILobDisableBuffering()

  • OCILobEnableBuffering()

  • OCILobFlushBuffer()

  • OCILobFileOpen()

  • OCILobFileIsOpen()

  • OCILobFileCloseAll()

  • OCILobFileClose()

  • OCILobCreateTemporary()

  • OCILobFreeTemporary()

  • OCILobLocatorAssign()

  • OCILobAssign()

  • OCILobIsEqual()

  • OCILobLocatorIsInit()

  • OCILobCharSetForm()

  • OCILobFileSetName()

  • OCILobFileGetName()

  • OCILobIsTemporary()

  • OCILobLocatorAssign()

OCIのアプリケーション・コンティニュイティでサポートされない構成要素

OCIのアプリケーション・コンティニュイティでサポートされない構成要素について説明します。

OCIのアプリケーション・コンティニュイティでは、次の構成要素はサポートされません。

  • XAトランザクション

  • 埋込みCOMMIT文を含むPL/SQLブロック

  • デキュー即時モードのAQデキュー(deqopt.visibility)

  • ストリーム・バインドまたは定義

  • 異なるデータベース・セッション間での共有クライアント文ハンドル

  • OCI_CONTINUEを戻さないOCI_CBTYPE_ENTRY型の登録済OCIコールバック

  • COMMIT NOWAIT

  • DCLコマンド

アプリケーション・コンティニュイティで発生する可能性のある副作用

OCIのアプリケーション・コンティニュイティは、セッションが再構築されてデータベース状態がリストアされると、リカバリ可能なエラーの後に元のPL/SQLおよびSQL文をリプレイします。リプレイによって、副作用が2回発生しますが、これは望ましい場合と望ましくない場合があります。

アプリケーションでは、これらの副作用を理解して、重複する実行を受け入れることができるかどうかを判断することが重要です。受け入れることができない場合、リプレイの影響を受容または緩和するためのアクションをアプリケーションで実行する必要があります。たとえば、OCIRequestDisableReplay()をコールします。

関連項目:

副作用を発生させるアクションの例は、『Oracle Real Application Clusters管理およびデプロイメント・ガイド』を参照してください。

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

OCIでは、Streams Advanced Queuing (Streams AQ)機能へのインタフェースを提供します。Streams AQは、Oracle Databaseと統合されたメッセージ・キューイングです。

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

ノート:

Streamsアドバンスト・キューイングを使用するには、Oracle DatabaseのEnterprise Editionを使用している必要があります。

関連項目:

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

OCI Streamsアドバンスト・キューイング関数を示します。

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

  • OCIAQEnq()

  • OCIAQDeq()

  • OCIAQListen() (非推奨)

  • OCIAQListen2()

  • OCIAQEnqArray()

  • OCIAQDeqArray()

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

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

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関数との比較を示します。

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

表10-2 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()

表10-3では、エンキュー関数のパラメータを比較しています。

表10-3 エンキュー・パラメータ

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

-

ノート: OCIAQEnq()では、さらにsvcherrh、payload_tdo、payload_indおよびflagsの各パラメータが必要です。

表10-4では、デキュー関数のパラメータを比較しています。

表10-4 デキュー・パラメータ

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

-

ノート: OCIAQDeq()では、さらにsvcherrh、dequeue_options、message_properties、payload_tdo、payload、payload_indおよびflagsの各パラメータが必要です。

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

表10-5 リスニング・パラメータ

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

lopts

-

ノート: OCIAQListen2()では、さらにsvchp、errhp、agent_list、num_agents、agent、lmopsおよびflagsの各パラメータが必要です。

表10-6では、配列エンキュー関数のパラメータを比較しています。

表10-6 配列エンキュー・パラメータ

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()では、さらにsvcherrh、payload_tdo、payload_ind、ctxp、enqcbfpおよびflagsの各パラメータが必要です。

表10-7では、配列デキュー関数のパラメータを比較しています。

表10-7 配列デキュー・パラメータ

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()では、さらにsvcherrh、msgprop、payload_tdo、payload_ind、ctxp、deqcbfpおよびflagsの各パラメータが必要です。

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

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

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

OCI_ATTR_AGENT_NAME

address

OCI_ATTR_AGENT_ADDRESS

protocol

OCI_ATTR_AGENT_PROTOCOL

表10-9では、メッセージ・プロパティのパラメータを比較しています。

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

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

表10-10では、エンキュー・オプション属性を比較しています。

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

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

OCI_ATTR_VISIBILITY

relative_msgid

OCI_ATTR_RELATIVE_MSGID

sequence_deviation

OCI_ATTR_SEQUENCE_DEVIATION

(非推奨)

transformation

OCI_ATTR_TRANSFORMATION

delivery_mode

OCI_ATTR_MSG_DELIVERY_MODE

表10-11では、デキュー・オプション属性を比較しています。

表10-11 デキュー・オプション属性

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

deq_condition

OCI_ATTR_DEQCOND

transformation

OCI_ATTR_TRANSFORMATION

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が設定されます。順序を指定していない場合エラーは戻されませんが、関連メッセージの前にこのメッセージはデキューされません。

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

関連項目:

OCIAQEnq()

バッファ・メッセージの使用

バッファ・メッセージは、Streams AQ内の非永続型メッセージ機能で、Oracle Database 10gリリース2から使用可能になりました。

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

  • OCIAQEnq()

  • OCIAQDeq()

  • OCIAQListen2()

例10-15では、エンキュー・バッファ・メッセージの例を示しています。

例10-16では、デキュー・バッファ・メッセージの例を示しています。

ノート:

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

例10-15 エンキュー・バッファ・メッセージ

...
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));
...

例10-16 デキュー・バッファ・メッセージ

...
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 must 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 do not return the payload (to simplify the code
   fragment)
*/
OCIAQDeq(svchp, errhp,  "test_queue", deqopt, msgprop, msg_tdo, (void **)0,
         (void **)0, (OCIRaw**)0, 0);
...

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

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

図10-2は、そのプロセスを示しています。

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

図10-2の説明が続きます
「図10-2 パブリッシュ・サブスクライブのモデル」の説明

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

  • AQネームスペースに通知の実行を登録し、エンキューが発生したときに通知を受け取ります。

  • データベース・イベントのサブスクリプションの実行を登録し、そのイベントがトリガーされたときに通知を受け取ります。

  • 一時的な登録の解除またはすべての登録の破棄など、登録を管理します。

  • 登録されたクライアントに通知を送信します。

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

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

関連項目:

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

データベースに直接登録することも、Lightweight Directory Access Protocol (LDAP)を使用して登録することもできます。

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

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

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

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

イベント通知の直接登録および受信を行うには、OCIアプリケーションで次のステップを実行する必要があります。

適切なイベント・トリガーまたはAQエンキューがセットアップされていることを前提としています。初期化パラメータCOMPATIBLEは8.1以上に設定されている必要があります。

ノート:

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

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

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

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

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

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

    • OCI_ATTR_SUBSCR_HOSTADDR- 通信の送信先であるクライアントIP (IPv4またはIPv6書式)を設定する環境ハンドル属性。

      Oracle Databaseコンポーネントおよびユーティリティでは、インターネット・プロトコル・バージョン6 (IPv6)アドレスをサポートします。

      関連項目:

      IPアドレスのIPv6書式の詳細は、「OCI_ATTR_SUBSCR_HOSTADDR」「OCI_ATTR_SUBSCR_IPADDR」および『Oracle Database Net Services管理者ガイド』を参照してください

    • 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_SUBSCR_QOS_PURGE_ON_NTFNを設定すると、最初の通知で登録が削除されます。

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

    • 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_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、タイムアウト間隔、ネームスペースおよびポートの値を設定します(例9 15を参照) 。

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

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

    関連項目:

    通知プロシージャ

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

例10-17では、QOSレベルの設定の例を示しています。

例10-17 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);
パブリッシュ・サブスクライブのオープン登録

パブリッシュ・サブスクライブのオープン登録の前提条件を示します。

パブリッシュ・サブスクライブのオープン登録の前提条件は、次のとおりです。

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

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

     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のオープン登録

LDAP登録を使用したオープン登録の方法を示します。

  1. modeOCI_EVENTS | OCI_USE_LDAPに設定したOCIEnvCreate()または OCIEnvNlsCreate()をコールします。
  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 Databaseに対する管理コンテキスト。

  3. ハンドル・タイプをOCI_HTYPE_SUBSCRIPTION,に指定してOCIHandleAlloc()をコールし、サブスクリプション・ハンドルを割り当てます。
  4. 記述子タイプをOCI_DTYPE_SRVDNに指定してOCIArrayDescriptorAlloc()をコールし、サーバーの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_RECPTRES- 通知を受信するための表示。

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

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

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

  7. QOS、タイムアウト間隔、ネームスペースおよびポートの値を設定します。「QOS、タイムアウト間隔、ネームスペース、クライアント・アドレスおよびポート番号の設定」を参照してください。
  8. OCISubscriptionRegister()をコールし、サブスクリプションを登録します。データベースがLDAPにアクセスして新規登録を取り出すと、登録が有効になります。取り出す頻度は、LDAP_REG_SYNC_INTERVALの値によって決まります。
QOS、タイムアウト間隔、ネームスペース、クライアント・アドレスおよびポート番号の設定

OCIAttrSet()を使用して、QOSFLAGSを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);

クライアント・アドレス設定の例

text ipaddr[16] = "10.177.246.40";
(void)(OCIAttrSet((dvoid *) envhp, (ub4) OCI_HTYPE_ENV,
       (dvoid *) ipaddr, (ub4) strlen((const char *)ipaddr),
       (ub4) OCI_ATTR_SUBSCR_IPADDR, errhp));

関連項目:

OCI_ATTR_SUBSCR_IPADDR

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

パブリッシュ・サブスクライブ通知を管理するために使用する関数をリストし、説明します。

表10-12は、パブリッシュ・サブスクライブ通知に使用される関数のリストです。

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

関数 用途

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のみです。この値は、デフォルトのコールを実行します。

例10-18では、通知コールバックでのAQグループ化通信属性の使用方法を示しています。

例10-18 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 hexadecimal 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属性によって設定されます。

関連項目:

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

直接登録を使用したパブリッシュ・サブスクライブ通知の実装例を示します。

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

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

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

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

例10-19 パブリッシュ・サブスクライブ通知の実装

----------------------------------------------------------
----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;
/

例10-20 コールバック関数を使用する通知の登録

...
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 initializes 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*)  "xyz@company.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);
}

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

LDAP登録を行う方法を説明する例を示します。

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

例10-21 LDAP登録

...

  /* To use the LDAP registration feature, OCI_EVENTS | OCI_EVENTS |OCI_USE_LDAP*/
  /*   must be set in OCIEnvCreate or OCIEnvNlsCreate */
  /*     (Note: OCIInitialize is deprecated): */
  (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);

  /* administrative 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);

  /* administrative 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 you 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 you 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
     an Oracle database*/
    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. However,
     if mode OCI_REG_LDAPONLY is used, the registration request
     will be directly sent to LDAP. This mode should be used when there is 
     more than one server DN in the server DN descriptor or you are sure
     that a database connection is not available.

     In this example, two DNs are entered, so you should use mode 
     OCI_REG_LDAPONLY in LDAP registration. */
  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);
}
...