この章は、次の項目で構成されています。
OCIには、ローカル・トランザクションとグローバル・トランザクションの両方での操作に対応した一連のAPIコールが用意されています。これらのコールにはオブジェクトのサポートが含まれているため、OCIアプリケーションがオブジェクト・モードで実行されている場合はコミット・コールおよびロールバック・コールでオブジェクト・キャッシュとトランザクションの状態が同期します。
トランザクション操作を実行する関数は、次のとおりです。それぞれのコールは、適切なサーバー・コンテキストおよびユーザー・セッション・ハンドルで初期化する必要がある、サービス・コンテキスト・ハンドルを取ります。トランザクション・ハンドルは、サービス・コンテキストの3番目の要素で、トランザクションに関する特定の情報を格納します。SQL文が準備されると、トランザクション・ハンドルは、特定のサービス・コンテキストと関連付けられます。その文が実行されると、その結果(問合せ、フェッチ、挿入)は、サービス・コンテキストと現在関連しているトランザクションの一部になります。
OCITransStart()− トランザクションの開始をマークします。
OCITransDetach()− トランザクションを連結解除します。
OCITransCommit()− トランザクションをコミットします。
OCITransRollback()− トランザクションをロールバックします。
OCITransPrepare()− 分散処理環境でトランザクションをコミットする準備をします。
OCITransMultiPrepare()−1回のコールで、複数のブランチのあるトランザクションを準備します。
OCITransForget()− 発見的方法で完了したグローバル・トランザクションの記憶をサーバーから消します。
これらのコールをすべて使用するか、そのうちのいくつかのみを使用するかは、アプリケーションでのトランザクションの複雑度のレベルによって異なります。次の項で、詳細に説明します。
OCIでは、複数のレベルのトランザクションの複雑度をサポートします。
ほとんどのアプリケーションで扱うのは、単純なローカル・トランザクションのみです。これらのアプリケーションでは、そのアプリケーションの中でデータベースを変更すると、暗黙的トランザクションが作成されます。アプリケーションなどで必要とされるトランザクション特有のコールには、次のようなコールがあります。
OCITransCommit()− トランザクションをコミットします。
OCITransRollback()− トランザクションをロールバックします。
1つのトランザクションがコミットまたはロールバックするとすぐに、次のデータベース変更によって、そのアプリケーションに対する新規の暗黙的トランザクションが作成されます。
1つのサービス・コンテキストでは、常に1つの暗黙的トランザクションのみをアクティブにできます。暗黙的なトランザクションの属性は、ユーザーには不透明です。
アプリケーションで複数のセッションを作成すると、それぞれの権限に対し、関連する暗黙的トランザクションを1つずつ保持することができます。
|
関連項目: 単純なローカル・トランザクションの使用方法を示すコード例は、「OCITransCommit()」を参照してください。Oracle Database 10g リリース2で導入された新しいフラグ( OCI_TRANS_WRITEBATCHおよびOCI_TRANS_WRITENOWAIT)を参照してください。 |
アプリケーションでシリアライズ可能または読取り専用トランザクションが必要な場合は、トランザクションを開始する追加のOCIのOCITransStart()コールが必要です。
OCITransStart()コールでは、flagsパラメータに対して、OCI_TRANS_SERIALIZABLEまたはOCI_TRANS_READONLYの適切な値を指定する必要があります。フラグが指定されない場合、デフォルト値は、標準の読取り/書込みトランザクションのOCI_TRANS_READWRITEです。
OCITransStart()コールで読取り専用オプションを指定すると、SET TRANSACTION READ ONLY文を実行するためのサーバー・ラウンドトリップは必要ありません。
グローバル・トランザクションは、より高度なトランザクションを処理するアプリケーションでのみ必要です。
トランザクション処理(TP)モニターなどの3層アプリケーションでは、グローバル・トランザクションの作成および管理を行います。これらのアプリケーションは、グローバル・トランザクション識別子(XID)を提供します。これによって、サーバーにローカル・トランザクションが関連付けられます。
グローバル・トランザクションには、1つ以上のブランチがあります。それぞれのブランチは、XIDによって識別されます。XIDは、グローバル・トランザクション識別子(gtrid)とブランチ修飾子(bqual)で構成されています。この構造は、標準のXA仕様に基づくものです。
次の例では、XIDが1234の構造です。
OCIトランザクション・コールで使用されるトランザクション識別子は、OCIAttrSet()を使用して、トランザクション・ハンドルのOCI_ATTR_XID属性に設定します。かわりに、OCI_ATTR_TRANS_NAME属性で設定した名前でトランザクションを識別することもできます。
この属性をトランザクション・ハンドルで設定するときは、名前の長さは最大64バイトにできます。XIDのformatidは0(ゼロ)で、ブランチ修飾子も0(ゼロ)です。
この属性をトランザクション・ハンドルから取り出すときは、戻されたトランザクション名はグローバル・トランザクション識別子となります。サイズはグローバル・トランザクション識別子の長さとなります。
単一のグローバル・トランザクションで、一対のブランチが密結合している場合と疎結合している場合の両方をサポートしています。
密結合ブランチは同じローカル・トランザクションを共有します。この場合、gtridは、単一のローカル・トランザクションを指し、複数のブランチが同じトランザクションを指し示します。トランザクションの所有者は、最初に作成されたブランチです。
疎結合ブランチは異なるローカル・トランザクションを使用します。この場合は、gtridとbqualがともに一意のローカル・トランザクションにマッピングされます。各ブランチは、異なるトランザクションを指し示します。
OCITransStart()のflagsパラメータによって、アプリケーションから、OCI_TRANS_TIGHTまたはOCI_TRANS_LOOSEを渡して結合のタイプを指定できます。
セッションはOCISessionBegin()で作成され、ユーザー・セッションに対応します。
図8-1では、あるアプリケーションの密結合ブランチを説明します。S1とS2がセッション、B1とB2がブランチ、Tがトランザクションです。2つのブランチのXIDが同じトランザクションで操作されているので、同じgtridを共有します。ただし、これらは別々のブランチなので、異なるbqualを持ちます。
図8-2では、異なるブランチでの単一セッションの操作を説明します。これらは異なるグローバル・トランザクションを表すため、XIDのgtridコンポーネントが異なります。
同じトランザクションを共有する複数のブランチを単一セッションで操作することも可能ですが、これはあまり実用的ではありません。
トランザクションのブランチには、アクティブ・ブランチと非アクティブ・ブランチの2通りの状態があります。
サーバー・プロセスがブランチに対する要求を実行しているとき、ブランチはアクティブ状態です。サーバー・プロセスがブランチ内の要求を実行していないとき、ブランチは非アクティブ状態です。この場合は、ブランチの親セッションは1つもなく、ブランチはサーバーのPMONプロセスに所有されます。
OCIアプリケーションがOCITransDetach()コールを使用してブランチを連結解除すると、ブランチは非アクティブ状態になります。flagsパラメータにOCI_TRANS_RESUMEを設定したOCITransStart()のコールを使用してブランチを再開すると、ブランチを再びアクティブにできます。
アプリケーションでOCITransDetach()をコールし、ブランチを連結解除する場合は、そのブランチを作成したOCITransStart()コールのtimeoutパラメータで指定した値を利用します。timeoutパラメータで指定した秒数が経過すると、PMONの子として休止しているトランザクションは削除されます。
アプリケーションでブランチを再開するには、OCITransStart()をコールします。このとき、トランザクション・ハンドルの属性としてブランチのXIDを、flagsパラメータにOCI_TRANS_RESUMEを指定し、またtimeoutパラメータには前回とは異なる値を指定します。このコールのtimeoutの値は、ブランチが他のプロセスで使用中のとき、そのブランチが使用可能になるまでセッションが待つ時間の長さを指定します。ブランチにアクセスしているプロセスがない場合は、すぐに再開されます。トランザクションは、そのトランザクションを連結解除したプロセス以外のプロセスからでも再開できます。ただし、トランザクションを再開できるのは、そのトランザクションを連結解除したプロセスと同じ権限を持つプロセスに限られます。
サーバー・ハンドルには、OCI_ATTR_EXTERNAL_NAME属性およびOCI_ATTR_INTERNAL_NAME属性があります。これらの属性により、グローバル・トランザクションを実行するときに記録されるクライアント・データベース名を設定します。障害のために準備状態で保留になっている可能性があるトランザクションを追跡するために、データベース管理者がこの名前を使用できます。
|
注意: OCIアプリケーションでは、グローバル・トランザクションにログオンして使用する前に、 OCIAttrSet()を使用して、これらの属性を設定します。 |
グローバル・トランザクションは、1フェーズまたは2フェーズでコミットされます。最も単純な状態は、単一のトランザクションが単一のデータベースに対して操作されている場合です。この場合は、アプリケーションでOCITransCommit()をコールすることによって、そのトランザクションの1フェーズ・コミットを実行できます。OCITransCommit()コールのデフォルト値は、1フェーズ・コミットです。
アプリケーションから、複数のデータベースまたは複数のOracleサーバーに対してトランザクションが処理されている場合は、さらに複雑な状態になります。この場合は、2フェーズ・コミットが必要です。2フェーズ・コミットは、次のステップで構成されています。
準備: アプリケーションから各トランザクションに対し、OCITransPrepare()コールを発行します。トランザクションでは、現行の作業をコミットできるか(OCI_SUCCESS)、できないか(OCI_ERROR)を示す値を戻します。
コミット: 各OCITransPrepare()コールがOCI_SUCCESSの値を戻した場合は、アプリケーションから各トランザクションにOCITransCommit()コールを発行できます。このコールのデフォルトが1フェーズ・コミットであるため、適切な処理を行うためには、このコミット・コールのflagsパラメータにOCI_TRANS_TWOPHASEを明示的に設定する必要があります。
|
注意: トランザクションが読取り専用であることを示す必要がある場合、 OCITransPrepare()コールはOCI_SUCCESS_WITH_INFOを戻します。これは、コミットが不適切かつ不必要であることを意味します。 |
追加のコールOCITransForget()は、完了したトランザクションの情報をデータベースで「消す」ことを示します。このコールは、問題が起きて2フェーズ・コミットを異常終了する必要がある場合に使用します。サーバーでOCITransForget()コールを受け取ると、トランザクションに関する情報はすべて消去されます。
同じOracleデータベースに対し、複数のアプリケーションでグローバル・トランザクションの様々なブランチが使用される場合があります。そのようなトランザクションをコミットする前に、すべてのブランチを準備する必要があります。
大抵の場合、ブランチを使用しているアプリケーションにはそれぞれ固有のブランチを準備する役割があります。しかし、アーキテクチャによってはこの役割を外部のトランザクション・サービスに求めるものがあります。その場合、外部のトランザクション・サービスで、グローバル・トランザクションの各ブランチを準備してください。従来のOCITransPrepare()コールを使用すると、各ブランチを別々に準備する必要があるため、手間がかかります。OCITransMultiPrepare()コールを使用することで、クライアントからサーバーに送信するメッセージの数を軽減できます。このコールによって、1回のラウンドトリップで同じグローバル・トランザクションに関連する複数のブランチを準備できます。
ここでは、トランザクションOCIコールの使用方法について説明します。
次の表では、一連のOCIコールおよびその他の処理を、その処理結果とともに示しています。簡潔な表にするため、これらのコールのパラメータをすべてリストするのではなく、コールの流れを中心に解説します。
OCIの処理列は、OCIアプリケーションの活動やコールの内容を示します。XID列には、必要に応じて、トランザクション識別子がリストされます。フラグ列は、flagsパラメータに渡された値を示します。結果列は、コールの結果を説明します。
グローバル・トランザクション・ブランチおよび移行可能オープン接続の使用に関連する初期化パラメータには、次の2つがあります。
TRANSACTIONS− このパラメータでは、システム全体のグローバル・トランザクション・ブランチの最大数を指定します。一方、1つのグローバル・トランザクションの最大ブランチ数は8です。
OPEN_LINKS_PER_INSTANCE− このパラメータでは、移行可能オープン接続の最大数を指定します。移行可能オープン接続は、トランザクションをコミットした後に接続をキャッシュできるように、グローバル・トランザクションで使用します。これは、OPEN_LINKSパラメータとは対照的です。OPEN_LINKSは1セッションからの接続数を制御しますが、グローバル・トランザクションを使用するアプリケーションには適用できません。
次に、ステップのリストを示します。
表8-3 2フェーズ・コミット
| ステップ | OCIの処理 | XID | フラグ | 結果 |
|---|---|---|---|---|
|
1 |
OCITransStart() |
1234 |
|
新規の読取り専用トランザクションを開始する |
|
2 |
SQL UPDATE |
- |
- |
列を更新する |
|
3 |
OCITransDetach() |
- |
- |
トランザクションを連結解除する |
|
4 |
OCITransStart() |
1234 |
トランザクションを再開する |
|
|
5 |
SQL UPDATE |
- |
- |
- |
|
6 |
OCITransPrepare() |
- |
- |
トランザクションで2フェーズ・コミットを準備する |
|
7 |
OCITransCommit |
- |
トランザクションをコミットする |
ステップ4では、同じ権限を持つ別のプロセスからトランザクションを再開することもできます。
OCIアプリケーションで、複数のユーザーの認証とメンテナンスができます。
OCISessionBegin()コールは、サービス・コンテキスト・ハンドルに設定されたサーバー・セットに対して、ユーザーを認証します。これは、指定されたサーバー・ハンドルに対する最初のコールである必要があります。OCISessionBegin()は、このコールのサーバー・ハンドルおよびサービス・コンテキストによって指定された、ユーザーによるOracleサーバーへの接続を認証します。つまり、OCIServerAttach()によってサーバー・ハンドルを初期化した後、サーバーに対してユーザーを認証するためにOCISessionBegin()をコールする必要があります。
サーバー・ハンドルに対して、最初にOCISessionBegin()をコールしたとき、ユーザー・セッションは、移行可能モード(OCI_MIGRATE)では作成されません。サーバー・ハンドルに対してOCISessionBegin()をコールした後、アプリケーションではOCISessionBegin()を再度コールし、別のユーザー・セッション・ハンドルを、別の(または同じ)資格証明と別の(または同じ)操作モードを使用して初期化できます。アプリケーションでユーザーをOCI_MIGRATEモードで認証する場合、サービス・ハンドルは、移行不可能なユーザー・ハンドルにすでに関連付けられている必要があります。このユーザー・ハンドルのuseridは、移行可能なユーザー・セッションの所有者IDになります。移行可能なすべてのセッションは、移行不可能な親セッションを持つ必要があります。
OCI_MIGRATEを指定しない場合、ユーザー・セッション・コンテキストは、OCISessionBegin()で使用したサーバー・ハンドルでのみ使用できます。
OCI_MIGRATEモードを指定した場合、ユーザー認証は、その他のサーバー・ハンドルで設定できます。ただし、ユーザー・セッション・コンテキストは、同じデータベース・インスタンスを解決するサーバー・ハンドルでのみ使用できます。セキュリティ・チェックは、セッションの切替え中に行われます。
移行可能セッションを別のサーバー・ハンドルに切り替えることができるのは、セッションの所有者IDが、現在その同じサーバーに接続されている移行不可能なセッションのuseridと一致する場合のみです。
OCI_SYSDBA、OCI_SYSOPERおよびOCI_PRELIM_AUTH設定は、主ユーザー・セッション・コンテキストでのみ使用できます。
移行可能セッションは、環境ハンドルによって表される環境内のサーバー・ハンドルに切替えまたは移行できます。また、移行可能セッションは、同じプロセスまたは別のモードの別プロセス内にある、別の環境のサーバー・ハンドルに移行またはクローニングすることもできます。この移行またはクローニングを実行するには、次の手順を行う必要があります。
OCI_ATTR_MIGSESSIONを使用してセッション・ハンドルからセッションIDを抽出します。これは、コール元が変更する必要がないバイトの配列です。
このセッションIDを別のプロセスに移送します。
新しい環境内でセッション・ハンドルを作成し、OCI_ATTR_MIGSESSIONを使用してセッションIDを設定します。
OCISessionBegin()を実行します。結果のセッション・ハンドルは、完全に認証されています。
OCISessionBegin()へのコールに資格証明を与えるために、ユーザー・セッション・ハンドル・パラメータにおけるデータベース認証に対して、有効なユーザー名およびパスワードのペアを提供する必要があります。この方法では、OCIAttrSet()を使用して、ユーザー・セッション・ハンドルに対してOCI_ATTR_USERNAME属性およびOCI_ATTR_PASSWORD属性を設定します。これにより、OCI_CRED_RDBMSを指定してOCISessionBegin()がコールされます。
ユーザー・セッション・ハンドルがOCISessionEnd()によって終了した場合、ユーザー名とパスワード属性は変更されるため、OCISessionBegin()への次回以降のコールでは再利用できません。次回のOCISessionBegin()コールの前に新しい値を再設定する必要があります。
または、外部資格証明を与えることもできます。この方法では、OCISessionBegin()をコールする前に、ユーザー・セッション・ハンドルについて属性を設定する必要はありません。資格証明のタイプはOCI_CRED_EXTです。すでにOCI_ATTR_USERNAMEおよびOCI_ATTR_PASSWORDに値が設定してある場合、OCI_CRED_EXTを使用すると、これらの値は無視されます。
OCIPasswordChange()コールにより、アプリケーションを使用して、必要に応じてユーザーのデータベース・パスワードを変更できます。これは、OCISessionBegin()へのコールで、ユーザーのパスワードが期限切れであることを示すエラー・メッセージまたは警告が戻された場合に特に有効です。
アプリケーションでは、OCIPasswordChange()を使用して、ユーザー認証コンテキストを設定し、パスワードを変更できます。OCIPasswordChange()が未初期化サービス・コンテキストとともにコールされる場合は、サービス・コンテキストが確立され、旧パスワードを使用してユーザーのアカウントを認証し、次に新しいパスワードに変更されます。OCI_AUTHフラグを設定している場合、ユーザー・セッションは初期化された状態のままになります。OCI_AUTHフラグを設定していない場合、ユーザー・セッションは消去されます。
OCIPasswordChange()に渡したサービス・コンテキストがすでに初期化されている場合、OCIPasswordChange()では、指定のアカウントが旧パスワードを使用して認証され、旧パスワードが新パスワードに変更されます。この場合、どのフラグを設定していても、ユーザー・セッションは初期化された状態のままになります。
データベースへの接続にアプリケーションでパスワード資格証明を使用するような大規模なデプロイメントのかわりとして、このような資格証明をクライアント側のOracleウォレットに格納できます。Oracleウォレットは、認証や署名の資格証明を格納するために使用する、セキュアなソフトウェア・コンテナです。
クライアント側のOracleウォレットにデータベースのパスワード資格証明を格納すると、アプリケーション・コードやバッチ・ジョブ、スクリプトにユーザー名とパスワードを埋め込む必要がなくなります。このため、スクリプトやアプリケーション・コード内にクリアテキスト・パスワードを公開する危険性が減少し、ユーザー名やパスワードを変更するたびにコードを変更する必要がなくなるため、管理が単純化されます。さらに、アプリケーション・コードを変更する必要がないため、これらのユーザー・アカウントに対するパスワード管理ポリシーの実施も今までより簡単になります。
外部パスワード・ストアを使用するようにクライアントを構成すると、アプリケーションではパスワード認証を使用するデータベースの接続に次の構文を使用できます。
CONNECT /@database_alias
このCONNECT文では、データベース・ログイン資格証明を指定する必要はありません。かわりに、クライアントのウォレットでデータベース・ログイン資格証明が検索されます。
ユーザー・セッションをいくつかのサーバー接続に渡って多重化することによりユーザーのロードをアクティブに均衡化するトランザクション・サーバーでは、これらの接続をサーバー・グループにグループ化する必要があります。サーバー・グループを使用してこれらの接続を識別し、セッションを効率的で安全に管理できます。
OCIAttrSet()コールを使用してサーバー・グループ名を指定するには、OCI_ATTR_SERVER_GROUP属性を定義する必要があります。
OCIAttrSet ((void *) srvhp, (ub4) OCI_HTYPE_SERVER, (void *) group_name,
(ub4) strlen ((CONST char *) group_name),
(ub4) OCI_ATTR_SERVER_GROUP, errhp);
サーバー・グループ名は、最大30文字までの英数字文字列です。この属性は、OCIServerAttach()のコール後にのみ設定できます。OCI_ATTR_SERVER_GROUP属性は、そのコンテキストを使用して移行不可能なセッションを作成する前に、サーバー・コンテキストで設定する必要があります。セッションの作成が成功し、サーバーへの接続が確立した後は、サーバー・グループ名は変更できません。
あるサーバー・グループ内のサーバー上で作成されたすべての移行可能セッションは、同じサーバー・グループ内の他のサーバーにのみ移行が可能です。終了したサーバーは、サーバー・グループから削除されます。既存のサーバー・グループ内には、いつでも新しいサーバーを作成できます。
サーバー・グループの使用はオプションです。サーバー・グループが指定されない場合、サーバーはDEFAULTというサーバー・グループ内に作成されます。
デフォルト以外のサーバー・グループ内の、最初の移行不可能なセッションの所有者は、そのサーバー・グループの所有権となります。このサーバー・グループ内のすべてのサーバーについて、以降に作成されるすべての移行不可能なセッションは、そのサーバー・グループの所有者によって作成される必要があります。
サーバー・グループ機能は、専用サーバーを使用する場合に便利です。この機能は、共有サーバーには効果がありません。すべての共有サーバーはサーバー・グループDEFAULTに効率的に所属しています。
中間層アプリケーションでは、ブラウザ・クライアントからの要求が受信され、データベース接続、およびHTMLページを生成するかどうかが決定されます。アプリケーションには、1つのデータベース・セッションに複数の軽量ユーザー・セッションを含めることが可能です。これらの軽量セッションを使用すると、別のデータベース接続によるオーバーヘッドなしに各ユーザーが認証され、実ユーザーとしての識別情報が中間層を通して保たれます。
クライアントの認証が中間層で行われ、また、中間層の認証がデータベースで行われ、中間層がクライアントのかわりに稼働する権限を管理者から与えられているかぎり、クライアントのセキュリティに影響することなく、クライアントの識別情報をデータベース内で幅広くメンテナンスできます。
セキュアな3層アーキテクチャは、3つのトラスト・ゾーンを囲むように設計されています。
第1は、クライアント・トラスト・ゾーンです。Webアプリケーション・サーバーに接続しているクライアントは、パスワード、暗号トークンなどを使用して中間層で認証されます。この方法は、他のトラスト・ゾーンの確立に使用する方法とはまったく異なります。
第2のトラスト・ゾーンは、アプリケーション・サーバーです。データ・サーバーではアプリケーション・サーバーの識別情報を検証し、信頼できる場合、正しいクライアントの識別情報を渡します。
第3のトラスト・ゾーンでは、データ・サーバーが認可サーバーと対話し、クライアントとアプリケーション・サーバーに割り当てられているロールを取得します。
アプリケーション・サーバーがサーバーに接続すると、1次セッションが確立されます。アプリケーション・サーバーにより通常の方法でデータベースに対して認証が行われ、アプリケーション・サーバー・トラスト・ゾーンが作成されます。これで、アプリケーション・サーバーの識別情報はデータ・サーバーよって予約済になり、信頼されたことになります。
アプリケーションで、アプリケーション・サーバーに接続しているクライアントの識別情報を検証した場合は、第1のトラスト・ゾーンが作成されます。アプリケーション・サーバーでは、クライアントの要求に応じることができるように、クライアントに対するセッション・ハンドルが必要です。中間層プロセスではセッション・ハンドルを割り当て、OCIAttrSet()を使用して次のようなクライアント属性を設定します。
クライアントのデータベース・ユーザー名を設定するOCI_ATTR_USERNAME
プロキシ要求を行う認証済アプリケーションを示すOCI_ATTR_PROXY_CREDENTIALS
アプリケーション・サーバーがクライアントとして接続した後にアクティブ化するロールのリストを設定する場合は、OCISessionBegin()の前にOCIAttrSet()をコールできます。そのときに、属性OCI_ATTR_INITIAL_CLIENT_ROLESおよびロールのリストを含む文字列の配列を指定します。1回のラウンドトリップで、ロールが確立され、プロキシ機能が検証されます。アプリケーション・サーバーがクライアントの代理として機能することが許可されていない場合、あるいは特定のロールをアクティブ化することが許可されていない場合は、OCISessionBegin()コールが失敗します。
次の属性を使用すると、クライアントの外部名と初期権限を指定できます。アプリケーションでは、クライアントを識別または認証する代替方法として、これらの資格証明が使用されます。
クライアントにかわってアプリケーション・サーバーでセッションを開始した場合、OCI_CRED_RDBMS(データベースのユーザー名とパスワードが必要です)やOCI_CRED_EXT(外部に資格証明が与えられます)ではなく、OCI_CRED_PROXYをOCISessionBegin()のcredtパラメータに渡される値として使用します。
この属性を使用して、クライアントの認証に使用するアプリケーション・サーバーの資格証明を指定します。次の宣言およびOCIAttrSet()コールをコード化できます。
OCISession *session_handle;
OCISvcCtx *application_server_session_handle;
OCIError *error_handle;
...
OCIAttrSet((void *)session_handle, (ub4) OCI_HTYPE_SESSION,
(void *)application_server_session_handle, (ub4) 0,
OCI_ATTR_PROXY_CREDENTIALS, error_handle);
アプリケーションでは、データベースのユーザー名ではなく、X.509証明書に含まれる識別名をクライアントのログイン名として使用できます。
クライアントの識別名を渡すには、次のように中間層サーバーでOCIAttrSet()をコールし、OCI_ATTR_DISTINGUISHED_NAMEを渡します。
/* Declarations */
...
OCIAttrSet((void *)session_handle, (ub4) OCI_HTYPE_SESSION,
(void *)distinguished_name, (ub4) 0,
OCI_ATTR_DISTINGUISHED_NAME, error_handle);
OCI_ATTR_CERTIFICATEを使用した証明書ベースのプロキシ認証は、Oracle Databaseの今後のリリースではサポートされません。かわりに、OCI_ATTR_DISTINGUISHED_NAME属性またはOCI_ATTR_USERNAME属性を使用してください。この認証方法は、識別名の使用に似ています。X.509証明書全体が、中間層サーバーによってデータベースに渡されます。
証明書全体を渡すには、次のように中間層サーバーでOCIAttrSet()をコールし、OCI_ATTR_CERTIFICATEを渡します。
OCIAttrSet((void *)session_handle, (ub4) OCI_HTYPE_SESSION,
(void *)certificate, ub4 certificate_length,
OCI_ATTR_CERTIFICATE, error_handle);
アプリケーション・サーバーがOracleサーバーに接続する場合は、OCI_ATTR_INITIAL_CLIENT_ROLES属性を使用して、クライアントが所有するロールを指定します。一連のロールを使用可能にするために、属性、NULLで終了する文字列の配列および配列内の文字列の数を指定してOCIAttrSet()関数をコールします。
OCIAttrSet((void *)session_handle, (ub4) OCI_HTYPE_SESSION,
(void *)role_array, (ub4) number_of_strings,
OCI_ATTR_INITIAL_CLIENT_ROLES, error_handle);
多数の中間層アプリケーションがデータベースにアプリケーションとして接続し、中間層を使用してエンド・ユーザーの識別情報を追跡します。様々なデータベース・コンポーネントでこれらのエンド・ユーザーの追跡を統合するため、データベース・クライアントはいつでもセッション・ハンドルにクライアント識別子(アプリケーション・コンテキスト・ネームスペースUSERENVの事前定義済属性)を設定できます。OCIAttrSet()へのコールでOCIのOCI_ATTR_CLIENT_IDENTIFIER属性を使用します。サーバーへの次の要求で情報が伝播され、サーバー・セッションに格納されます。
グローバル・アプリケーション・コンテキストをサポートするために、クライアントは、いつでもセッション・ハンドルにCLIENT_IDENTIFIER(アプリケーション・コンテキスト・ネームスペースUSERENVの事前定義済属性)を設定できます。OCIAttrSet()へのコールでOCIのOCI_ATTR_CLIENT_IDENTIFIER属性を使用します。サーバーへの次の要求で情報が伝播され、サーバー・セッションに格納されます。
OCIAttrSet((void *)session_handle, (ub4) OCI_HTYPE_SESSION,
(void *)"janedoe", (ub4)strlen("janedoe"),
OCI_ATTR_CLIENT_IDENTIFIER, error_handle);
クライアントに複数のセッションがあるときは、同じクライアント識別子を使用して、各セッションに対してOCIAttrSet()を実行します。次のセッションを行うイベントでセッションが再接続されるときは、各プロセスに対してOCIAttrSet()を自動的に実行する必要があります。
インスタンス間で移行されるセッション
dblinkを使用したインスタンスにまたがるセッション
TAF PRECONNECTを使用して事前接続されたセッション
TAF BASICを使用するセッション
クライアント識別子は、CLIENT_IDENTIFIER列としてV$SESSIONにあるか、または次のSQL文とともにシステム・コンテキスト内にあります。
SELECT sys_context('userenv', 'client_identifier') FROM dual;
中間層では、データベース・サーバーに対して、認証自体を行うかわりにクライアントのパスワードを検証することでクライアントを認証するように要求できます。クライアント/サーバー接続と同じように見えますが、クライアントでは、データベース操作を実行するために、クライアントのシステムにOracleソフトウェアをインストールする必要はありません。アプリケーション・サーバーでは、クライアントのパスワードを使用するため、既存のOCI_ATTR_PASSWORD属性を使用してOCIAttrSet()に次の認証データを提供します。
OCIAttrSet((void *)session_handle, (ub4) OCI_HTYPE_SESSION, (void *)password,
(ub4)0, OCI_ATTR_PASSWORD, error_handle);
次に、中間層の例を示します。
...
*OCIEnv *environment_handle;
OCIServer *data_server_handle;
OCIError *error_handle;
OCISvcCtx *application_server_service_handle;
OraText *client_roles[2];
OCISession *first_client_session_handle, second_client_session_handle;
...
/*
** General initialization and allocation of contexts.
*/
(void) OCIInitialize((ub4) OCI_DEFAULT, (void *)0,
(void * (*)(void *, size_t)) 0,
(void * (*)(void *, void *, size_t))0,
(void (*)(void *, void *)) 0 );
(void) OCIEnvInit( (OCIEnv **) &environment_handle, OCI_DEFAULT, (size_t) 0,
(void **) 0 );
(void) OCIHandleAlloc( (void *) environment_handle, (void **) &error_handle,
OCI_HTYPE_ERROR, (size_t) 0, (void **) 0);
/*
** Allocate and initialize the server and service contexts used by the
** application server.
*/
(void) OCIHandleAlloc( (void *) environment_handle,
(void **)&data_server_handle, OCI_HTYPE_SERVER, (size_t) 0, (void **) 0);
(void) OCIHandleAlloc( (void *) environment_handle, (void **)
&application_server_service_handle, OCI_HTYPE_SVCCTX, (size_t) 0,
(void **) 0);
(void) OCIAttrSet((void *) application_server_service_handle,
OCI_HTYPE_SVCCTX, (void *) data_server_handle, (ub4) 0, OCI_ATTR_SERVER,
error_handle);
/*
** Authenticate the application server. In this case, external authentication is
** being used.
*/
(void) OCIHandleAlloc((void *) environment_handle,
(void **)&application_server_session_handle, (ub4) OCI_HTYPE_SESSION,
(size_t) 0, (void **) 0);
checkerr(error_handle, OCISessionBegin(application_server_service_handle,
error_handle, application_server_session_handle, OCI_CRED_EXT,
OCI_DEFAULT));
/*
** Authenticate the first client
** Note that no password is specified by the
** application server for the client as it is trusted.
*/
(void) OCIHandleAlloc((void *) environment_handle,
(void **)&first_client_session_handle, (ub4) OCI_HTYPE_SESSION,
(size_t) 0,(void **) 0);
(void) OCIAttrSet((void *) first_client_session_handle,
(ub4) OCI_HTYPE_SESSION, (void *) "jeff", (ub4) strlen("jeff"),
OCI_ATTR_USERNAME, error_handle);
/*
** In place of specifying a password, pass the session handle of the application
** server instead.
*/
(void) OCIAttrSet((void *) first_client_session_handle,
(ub4) OCI_HTYPE_SESSION, (void *) application_server_session_handle,
(ub4) 0, OCI_ATTR_PROXY_CREDENTIALS, error_handle);
(void) OCIAttrSet((void *) first_client_session_handle,
(ub4) OCI_HTYPE_SESSION, (void *) "jeff@VeryBigBank.com",
(ub4) strlen("jeff@VeryBigBank.com"), OCI_ATTR_EXTERNAL_NAME,
error_handle);
/*
** Establish the roles that the application server can use as the client.
*/
client_roles[0] = (OraText *) "TELLER";
client_roles[1] = (OraText *) "SUPERVISOR";
(void) OCIAttrSet((void *) first_client_session_handle,
OCI_ATTR_INITIAL_CLIENT_ROLES, error_handle);
checkerr(error_handle, OCISessionBegin(application_server_service_handle,
error_handle, first_client_session_handle, OCI_CRED_PROXY, OCI_DEFAULT));
/*
** To start a session as another client, the application server does the
** following. It must be
** noted this code is unchanged from the current way of doing session switching.
*/
(void) OCIHandleAlloc((void *) environment_handle,
(void **)&second_client_session_handle, (ub4) OCI_HTYPE_SESSION,
(size_t) 0, (void **) 0);
(void) OCIAttrSet((void *) second_client_session_handle,
(ub4) OCI_HTYPE_SESSION, (void *) "mutt", (ub4) strlen("mutt"),
OCI_ATTR_USERNAME, error_handle);
(void) OCIAttrSet((void *) second_client_session_handle,
(ub4) OCI_HTYPE_SESSION, (void *) application_server_session_handle,
(ub4) 0, OCI_ATTR_PROXY_CREDENTIALS, error_handle);
(void) OCIAttrSet((void *) second_client_session_handle,
(ub4) OCI_HTYPE_SESSION, (void *) "mutt@VeryBigBank.com",
(ub4) strlen("mutt@VeryBigBank.com"), OCI_ATTR_EXTERNAL_NAME,
error_handle);
/*
** Note that the application server has not specified any initial roles to have
** as the second client.
*/
checkerr(error_handle, OCISessionBegin(application_server_service_handle,
error_handle, second_client_session_handle, OCI_CRED_PROXY, OCI_DEFAULT));
/*
** To switch to the first user, the application server applies the session
** handle obtained by the first
** OCISessionBegin() call. This is the same as is currently done.
*/
(void) OCIAttrSet((void *)application_server_service_handle,
(ub4) OCI_HTYPE_SVCCTX, (void *)first_client_session_handle,
(ub4)0, (ub4)OCI_ATTR_SESSION, error_handle);
/*
** After doing some operations, the application server can switch to
** the second client. That
** is be done by the following call:
*/
(void) OCIAttrSet((void *)application_server_service_handle,
(ub4) OCI_HTYPE_SVCCTX,
(void *)second_client_session_handle, (ub4)0, (ub4)OCI_ATTR_SESSION,
error_handle);
/*
** and then do operations as that client
*/
...
次の属性を使用して、サーバー・ラウンドトリップを含まない、サーバーのコール・タイムを測定します。また、これらの属性は、サーバーに対してラウンドトリップを1回発生させる、PL/SQLパッケージDBMS_APPLICATION_INFOを使用して設定できます。OCIを使用して属性を設定すると、ラウンドトリップは発生しません。
boolean変数をTRUEまたはFALSEに設定します。次に、OCIAttrSet()をコールしてこの属性を設定した後で、サーバーにより各コール・タイムを測定します。変数をTRUEに設定した時間とFALSEに設定した時間の間のすべてのサーバー・タイムが測定されます。
最後のサーバー・コールの経過時間(マイクロ秒)は、この属性を使用してOCIAttrGet()をコールすると、ub8変数で戻されます。次のコードの断片により、実行方法を示します。
boolean enable_call_time;
ub8 call_time;
...
enable_call_time = TRUE;
OCIAttrSet(session, OCI_HTYPE_SESSION, (void *)&enable_call_time,
(ub4)0, OCI_ATTR_COLLECT_CALL_TIME,
(OCIError *)error_handle);
OCIStmtExecute(...);
OCIAttrGet(session, OCI_HTYPE_SESSION, (void *)&call_time,
(ub4)0, OCI_ATTR_CALL_TIME,
(OCIError *)error_handle);
...
外部で初期化されたコンテキストとは、属性をOCIから初期化できるアプリケーション・コンテキストです。SQL文のCREATE CONTEXTでINITIALIZED EXTERNALLYオプションを使用して、サーバーにコンテキスト・ネームスペースを作成します。
これで、OCIAttrSet()とOCISessionBegin()を使用してセッションを確立するときに、OCIインタフェースを初期化できます。CREATE CONTEXT文で指定されたPL/SQLパッケージのみを使用して、ネームスペース内に属性を書き込む後続のコマンドを発行します。
デフォルト値と他のセッション属性は、OCISessionBegin()コールを使用して設定できるため、サーバー・ラウンドトリップ回数が削減されます。
クライアント・アプリケーションを開発する場合、認証を行う前に、OCI関数に次の属性を使用して、明示的にアプリケーション・コンテキストをセッション・ハンドルに設定できます。
この属性をOCIAttrSet()コールで使用して、コンテキストの配列サイズを任意の数のコンテキスト属性で初期化します。
OCIAttrSet(session, (ub4) OCI_HTYPE_SESSION,
(void *)&size, (ub4)0, OCI_ATTR_APPCTX_SIZE, error_handle);
この属性をOCIAttrGet()コールで使用して、セッションに対するアプリケーション・コンテキスト・リスト記述子のハンドルを入手します。(ctxl_descパラメータのデータ型は、OCIParam *であることが必要です。)
OCIAttrGet(session, (ub4) OCI_HTYPE_SESSION,
(void *)&ctxl_desc, (ub4)0, OCI_ATTR_APPCTX_LIST, error_handle);
OCIParamGet()のコールでそのアプリケーション・コンテキスト・リスト記述子を使用し、i番目のアプリケーション・コンテキストの個々の記述子を取得します。
OCIParamGet(ctxl_desc, OCI_DTYPE_PARAM, error_handle,(void **)&ctx_desc, i);
次の属性を使用して、アプリケーション・コンテキストの適切な値を設定します。
OCI_ATTR_APPCTX_NAMEを使用して、コンテキスト・ネームスペースを設定します。これは、有効なSQL識別子であることが必要です。
OCI_ATTR_APPCTX_ATTRを使用して、指定されたコンテキストに属性名を設定します。属性名は、最大30バイトの大/小文字区別がない文字列です。
各ネームスペースに複数の属性を保持でき、各属性には1つの値を設定します。次に、値を設定するために使用できるコールを示します。
OCIAttrSet(ctx_desc, OCI_DTYPE_PARAM,
(void *)ctx_name, sizeof(ctx_name), OCI_ATTR_APPCTX_NAME, error_handle);
OCIAttrSet(ctx_desc, OCI_DTYPE_PARAM,
(void *)attr_name, sizeof(attr_name), OCI_ATTR_APPCTX_ATTR, error_handle);
OCIAttrSet(ctx_desc, OCI_DTYPE_PARAM,
(void *)value, sizeof(value), OCI_ATTR_APPCTX_VALUE, error_handle);
アプリケーション・コンテキスト操作はVARCHAR2データ型に基づいているため、キャラクタ・タイプのみサポートされていることに注意してください。
OCISessionBegin()をコールすると、セッション・ハンドルに設定されたコンテキストがサーバーにプッシュされます。このサーバー・セッションに追加コンテキストは伝播されません。次のコード例は、これらのコールおよび属性の使用方法を示します。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <oci.h>
static OraText *username = (OraText *) "HR";
static OraText *password = (OraText *) "HR";
static OCIEnv *envhp;
static OCIError *errhp;
int main(/*_ int argc, char *argv[] _*/);
static sword status;
int main(argc, argv)
int argc;
char *argv[];
{
OCISession *authp = (OCISession *) 0;
OCIServer *srvhp;
OCISvcCtx *svchp;
OCIDefine *defnp = (OCIDefine *) 0;
void *parmdp;
ub4 ctxsize;
OCIParam *ctxldesc;
OCIParam *ctxedesc;
OCIEnvCreate(&envhp, OCI_DEFAULT, (void *)0, 0, 0, 0,
(size_t)0, (void *)0);
(void) OCIHandleAlloc( (void *) envhp, (void **) &errhp, OCI_HTYPE_ERROR,
(size_t) 0, (void **) 0);
/* server contexts */
(void) OCIHandleAlloc( (void *) envhp, (void **) &srvhp, OCI_HTYPE_SERVER,
(size_t) 0, (void **) 0);
(void) OCIHandleAlloc( (void *) envhp, (void **) &svchp, OCI_HTYPE_SVCCTX,
(size_t) 0, (void **) 0);
(void) OCIServerAttach( srvhp, errhp, (OraText *)"", strlen(""), 0);
/* set attribute server context in the service context */
(void) OCIAttrSet( (void *) svchp, OCI_HTYPE_SVCCTX, (void *)srvhp,
(ub4) 0, OCI_ATTR_SERVER, (OCIError *) errhp);
(void) OCIHandleAlloc((void *) envhp, (void **)&authp,
(ub4) OCI_HTYPE_SESSION, (size_t) 0, (void **) 0);
/****************************************/
/* set app ctx size to 2 because we want to set up 2 application contexts */
ctxsize = 2;
/* set up app ctx buffer */
(void) OCIAttrSet((void *) authp, (ub4) OCI_HTYPE_SESSION,
(void *) &ctxsize, (ub4) 0,
(ub4) OCI_ATTR_APPCTX_SIZE, errhp);
/* retrieve the list descriptor */
(void) OCIAttrGet((void *)authp, (ub4) OCI_HTYPE_SESSION,
(void *)&ctxldesc, 0, OCI_ATTR_APPCTX_LIST, errhp );
/* retrieve the 1st ctx element descriptor */
(void) OCIParamGet(ctxldesc, OCI_DTYPE_PARAM, errhp, (void**)&ctxedesc, 1);
(void) OCIAttrSet((void *) ctxedesc, (ub4) OCI_DTYPE_PARAM,
(void *) "HR", (ub4) strlen((char *)"HR"),
(ub4) OCI_ATTR_APPCTX_NAME, errhp);
(void) OCIAttrSet((void *) ctxedesc, (ub4) OCI_DTYPE_PARAM,
(void *) "ATTR1", (ub4) strlen((char *)"ATTR1"),
(ub4) OCI_ATTR_APPCTX_ATTR, errhp);
(void) OCIAttrSet((void *) ctxedesc, (ub4) OCI_DTYPE_PARAM,
(void *) "VALUE1", (ub4) strlen((char *)"VALUE1"),
(ub4) OCI_ATTR_APPCTX_VALUE, errhp);
/* set second context */
(void) OCIParamGet(ctxldesc, OCI_DTYPE_PARAM, errhp, (void**)&ctxedesc, 2);
(void) OCIAttrSet((void *) ctxedesc, (ub4) OCI_DTYPE_PARAM,
(void *) "HR", (ub4) strlen((char *)"HR"),
(ub4) OCI_ATTR_APPCTX_NAME, errhp);
(void) OCIAttrSet((void *) ctxedesc, (ub4) OCI_DTYPE_PARAM,
(void *) "ATTR2", (ub4) strlen((char *)"ATTR2"),
(ub4) OCI_ATTR_APPCTX_ATTR, errhp);
(void) OCIAttrSet((void *) ctxedesc, (ub4) OCI_DTYPE_PARAM,
(void *) "VALUE2", (ub4) strlen((char *)"VALUE2"),
(ub4) OCI_ATTR_APPCTX_VALUE, errhp);
/****************************************/
(void) OCIAttrSet((void *) authp, (ub4) OCI_HTYPE_SESSION,
(void *) username, (ub4) strlen((char *)username),
(ub4) OCI_ATTR_USERNAME, errhp);
(void) OCIAttrSet((void *) authp, (ub4) OCI_HTYPE_SESSION,
(void *) password, (ub4) strlen((char *)password),
(ub4) OCI_ATTR_PASSWORD, errhp);
OCISessionBegin ( svchp, errhp, authp, OCI_CRED_EXT, (ub4) OCI_DEFAULT);
}
アプリケーション・コンテキストでは、データベースのクライアント(中間層のアプリケーションなど)が、任意のセッション・データを設定し、これを1回のラウンドトリップのみで、それぞれに実行する文でサーバーに送信できるようにします。このデータは、サーバーによって文の実行前にセッション・コンテキストに格納されます。この格納されたデータは、問合せまたはDML操作の制限に使用できます。ビュー、トリガー、VPDポリシーまたはPL/SQLストアド・プロシージャなどのすべてのデータベース機能では、セッション・データを使用して操作を制限できます。
パブリックで書込み可能なネームスペースnmが作成されます。
CREATE CONTEXT nm USING hr.package1;
ネームスペースでグループ化されたデータを変更するには、指定されたPL/SQLパッケージ(hr.package1)を実行する必要があります。ただし、ユーザー・セッションではこの情報の問合せに権限は必要ありません。
ユーザー・セッションに格納された可変長のアプリケーション・コンテキスト・データの形式は、コンテキスト・ネームスペースの下でグループ化された属性と値のペアです。
たとえば、人事管理アプリケーションなどでユーザー・セッションにエンドユーザーの職責情報を格納する必要がある場合、nmネームスペースと、「responsibility」という、「manager」や「accountant」といった値を割り当てるための属性を作成できます。このマニュアルでは、これを設定操作と呼びます。
アプリケーションでは、nmネームスペース内のresponsibility属性の値を消去する場合、この属性をNULLまたは空の文字列に設定します。このマニュアルでは、これを消去操作と呼びます。
アプリケーションでは、nmネームスペース内のすべての情報を消去する場合、全消去の操作の一部としてネームスペース情報をサーバーに送信します。このマニュアルでは、これをネームスペース内の全消去操作と呼びます。
ネームスペースに対してパッケージ-セキュリティが定義されていない場合は、このネームスペースはクライアントのネームスペースとみなされ、すべてのOCIクライアントがこのネームスペースのデータをサーバーに転送できるようになります。権限やパッケージのセキュリティ・チェックは行われません。
アプリケーション・コンテキスト・データのネットワーク・トランスポートは、サーバーへの1回のラウンドトリップで行われますます。
OCIAppCtxSet()を使用して、CLIENTCONTEXTネームスペース内のresponsibility属性に対して一連の設定操作を行います。この情報がサーバーに転送されると、この最新値がネームスペース内の特定の属性よりも優先されます。CLIENTCONTEXTネームスペース内のresponsibility属性の値を「manager」から「vp」に変更するには、クライアント側で、次に示したコードの断片を使用します。この情報がサーバーに転送されると、サーバーでは、CLIENTCONTEXTネームスペース内のresponsibility属性に対して最新値の「vp」が表示されます。
err = OCIAppCtxSet((void *) sesshndl,(void *)"CLIENTCONTEXT",(ub4) 13,
(void *)"responsibility", 14
(void *)"manager", 7, errhp, OCI_DEFAULT);
err = OCIAppCtxSet((void *) sesshndl, (void*)"CLIENTCONTEXT", 13,
(void *)"responsibility", 14,(void *)"vp",2, errhp,
OCI_DEFAULT);
クライアントのネームスペース内の特定の属性情報を消去できます。次のように、OCIAppCtxSet()関数を使用して、属性の値をNULLまたは空の文字列に設定して消去します。
(void) OCIAppCtxSet((void *) sesshndl, (void *)"CLIENTCONTEXT", 13,
(void *)"responsibility", 14, (void *)0, 0,errhp,
OCI_DEFAULT);
または、
(void) OCIAppCtxSet((void *) sesshndl, (void *)"CLIENTCONTEXT", 13
(void *)"responsibility", 14, (void *)"", 0,errhp,
OCI_DEFAULT);
特定のクライアント・ネームスペース内のすべてのコンテキスト情報は、OCIAppCtxClearAll() 関数を使用して消去できます。このコンテキスト情報は、次のネットワーク・トランスポート時に、サーバー側ユーザー・セッションでも消去されます。
クライアント・アプリケーションでは、いくつかの設定操作の後ネームスペース内で全消去操作を行うと、ネームスペース内のすべての属性値でこの全消去操作の前に設定されたものは、クライアント側およびサーバー側でクリーン・アップされます。全消去操作の後に行われた設定操作のみが、サーバー側で反映されます。クライアント側では、次のようになります。
err = OCIAppCtxSet((void *) sesshndl,(void *)"CLIENTCONTEXT", 13,
(void *)"responsibility", 14,
(void *)"manager", 7,errhp, OCI_DEFAULT);
err = OCIAppCtxClearAll((void *) sesshndl, (void *)"CLIENTCONTEXT", 13, errhp,
OCI_DEFAULT);
err = OCIAppCtxSet((void *) sesshndl, (void*)"CLIENTCONTEXT",13
(void *)"office",6, (void *)"2op123", 5, errhp, OCI_DEFAULT);
全消去操作は、ネームスペースCLIENTCONTEXT内の全消去操作より前に行われた操作で設定されたすべての情報を消去します(responsibilityの属性値「manager」はクリーン・アップされます)。その後に設定された情報は、サーバー側で反映されます。
アプリケーションでは、OCIStmtExecute()コールのアプリケーション・コンテキスト情報をサーバーに送信できます。また、DBMS_SESSIONパッケージを実行することによって、このコール時に同じコンテキスト情報の変更も試行できます。
通常、サーバー側では、転送された情報が最初に処理され、メイン・コールは後で処理されます。この動作は、アプリケーション・コンテキストのネットワーク・トランスポートにも当てはまります。
この両方が同じクライアント・ネームスペースおよび属性セットへの書込みである場合、メイン・コールの情報により、最初のネットワークのトランスポート・メカニズムによって設定された情報が上書きされます。ネットワーク・トランスポートのコールでエラーが発生した場合、メイン・コールは実行されないことに注意してください。
メイン・コールで発生したエラーは、ネットワーク・トランスポートのコール処理には影響しません。ネットワーク・トランスポートがいったん処理されると、メイン・コールでエラーが発生しても、ネットワーク・トランスポートを元に戻す方法はありません。このエラーは、(OCI関数によって)コール元にレポートされるときに、汎用ORAエラーとしてレポートされます。現時点では、ネットワーク・トランスポートで発生したエラーとメイン・コールで発生したエラーとを識別する簡単な方法はありません。クライアントでは、メイン・コールで発生したエラーによりラウンドトリップ・ネットワーク処理を元に戻すことは考えずに、適切な例外処理メカニズムを実装して、非一貫性が発生しないようにしてください。
次のセキュリティ拡張機能では、init.oraファイルまたはsqlnet.oraファイル内の構成済パラメータを使用します(後者のファイルは、該当する機能について具体的に示されています)。詳細は、『Oracle Databaseセキュリティ・ガイド』を参照してください。これらの初期化パラメータは、データベースの全インスタンスに適用されます。
認証前に(OCIServerAttach()をコールした後に接続済サーバー・ハンドルで)OCIServerVersion()を発行して、データベースのバージョンを取得できます。認証前にデータベース・バージョン文字列が開示されないようにするには、初期化パラメータSEC_RETURN_SERVER_RELEASE_BANNERをNOに設定します。たとえば、次のようにします。
SEC_RETURN_SERVER_RELEASE_BANNER = NO
これにより、リリース11.1以降の全リリースおよびパッチ・セットの場合は次の文字列が表示されます。
Oracle Database 11g Release 11.1.0.0.0 - Production
SEC_RETURN_SERVER_RELEASE_BANNERをYESに設定すると、現行のバナーが表示されます。リリース11.2.0.2をインストール済の場合は、次のバナーが表示されます。
Oracle Database 11g Enterprise Edition Release 11.2.0.2 - Production
この機能は、リリース11.1以降のサーバーとすべてのバージョンのクライアントで動作します。
次のシステム全体のパラメータはsqlnet.oraにあり、不正なアクセスとユーザー・アクションに可能な監査に関する警告をユーザーに対して表示します。これらの機能は、リリース11.1以降のサーバーおよびクライアントで使用可能です。バナーのコンテンツは、データベース管理者が作成するテキスト・ファイルにあります。アクセス・バナーの構文は、次のとおりです。
SEC_USER_UNAUTHORIZED_ACCESS_BANNER = file_path1
file_path1はテキスト・ファイルのパスです。このバナーを取得するには、OCIServerAttach()またはOCISessionGet()のコール後に、サーバー・ハンドルから属性OCI_ATTR_ACCESS_BANNERの値を取得します。
監査バナーの構文は、次のとおりです。
SEC_USER_AUDIT_ACTION_BANNER = file_path2
file_path2はテキスト・ファイルのパスです。このバナーを取得するには、OCISessionBegin()、OCISessionGet()、OCILogon()またはOCILogon2()のコール後に、セッション・ハンドルから属性OCI_ATTR_AUDIT_BANNERの値を取得します。
スレッドは大きなプロセス内に存在する軽量のプロセスです。複数のスレッドで同一コードおよび同一データ・セグメントを共有しますが、プログラム・カウンタ、マシン・レジスタおよびスタックはスレッドごとにあります。グローバル変数および静的変数は、すべてのスレッドに共通です。そのため、1つのアプリケーション内で、複数のスレッドによるこれらの変数へのアクセスを管理する相互排他メカニズムが必要です。
スレッドは、一度作成されると他のスレッドとは非同期的に動作します。このため、順序を気にせずに共通のデータ要素にアクセスし、OCIコールを実行できます。このようにしてデータ要素に共有アクセスを行うため、アクセスされるデータの完全性を維持する同期化メカニズムが必要です。
データ・アクセスを管理するメカニズムでは、mutexes(相互排他ロック)のフォームを使用します。これは、ユーザーには不透明な共有内部データにアクセスする複数のスレッド間で競合が発生しないように実装されています。OCIでは、mutexはそれぞれの環境ハンドルに付与されます。
Oracleデータベース・サーバーおよびOCIライブラリのスレッド・セーフティ機能により、開発者はOCIをマルチスレッド環境で使用できます。スレッド・セーフティ機能を使用すると、副作用なしに、OCIのコードを、OCIコールを実行する複数のスレッドから再入可能にできます。
OCIにスレッド・セーフティを実装すると、次の利点があります。
複数のスレッドでOCIコールを実行した場合も、単一のスレッドで連続するコールを実行した場合と同じ結果になります。
複数のスレッドでOCIコールを実行した場合は、スレッド間で副作用がありません。
マルチスレッド・プログラムを作成しない場合でも、スレッド・セーフのOCIコールを使用してもパフォーマンスが低下しません。
複数のスレッドを使用すると、プログラムのパフォーマンスが向上します。パフォーマンスが向上するのは、別々のプロセッサでスレッドを同時実行するマルチプロセッサ・システム、および低速操作と高速操作の間でオーバーラップが発生するシングル・プロセッサ・システムの場合です。
クライアント/サーバー・アプリケーションでは、クライアントをマルチスレッド・プログラムにできます。クライアント/サーバー以外のマルチスレッド・アプリケーションの典型は、3層アーキテクチャまたはクライアント/エージェント/サーバー・アーキテクチャのアプリケーションです。このアーキテクチャでは、クライアントが関係するのは表示サービスのみです。エージェント、すなわちアプリケーション・サーバーは、クライアント・アプリケーションのアプリケーション・ロジックを処理します。一般的には、この関係は多対1の関係で、複数のクライアントが同一のアプリケーション・サーバーを共有します。
この場合のサーバー層は、データベースです。このアプリケーション・サーバー、すなわちエージェントは、それぞれのスレッドが1つのクライアント・アプリケーションに対してサービスを提供することから、マルチスレッド・アプリケーション・サーバーとして最適です。Oracle環境では、このアプリケーション・サーバーはOCIプログラムまたはプリコンパイラ・プログラムです。
スレッド・セーフティを利用するには、アプリケーションをスレッド・セーフなオペレーティング・システム上で実行する必要があります。アプリケーションをマルチスレッド環境で実行するように指定するには、そのアプリケーションからmodeパラメータの値としてOCI_THREADEDを使用して、OCIEnvNlsCreate()をコールします。
OCIEnvNlsCreate()の後続のコールすべてにOCI_THREADEDを指定する必要があります。
|
注意: スレッド・セーフでないオペレーティング・システムで実行するアプリケーションの場合は、 OCIInitialize()にも、OCIEnvNlsCreate()にもOCI_THREADEDの値を渡さないでください。 |
シングル・スレッド・アプリケーションでは、オペレーティング・システムがスレッド・セーフであるかどうかにかかわらず、OCIInitialize()またはOCIEnvNlsCreate()にOCI_DEFAULTの値を渡す必要があります。シングル・スレッド・アプリケーションをOCI_THREADEDモードで実行すると、パフォーマンスが低下する可能性があります。
マルチスレッド・アプリケーションがスレッド・セーフなオペレーティング・システム上で動作している場合は、OCIライブラリで、アプリケーションのmutexが各環境ハンドルに対して管理されます。modeパラメータのOCI_NO_MUTEXの値をOCIEnvCreate()コールに指定すると、アプリケーションではこの機能を上書きし、独自のmutexスキームを採用することもできます。
想定されるのは、次のシナリオです。このシナリオは、各環境ハンドルに存在する接続の数、および各接続で作成されるスレッドの数によって変わります。
アプリケーションに複数の環境ハンドルがあっても、それぞれの環境ハンドルにスレッドが1つの場合は、mutexは必要ありません。
OCI_THREADEDモードで実行しているアプリケーションに1つ以上の環境ハンドルがあり、それぞれの環境ハンドルに複数の接続がある場合は、次の方法の中から選択できます。
OCIEnvNlsCreate()のmodeに対してOCI_NO_MUTEXの値を渡します。アプリケーション側で、同一環境ハンドルで実行するOCIコールをmutex化する必要があります。この方法には、アプリケーションの設計に対してmutexスキームを最適化できるという利点があります。この方法では、1つの環境ハンドル接続で必ず一度に1つのOCIコールを処理するようにプログラミングする必要があります。
OCIEnvNlsCreate()のmodeに対してOCI_DEFAULTの値を渡します。OCIライブラリが、同一環境ハンドルの各OCIコールに対して自動的にmutexを取得します。
OCIThreadパッケージには、通常使用するスレッドの基本形が多数用意されています。様々なオペレーティング・システムに固有のスレッド機能に対して、移植可能なインタフェースも備えています。ただし、固有のスレッド機能を持たないオペレーティング・システムでは、スレッドが実装されません。
OCIThreadでは、移植可能な実装が提供されません。固有のマルチスレッド機能に対する移植可能なカバーのセットとしてのみ機能します。したがって、マルチスレッドのための固有のサポートが用意されていないオペレーティング・システムで対応できる実装は、OCIThreadパッケージの一部に限られます。このため、OCIThreadのすべての機能を使用する製品は、すべてのオペレーティング・システムには移植されません。すべてのオペレーティング・システムに移植する必要のある製品は、OCIThread機能のサブセットを選択する必要があります。
OCIThread APIは、主に3つの部分で構成されます。ここでは、各部分について簡単に説明します。それぞれの部分の詳細は、これ以降の項で説明します。
初期化および終了。これらのコールでは、OCIThreadコンテキストの初期化と終了を行います。このコンテキストは、他のOCIThreadコールで必須です。
OCIThreadでは、OCIThreadがマルチスレッド・アプリケーション内で使用されているときにのみ、プロセス初期化関数であるOCIThreadProcessInit()のコールが必要です。シングル・スレッド・アプリケーションでOCIThreadProcessInit()をコールしなくても、エラーになりません。
OCIThreadInit()をコールするたびに、常に同じOCIThreadコンテキストが戻されます。各OCIThreadInit()コールは、結果的にOCIThreadTerm()コールに合致する必要があります。
非アクティブなスレッドの基本形。非アクティブなスレッドの基本形を使用して、相互排他ロック(mutex)、スレッドIDおよびスレッド固有のデータ・キーを操作します。これらの基本形が非アクティブと呼ばれる理由は、仕様上は複数のスレッドの存在が可能ですが、複数のスレッドを必要としないためです。これらの基本形は、シングル・スレッド環境とマルチスレッド環境の両方の仕様に応じて実装できます。このため、これらの基本形のみを使用するOCIThreadクライアントでは、複数のスレッドを使用しなくても正常に機能します。コードを分岐せずにシングル・スレッド環境を稼働することができます。
アクティブなスレッドの基本形。アクティブなスレッドの基本形では、スレッドの作成、終了および操作を行います。これらの基本形がアクティブと呼ばれる理由は、マルチスレッド環境以外では使用できないためです。これらの基本形の仕様では、複数のスレッドが明示的に要求されます。マルチスレッド環境であるかどうかを実行時に判断する必要がある場合は、OCIThreadのアクティブ基本形を使用する前に、OCIThreadIsMulti()をコールします。
同じアプリケーションをシングル・スレッド・オペレーティング・システムで実行するように修正するには、コードを分岐する必要があります。ソース・ファイルのアプリケーションを分岐するか、あるいはOCIThreadIsMulti()コールを使用して実行時に分岐します。
この項で説明する型および関数は、OCIThreadパッケージの初期化および終了に関係しています。OCIThreadの機能を使用する場合は、あらかじめ初期化する必要があります。
初期化関数と終了関数の外見上の動作は、OCIThreadをシングル・スレッド環境で使用した場合もマルチスレッド環境で使用した場合も同じになります。表8-6には、スレッド初期化関数および終了関数がリストされています。
表8-6 初期化マルチスレッド関数および終了マルチスレッド関数
| 関数 | 用途 |
|---|---|
|
|
OCIThreadプロセスの初期化を実行します。 |
|
|
OCIThreadコンテキストを初期化します。 |
|
|
OCIThreadレイヤーを終了してコンテキストのメモリーを解放します。 |
|
|
アプリケーションがマルチスレッド環境とシングル・スレッド環境のどちらで動作しているかを、コール元に通知します。 |
非アクティブなスレッドの基本形は、mutex、スレッドID、およびスレッド固有のデータを扱います。これらの基本形の仕様では、複数のスレッドが存在する必要がないため、マルチスレッドとシングル・スレッドの両方のオペレーティング・システムで使用できます。表8-7には、非アクティブなスレッドの実装に使用する関数がリストされています。
表8-7 非アクティブなスレッドの基本形
| 関数 | 用途 |
|---|---|
|
|
mutexの割当ておよび初期化を行います。 |
|
|
mutexの破棄および割当て解除を行います。 |
|
|
コールが行われたスレッドに対してmutexを取得します。 |
|
|
mutexを解放します。 |
|
|
キーの割当ておよび初期化を行います。 |
|
|
キーの破棄および割当て解除を行います。 |
|
|
コール側スレッドのキーの現在の値を取得します。 |
|
|
コール側スレッドのキー値を設定します。 |
|
|
スレッドIDの割当ておよび初期化を行います。 |
|
|
スレッドIDの破棄および割当て解除を行います。 |
|
|
スレッドIDの設定を変更します。 |
|
|
スレッドIDをNULLにします。 |
|
|
コール側スレッドのスレッドIDを取り出します。 |
|
|
2つのスレッドIDが同じスレッドを表すかどうかを調べます。 |
|
|
スレッドIDが |
OCIThreadMutexデータ型はmutexの表現に使用されます。このmutexを使用すると、次のことを確認できます。
特定のデータ・セットは、同時に1つのスレッドのみからアクセスされます。
コードの特定の重要セクションを、同時に複数のスレッドでは実行しません。
mutexポインタは、クライアント構造またはスタンドアロン変数の一部として宣言できます。これらのポインタは、使用する前にOCIThreadMutexInit()により初期化する必要があります。不要になった場合は、OCIThreadMutexDestroy()を使用して破棄する必要があります。
OCIThreadMutexAcquire()を使用して、スレッドからmutexを取得できます。この場合、同時に複数のスレッドによって特定のmutexが保持されることはありません。mutexを保持しているスレッドは、OCIThreadMutexRelease()をコールすると解放できます。
OCIThreadKeyデータ型は、スレッド固有の値を持ったプロセス全体の変数とみなすことができます。つまり、1つのプロセスのすべてのスレッドで特定のキーを使用できます。ただし、各スレッドでは、他のスレッドとは別個にそのキーの検査または修正を行うことができます。スレッドがキーを検査したときに検出する値は、そのスレッドがそのキーに対して最後に設定した値と常に同じです。他のスレッドによって設定されたキーの値は検出されません。キーによって保持される値のデータ型は、void *汎用ポインタです。
キーは、OCIThreadKeyInit()を使用して作成できます。キー値はすべてのスレッドに対して、NULLに初期化されます。
スレッドからは、OCIThreadKeySet()を使用してキーの値を設定できます。また、OCIThreadKeyGet()を使用してキーの値を取得できます。
OCIThreadキー関数によって、スレッド固有のデータの保存および取出しを行うことができます。クライアントでスレッド・プールを管理し、スレッドごとに異なるタスクを割り当てる場合、OCIThreadキー関数を使用してタスクに関連付けられたデータを保存するのが適切でないことがあります。
失敗する場合のシナリオを次に示します。スレッドが割り当てられて、タスクの初期化が実行されます。初期化中に、タスクではOCIThreadキー関数を使用して、データをスレッドに格納します。初期化後、そのスレッドはスレッド・プールに戻されます。次に、スレッド・プール・マネージャでは、そのタスクで一定の操作を実行するために、別のスレッドを割り当てます。また、そのタスクでは初期化時に格納したデータを取り出す必要があります。このタスクは別のスレッド内で実行しているので、そのデータを取り出せません。スレッド・プールを使用するアプリケーション開発者は、この問題を考慮する必要があります。
OCIThreadKeyDestFuncは、キーのデストラクタ・ルーチンへのポインタの型です。OCIThreadKeyInit()を使用してキーを作成するときに、キーをデストラクタ・ルーチンに関連付けることができます。キーのデストラクタ・ルーチンは、キーの値がNULLでないスレッドが終了するときに常にコールされます。デストラクタ・ルーチンの戻り値はなく、パラメータを1つ指定します。このパラメータは、スレッドの終了時にキーに対して設定された値です。
デストラクタ・ルーチンは、スレッドの終了後およびプロセスの終了前に、そのスレッドの値に対して常にコールされます。デストラクタ・ルーチンがコールされる正確なタイミングは不明です。このため、プロセス内のコードでは、デストラクタ・ルーチンの実行後の状態を想定していません。特に、デストラクタは、終了したスレッドが戻るときの結合コール前に実行されるとはかぎりません。
OCIThreadIdデータ型は、スレッドの識別に使用されます。どのような場合にも、複数のスレッドのOCIThreadIdが同じになることはありません。ただし、OCIThreadIdの値はリサイクルができます。つまり、スレッドが終了した後で、終了したスレッドと同じOCIThreadId値が付いたスレッドが新しく作成されます。特に、スレッドIDにより、スレッドTはプロセス内で一意に識別できる必要があります。TがスレッドUと常に同時に実行されるプロセスの場合は、すべてのスレッドU内でスレッドIDが一貫しており、有効である必要があります。スレッドTのスレッドIDは、スレッドT内で取出し可能であることが必要です。このIDの取出しは、OCIThreadIdGet()を使用して行います。
OCIThreadId型は、NULLスレッドIDの概念をサポートしています。NULLスレッドIDは、実際のスレッドのIDと同じになることはありません。
アクティブなスレッドの基本形では、実際のスレッドの操作を行います。これらの基本形の仕様では、多くの場合、複数のスレッドが要求されます。このため、OCIThreadが有効な場合にのみ正しく動作します。OCIThreadが無効な場合は、常にエラーが戻されます。ただし、OCIThreadHandleGet()は例外で、シングル・スレッド環境でコールできますが、この場合は効果がありません。
アクティブ基本形は、マルチスレッド環境で実行されているコードからのみコールできます。OCIThreadIsMulti()をコールして、環境がマルチスレッドかシングル・スレッドかどうかを調べることができます。表8-8に、アクティブなスレッドの実装に使用する関数がリストされています。