この章は、次の項目で構成されています。
OCIには、ローカル・トランザクションとグローバル・トランザクションの両方での操作に対応した一連のAPIコールが用意されています。これらのコールにはオブジェクトのサポートが含まれているため、OCIアプリケーションがオブジェクト・モードで実行されている場合は、コミット・コールおよびロールバック・コールでオブジェクト・キャッシュとトランザクションの状態が同期します。
トランザクション操作を実行する関数は、次のとおりです。それぞれのコールは、適切なサーバー・コンテキストおよびユーザー・セッション・ハンドルで初期化する必要がある、サービス・コンテキスト・ハンドルを取ります。トランザクション・ハンドルは、サービス・コンテキストの3番目の要素で、トランザクションに関する特定の情報を格納します。SQL文が準備されると、トランザクション・ハンドルは、特定のサービス・コンテキストと関連付けられます。その文が実行されると、その結果(問合せ、フェッチ、挿入)は、サービス・コンテキストと現在関連しているトランザクションの一部になります。
OCITransStart()は、トランザクションの開始をマークします。
OCITransDetach()は、トランザクションを連結解除します。
OCITransCommit()は、トランザクションをコミットします。
OCITransRollback()は、トランザクションをロールバックします。
OCITransPrepare()は、分散処理環境でトランザクションをコミットする準備をします。
OCITransMultiPrepare()は、1回のコールで、複数のブランチのあるトランザクションを準備します。
OCITransForget()は、発見的に完了したグローバル・トランザクションの記憶をサーバーから消します。
これらのコールをすべて使用するか、そのうちのいくつかのみを使用するかは、アプリケーションでのトランザクションの複雑度のレベルによって異なります。次の項で、詳細に説明します。
OCIでは、次のトランザクションの複雑度レベルをサポートします。
ほとんどのアプリケーションで扱うのは、単純なローカル・トランザクションのみです。これらのアプリケーションでは、そのアプリケーションの中でデータベースを変更すると、暗黙的トランザクションが作成されます。アプリケーションなどで必要とされるトランザクション特有のコールには、次のようなコールがあります。
OCITransCommit(): トランザクションをコミットします。
OCITransRollback(): トランザクションをロールバックします。
1つのトランザクションがコミットまたはロールバックするとすぐに、次のデータベース変更によって、そのアプリケーションに対する新規の暗黙的トランザクションが作成されます。
1つのサービス・コンテキストでは、常に1つの暗黙的トランザクションのみをアクティブにできます。暗黙的なトランザクションの属性は、ユーザーには不透明です。
アプリケーションで複数のセッションを作成すると、それぞれの権限に対し、関連する暗黙的トランザクションを1つずつ保持することができます。
アプリケーションでシリアライズ可能または読取り専用トランザクションが必要な場合は、トランザクションを開始する追加のOCIのOCITransStart()コールが必要です。
OCITransStart()コールでは、flags
パラメータに対して、OCI_TRANS_SERIALIZABLE
またはOCI_TRANS_READONLY
の適切な値を指定する必要があります。フラグが指定されない場合、デフォルト値は、標準の読取り/書込みトランザクションのOCI_TRANS_READWRITE
です。
OCITransStart()コールで読取り専用オプションを指定すると、SET TRANSACTION READ ONLY
文を実行するためのサーバー・ラウンドトリップは必要ありません。
グローバル・トランザクションは、より高度なトランザクションを処理するアプリケーションでのみ必要です。
トランザクション処理(TP)モニターなどの3層アプリケーションでは、グローバル・トランザクションの作成および管理を行います。これらは、グローバル・トランザクション識別子(XID
)を提供し、これによって、サーバーにローカル・トランザクションが関連付けられます。
1つのグローバル・トランザクションには、1つ以上のブランチがあります。それぞれのブランチは、XID
によって識別されます。XID
は、グローバル・トランザクション識別子(gtrid
)およびブランチ修飾子(bqual
)から構成されます。この構造は、標準のXA仕様に基づくものです。
表8-1では、1つの考えられる1234というXID
の構造を示しています。
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では、あるアプリケーションの密結合ブランチを説明します。2つのブランチ(B1とB2)のXID
は、同じトランザクション(T)で操作されているので、同じ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
属性があります。これらの属性により、グローバル・トランザクションを実行するときに記録されるクライアント・データベース名を設定します。障害のために準備状態で保留になっている可能性があるトランザクションを追跡するために、データベース管理者がこの名前を使用できます。
グローバル・トランザクションは、1フェーズまたは2フェーズでコミットされます。最も単純な状態は、単一のトランザクションが単一のデータベースに対して操作されている場合です。この場合は、アプリケーションではOCITransCommit()をコールすることによって、このコールのデフォルト値であるトランザクションの1フェーズ・コミットを実行できます。
アプリケーションで、複数のOracle Databaseに対してトランザクションが処理されている場合は、状況はさらに複雑になります。この場合は、2フェーズ・コミットが必要です。2フェーズ・コミット操作は、次のステップからなります。
準備: アプリケーションから各トランザクションに対し、OCITransPrepare()コールを発行します。各トランザクションでは、現行の作業をコミットできるか(OCI_SUCCESS
)、できないか(OCI_ERROR
)を示す値を戻します。
コミット: 各OCITransPrepare()コールがOCI_SUCCESS
の値を戻した場合は、アプリケーションから各トランザクションにOCITransCommit()コールを発行できます。このコミット・コールのデフォルトが1フェーズ・コミットであるため、適切な処理を行うためには、このコールのflags
パラメータにOCI_TRANS_TWOPHASE
を明示的に設定する必要があります。
注意: トランザクションが読取り専用であることを示す必要がある場合、OCITransPrepare()コールはOCI_SUCCESS_WITH_INFO も戻します。したがって、コミットは不適切かつ不必要です。 |
追加のコールOCITransForget()により、完了したトランザクションの情報がデータベースから消えることになります。このコールは、問題が起きて2フェーズ・コミットを終了させる必要がある場合に使用します。Oracle DatabaseでOCITransForget()コールを受け取ると、トランザクションに関する情報はすべて消去されます。
同じOracle Databaseに対し、複数のアプリケーションでグローバル・トランザクションの異なるブランチが使用される場合があります。そのようなトランザクションをコミットする前に、すべてのブランチを準備する必要があります。
大抵の場合、ブランチを使用しているアプリケーションにはそれぞれ固有のブランチを準備する役割があります。しかし、アーキテクチャによってはこの役割を外部のトランザクション・サービスに求めるものがあります。その場合、外部のトランザクション・サービスで、グローバル・トランザクションの各ブランチを準備してください。従来のOCITransPrepare()コールは、各ブランチを別々に準備する必要があるため、このタスクには非効率的です。OCITransMultiPrepare()コールでは、同じグローバル・トランザクションに含まれる複数のブランチを1ラウンドトリップで準備します。このコールの方が効率がよく、クライアントからサーバーに送られるメッセージ数を大幅に削減できます。
表8-1から表8-5までは、トランザクションOCIコールの使用方法を説明しています。
これらは、一連のOCIコールおよびその他の処理を、その処理結果とともに示しています。簡単にするために、これらのコールのパラメータをすべて示すのではなく、コールの流れを解説しています。
OCIの処理列は、OCIアプリケーションの活動やコールの内容を示します。XID
列には、必要に応じて、トランザクション識別子がリストされます。フラグ列は、flags
パラメータに渡された値を示します。結果列は、コールの結果を説明します。
グローバル・トランザクション・ブランチおよび移行可能オープン接続の使用に関連する初期化パラメータには、次の2つがあります。
TRANSACTIONS
- このパラメータでは、システム全体のグローバル・トランザクション・ブランチの最大数を指定します。一方、1つのグローバル・トランザクションの最大ブランチ数は8です。
OPEN_LINKS_PER_INSTANCE
- このパラメータでは、移行可能オープン接続の最大数を指定します。移行可能オープン接続は、トランザクションをコミットした後に接続をキャッシュできるように、グローバル・トランザクションで使用します。これと対照的に、OPEN_LINKS
パラメータは、1セッションからの接続数を制御しますが、グローバル・トランザクションを使用するアプリケーションには適用できません。
表8-2では、1フェーズ・コミット操作のステップを示しています。
表8-2 1フェーズ・コミット
ステップ | OCIの処理 | XID | フラグ | 結果 |
---|---|---|---|---|
1 |
OCITransStart() |
1234 |
新規の読取り/書込みトランザクションを開始する |
|
2 |
SQL UPDATE |
- |
- |
列を更新する。 |
3 |
OCITransCommit() |
- |
- |
コミットが成功する。 |
表8-3では、2フェーズ・コミット操作のステップを示しています。
表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では、同じ権限を持つ別のプロセスからトランザクションを再開することもできます。
表8-4では、読取り専用更新操作の失敗でのステップを示しています。
表8-4 読取り専用トランザクションの更新失敗
ステップ | OCIの処理 | XID | フラグ | 結果 |
---|---|---|---|---|
1 |
OCITransStart() |
1234 |
|
新規の読取り専用トランザクションを開始する。 |
2 |
SQL UPDATE |
- |
- |
トランザクションが読取り専用なので、更新は失敗する。 |
3 |
OCITransCommit() |
- |
- |
コミットによる影響はありません。 |
表8-5では、読取り専用トランザクションのステップを示しています。
表8-5 読取り専用トランザクション
ステップ | OCIの処理 | XID | フラグ | 結果 |
---|---|---|---|---|
1 |
OCITransStart() |
1234 |
|
新規の読取り専用トランザクションを開始する。 |
2 |
SQL SELECT |
- |
- |
データベースに問い合せる。 |
3 |
OCITransCommit() |
- |
- |
影響なし — トランザクションは読取り専用なので、何も変更なし |
OCIでは、複数のユーザーを認証および管理できます。
OCISessionBegin()コールは、サービス・コンテキスト・ハンドルに設定されたサーバーに対してユーザーを認証します。指定したサーバー・ハンドルに対して最初にコールする必要があります。OCISessionBegin()は、コールのサーバー・ハンドルおよびサービス・コンテキストで指定したOracle Databaseにアクセスするユーザーを認証します。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
フラグを設定している場合、ユーザー・セッションは初期化された状態のままになります。そうでない場合、ユーザー・セッションは消去されます。
OCIPasswordChange()に渡したサービス・コンテキストがすでに初期化されている場合、OCIPasswordChange()では、指定のアカウントが旧パスワードを使用して認証され、旧パスワードが新パスワードに変更されます。この場合、どのフラグを設定していても、ユーザー・セッションは初期化された状態のままになります。
データベースへの接続にアプリケーションでパスワード資格証明を使用するような大規模なデプロイメントの場合、このような資格証明をクライアント側のOracleウォレットに格納できます。Oracleウォレットは、認証や署名の資格証明を格納するために使用する、セキュアなソフトウェア・コンテナです。
クライアント側のOracleウォレットにデータベースのパスワード資格証明を格納すると、アプリケーション・コードやバッチ・ジョブ、スクリプトにユーザー名とパスワードを埋め込む必要がなくなります。このため、スクリプトやアプリケーション・コード内にクリアテキスト・パスワードを公開する危険性が減少し、ユーザー名やパスワードを変更するたびにコードを変更する必要がなくなるため、管理が単純化されます。さらに、アプリケーション・コードを変更する必要がないため、これらのユーザー・アカウントに対するパスワード管理ポリシーの実施は今までより簡単になります。
外部パスワード・ストアを使用するようにクライアントを構成すると、アプリケーションではパスワード認証を使用するデータベースの接続に次の構文を使用できます。
CONNECT /@database_alias
このCONNECT
文では、データベース・ログイン資格証明を指定する必要はありません。かわりに、クライアントのウォレットでデータベース・ログイン資格証明が検索されます。
関連項目: クライアントをセキュアな外部パスワード・ストアを使用するように構成する方法の詳細は、『Oracle Database管理者ガイド』を参照してください。 |
ユーザー・セッションをいくつかのサーバー接続に渡って多重化することによりユーザーのロードをアクティブに均衡化するトランザクション・サーバーでは、これらの接続をサーバー・グループにグループ化する必要があります。サーバー・グループを使用してこれらの接続を識別し、セッションを効率的で安全に管理できます。
OCIAttrSet()コールを使用してサーバー・グループ名を指定するには、例8-1に示すようにOCI_ATTR_SERVER_GROUP
属性を定義する必要があります。
例8-1 サーバー・グループ名を渡すための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
パラメータに渡される値として使用します。
OCI_ATTR_PROXY_CREDENTIALS
属性を使用して、クライアントの認証に使用するアプリケーション・サーバーの資格証明を指定します。例8-2のように、次の宣言とOCIAttrSet()コールをコード化できます。
例8-2 クライアント認証用のアプリケーション・サーバーの資格証明を指定するためのOCI_ATTR_PROXY_CREDENTIALS属性の定義
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証明書に含まれる識別名をクライアントのログイン名として使用できます。
クライアントの識別名を渡すには、例8-3のように、中間層サーバーでOCIAttrSet()をコールし、OCI_ATTR_DISTINGUISHED_NAME
を渡します。
OCI_ATTR_CERTIFICATE
を使用した証明書ベースのプロキシ認証は、Oracle Databaseの今後のリリースではサポートされません。かわりに、OCI_ATTR_DISTINGUISHED_NAME
属性またはOCI_ATTR_USERNAME
属性を使用してください。この認証方法は、識別名の使用に似ています。X.509証明書全体が、中間層サーバーによってデータベースに渡されます。
証明書全体を渡すには、例8-4のように、中間層サーバーでOCIAttrSet()をコールし、OCI_ATTR_CERTIFICATE
を渡します。
アプリケーション・サーバーがOracle Databaseに接続する場合は、OCI_ATTR_INITIAL_CLIENT_ROLES
属性を使用して、クライアントが所有するロールを指定します。一連のロールを使用可能にするために、例8-5のように、属性、NULL
で終了する文字列の配列および配列内の文字列の数を指定してOCIAttrSet()関数をコールします。
多数の中間層アプリケーションがデータベースにアプリケーションとして接続し、中間層を使用してエンド・ユーザーの識別情報を追跡します。様々なデータベース・コンポーネントでこれらのユーザーの識別情報の追跡を統合するため、データベース・クライアントはいつでもセッション・ハンドルにCLIENT_IDENTIFIER
(アプリケーション・コンテキスト・ネームスペースUSERENV
の事前定義済属性)を設定できます。OCIAttrSet()へのコールで、例8-6のように、OCIのOCI_ATTR_CLIENT_IDENTIFIER
属性を使用します。サーバーへの次の要求で情報が伝播され、サーバー・セッションに格納されます。
OCI_ATTR_CLIENT_IDENTIFIER
は、グローバル・アプリケーション・コンテキストとも使用でき、これらのユーザーの選択された識別情報に対してコンテキストの可用性を制限します。
例8-6 エンドユーザー識別情報を渡すための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()は、透過的アプリケーション・フェイルオーバー(TAF)により再確立されるセッションに対して、手動で実行する必要があります。
クライアント識別子は、CLIENT_IDENTIFIER
列としてV$SESSION
にあるか、または次のSQL文とともにシステム・コンテキスト内にあります。
SELECT SYS_CONTEXT
('userenv', 'client_identifier') FROM DUAL;
中間層では、データベース・サーバーに対して、認証自体を行うかわりにクライアントのパスワードを検証することでクライアントを認証するように要求できます。クライアント/サーバー接続と同じように見えますが、クライアントでは、データベース操作を実行するために、クライアントのシステムにOracle Databaseソフトウェアがインストールされている必要はありません。アプリケーション・サーバーでは、クライアントのパスワードを使用するため、例8-7のように、既存のOCI_ATTR_PASSWORD
属性を使用して、OCIAttrSet()に認証データを提供します。
例8-7 検証のパスワードを渡すためのOCI_ATTR_PASSWORD属性の定義
OCIAttrSet((void *)session_handle, (ub4) OCI_HTYPE_SESSION, (void *)password, (ub4)0, OCI_ATTR_PASSWORD, error_handle);
例8-8では、クライアントの外部名と初期権限を指定できるOCI属性を示しています。OCIアプリケーションでは、クライアントを識別または認証する代替方法として、これらの資格照明が使用されます。
例8-8 クライアントの外部名と初期権限を指定できるOCI属性
... *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. ** 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 */ ...
外部で初期化されたコンテキストとは、属性をOCIから初期化できるアプリケーション・コンテキストです。SQL文のCREATE
CONTEXT
でINITIALIZED
EXTERNALLY
オプションを使用して、サーバーにコンテキスト・ネームスペースを作成します。
これで、OCIAttrSet()とOCISessionBegin()を使用してセッションを確立するときに、OCIインタフェースを初期化できます。CREATE
CONTEXT
文で指定されたPL/SQLパッケージのみを使用して、ネームスペース内に属性を書き込む後続のコマンドを発行します。
デフォルト値と他のセッション属性は、OCISessionBegin()コールを使用して設定できるため、サーバー・ラウンドトリップ回数が削減されます。
関連項目:
|
クライアント・アプリケーションを開発する場合、認証を行う前に、OCI関数に次の属性を使用して、明示的にアプリケーション・コンテキストをセッション・ハンドルに設定できます。
OCI_ATTR_APPCTX_SIZE
属性を、例8-9のように、OCIAttrSet()コールで使用して、コンテキストの配列サイズを任意の数のコンテキスト属性で初期化します。
OCI_ATTR_APPCTX_LIST
属性を、例8-10のように、OCIAttrGet()コールで使用して、セッションに対するアプリケーション・コンテキスト・リスト記述子のハンドルを取得します。(ctxl_desc
パラメータのデータ型は、OCIParam *
であることが必要です。)
例8-10 セッションに対するアプリケーション・コンテキスト・リスト記述子のハンドルを取得するためのOCI_ATTR_APPCTX_LIST属性の使用
OCIAttrGet(session, (ub4) OCI_HTYPE_SESSION, (void *)&ctxl_desc, (ub4)0, OCI_ATTR_APPCTX_LIST, error_handle);
例8-11では、OCIParamGet()のコールでアプリケーション・コンテキスト・リスト記述子を使用し、i番目のアプリケーション・コンテキストの個々の記述子を取得する方法を示しています。
次の属性を使用して、アプリケーション・コンテキストの適切な値を設定します。
OCI_ATTR_APPCTX_NAME
を使用して、コンテキスト・ネームスペース(有効なSQL識別子であることが必要)を設定します。
OCI_ATTR_APPCTX_ATTR
を使用して、指定されたコンテキストに、最大30バイトの大/小文字区別がない文字列で属性名を設定します。
各ネームスペースに複数の属性を保持でき、各属性には1つの値を設定します。例8-12では、これらを設定するために使用できるコールを示しています。
例8-12 外部で初期化されたコンテキストを設定するためのセッション・ハンドル属性の定義
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
データ型に基づいているため、文字型のみサポートされていることに注意してください。
次の属性を使用して、サーバー・ラウンドトリップを含まない、サーバーのコール・タイムを測定します。また、これらの属性は、サーバーに対してラウンドトリップを1回発生させる、PL/SQLパッケージDBMS_APPLICATION_INFO
を使用して設定できます。OCIを使用して属性を設定すると、ラウンドトリップは発生しません。
boolean
変数をTRUE
またはFALSE
に設定します。OCIAttrSet()をコールしてOCI_ATTR_COLLECT_CALL_TIME
属性を設定した後で、サーバーにより各コール・タイムが測定されます。変数をTRUE
に設定した時間とFALSE
に設定した時間の間のすべてのサーバー・タイムが測定されます。
最後のサーバー・コールの経過時間(マイクロ秒)は、OCI_ATTR_CALL_TIME
属性を使用してOCIAttrGet()をコールすると、ub8
変数で戻されます。例8-13では、コード・フラグメントでこの方法を示しています。
例8-13 最後のサーバー・コールの経過時間を取得するためのOCI_ATTR_CALL_TIME属性の使用
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); ...
OCISessionBegin()をコールすると、セッション・ハンドルに設定されたコンテキストがサーバーにプッシュされます。このサーバー・セッションに追加コンテキストは伝播されません。例8-14では、これらのコールおよび属性の使用方法を示しています。
例8-14 外部で初期化されたコンテキストによる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 you 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"に変更するには、クライアント側で、例8-15に示したコード・フラグメントを使用します。この情報がサーバーに転送されると、サーバーでは、CLIENTCONTEXT
ネームスペース内の"responsibility"属性に最新の値"vp"が表示されます。
例8-15 CLIENTCONTEXTネームスペース内の"responsibility"属性値の変更
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);
クライアントのネームスペース内の特定の属性情報を消去できます。例8-16のように、 OCIAppCtxSet()関数を使用して、属性の値をNULL
または空の文字列に設定して消去します。
例8-16 クライアント・ネームスペース内の特定の属性情報を消去するための2つの方法
(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()関数を使用して消去でき、次のネットワーク・トランスポート時に、サーバー側ユーザー・セッションでも消去されます。
クライアント・アプリケーションでは、いくつかの設定操作の後にネームスペース内で全消去操作を行うと、ネームスペース内でこの全消去操作の前に設定されたすべての属性の値は、クライアント側とサーバー側でクリーン・アップされます。全消去操作の後に行われた設定操作のみが、サーバー側で反映されます。クライアント側では、例8-17のようなコードが表示されます。
例8-17 特定のクライアント・ネームスペースでのすてべのコンテキスト情報の消去
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エラーとしてレポートされます。現時点では、ネットワーク・トランスポートで発生したエラーとメイン・コールで発生したエラーとを識別する簡単な方法はありません。クライアントでは、メイン・コールで発生したエラーによりラウンドトリップ・ネットワーク処理を元に戻すことは考えずに、適切な例外処理メカニズムを実装して、非一貫性が発生しないようにしてください。
エディションは、既存のアプリケーションが使用可能であっても、アプリケーション・パッチによって変更されたエディション化可能なオブジェクトをインストールし実行できるステージング領域を提供します。セッション開始時にOCI_ATTR_EDITION
属性を設定することで、データベース・デフォルト以外のエディションを指定できます。アプリケーションでは、例8-18のように、この属性名およびエディションを値として指定したOCIAttrSet()をコールできます。
例8-18 OCI_ATTR_EDITION属性を設定するためのOCIAttrSet()のコール
static void workerFunction() { OCISvcCtx *svchp = (OCISvcCtx *) 0; OCIAuthInfo *authp = (OCIAuthInfo *)0; sword err; err = OCIHandleAlloc((void *) envhp, (void **)&authp, (ub4) OCI_HTYPE_AUTHINFO, (size_t) 0, (void **) 0); if (err) checkerr(errhp, err); checkerr(errhp, OCIAttrSet((void *) authp, (ub4) OCI_HTYPE_AUTHINFO, (void *) username, (ub4) strlen((char *)username), (ub4) OCI_ATTR_USERNAME, errhp)); checkerr(errhp,OCIAttrSet((void *) authp, (ub4) OCI_HTYPE_AUTHINFO, (void *) password, (ub4) strlen((char *)password), (ub4) OCI_ATTR_PASSWORD, errhp)); (void) OCIAttrSet((void *) authp, (ub4) OCI_HTYPE_SESSION, (void *) "Patch_Bug_12345", (ub4) strlen((char *)"Patch_Bug_12345"), (ub4) OCI_ATTR_EDITION, errhp); printf(("Create a new session that connects to the specified edition\n"); if (err = OCISessionGet(envhp, errhp, &svchp, authp, (OraText *)connstr, (ub4)strlen((char *)connstr), NULL, 0, NULL, NULL, NULL, OCI_DEFAULT)) { checkerr(errhp, err); exit(1); } checkerr(errhp, OCISessionRelease(svchp, errhp, NULL, (ub4)0, OCI_DEFAULT)); OCIHandleFree((void *)authp, OCI_HTYPE_AUTHINFO); }
OCIAttrSet()がコールされない場合、エディション名の値はオペレーティング・システム環境変数ORA_EDITION
から取得されます。その変数が設定されていない場合、OCI_ATTR_EDITION
の値は空の文字列となります。空でない値が指定された場合、サーバーがセッションの指定エディションを設定するか、セッションでデータベースのデフォルト・エディションが使用されます。次に、サーバーはユーザーがそのEditionに対するUSE
権限を持っているかどうかをチェックします。権限がない場合、接続に失敗します。存在しないエディション名が指定された場合は、エラーが戻されます。
関連項目:
|
次のセキュリティ拡張機能では、init.ora
ファイルまたはsqlnet.ora
ファイル内の構成済パラメータが使用され(後者のファイルは、該当する機能について具体的に示されています)、その詳細は『Oracle Databaseセキュリティ・ガイド』を参照してください。これらの初期化パラメータは、データベースの全インスタンスに適用されます。
関連項目: 『Oracle Databaseセキュリティ・ガイド』の中間層アプリケーションへのコールの埋込みによるクライアント・セッションIDの取得、設定および消去に関する項 |
認証前に(OCIServerAttach()をコールした後に接続済サーバー・ハンドルで)OCIServerVersion()関数を発行して、データベースのバージョンを取得できます。認証前にデータベース・バージョン文字列が開示されないようにするには、初期化パラメータSEC_RETURN_SERVER_RELEASE_BANNER
をNO
に設定します。たとえば、次のようにします。
SEC_RETURN_SERVER_RELEASE_BANNER = NO
これにより、Oracle Databaseリリース11.1以降の全リリースおよびパッチ・セットの場合は、次の文字列が表示されます。
Oracle Database 11g Release 11.1.0.0.0 - Production
SEC_RETURN_SERVER_RELEASE_BANNER
をYES
に設定すると、現行のバナーが表示されます。Oracle Databaseリリース11.2.0.2をインストール済の場合は、次のバナーが表示されます。
Oracle Database 11g Enterprise Edition Release 11.2.0.2 - Production
この機能は、Oracle Databaseリリース11.1以降のサーバーとすべてのバージョンのクライアントで動作します。
次のシステム全体のパラメータはsqlnet.ora
にあり、不正なアクセスとユーザー・アクションに可能な監査に関する警告をユーザーに対して表示します。これらの機能は、Oracle Databaseリリース11.1以降のサーバーおよびクライアントで使用可能です。バナーのコンテンツは、データベース管理者が作成するテキスト・ファイルにあります。バナーのコンテンツの表示には、512バイトのバッファ制限があります。このバッファ制限を超えると、バナーのコンテンツは途中で切り取られたようになります。アクセス・バナーの構文は、次のとおりです。
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 DatabaseおよびOCIライブラリのスレッド・セーフティ機能により、開発者はOCIをマルチスレッド環境で使用できます。スレッド・セーフティ機能を使用すると、副作用なしに、OCIのコードを、OCIコールを実行する複数のスレッドから再入可能にできます。
注意: スレッド・セーフティは、どのオペレーティング・システムでも利用できるとはかぎりません。詳細は、使用しているOracle Databaseシステム固有のドキュメントで確認してください。マルチスレッドのLinuxまたはUNIX環境では、OCIBreak()を除くOCIコールはユーザー・シグナル・ハンドラでは使用できません。 ハンドルを使用および解放する正しい方法とは、アプリケーションの終了時、すべてのスレッドが破棄された後にのみ、ハンドルを作成して使用してから解放することです。 |
OCIにスレッド・セーフティを実装すると、次の利点があります。
複数のスレッドでOCIコールを実行した場合も、単一のスレッドで連続するコールを実行した場合と同じ結果になります。
複数のスレッドでOCIコールを実行した場合は、スレッド間で副作用がありません。
マルチスレッド・プログラムを作成しない場合でも、スレッド・セーフのOCIコールを使用してもパフォーマンスが低下しません。
複数のスレッドを使用すると、プログラムのパフォーマンスが向上します。パフォーマンスが向上するのは、別々のプロセッサでスレッドを同時実行するマルチプロセッサ・システム、および低速操作と高速操作の間でオーバーラップが発生するシングル・プロセッサ・システムの場合です。
クライアント/サーバー・アプリケーションでは、クライアントをマルチスレッド・プログラムにでき、マルチスレッド・アプリケーションの典型的な使用は、3層(クライアント/エージェント/サーバー)アーキテクチャにおける使用です。このアーキテクチャでは、クライアントが関係するのは表示サービスのみです。エージェント(アプリケーション・サーバー)は、クライアント・アプリケーションのアプリケーション・ロジックを処理します。一般的には、この関係は多対1の関係で、複数のクライアントが同一のアプリケーション・サーバーを共有します。
この場合のサーバー層は、データベースです。このアプリケーション・サーバー(エージェント)は、それぞれのスレッドが1つのクライアント・アプリケーションに対してサービスを提供することから、マルチスレッド・アプリケーション・サーバーに最適です。Oracle Database環境では、このアプリケーション・サーバーはOCIプログラムまたはプリコンパイラ・プログラムです。
スレッド・セーフティを利用するには、アプリケーションをスレッド・セーフなオペレーティング・システム上で実行する必要があります。アプリケーションでは、mode
パラメータの値としてOCI_THREADED
を使用して、OCIEnvNlsCreate()をコールすることで、マルチスレッド環境で実行するように指定します。
OCIEnvNlsCreate()の後続のコールすべてにも、OCI_THREADED
を指定する必要があります。
注意: スレッド・セーフでないオペレーティング・システムで実行するアプリケーションの場合は、OCIEnvCreate()にもOCIEnvNlsCreate()にもOCI_THREADED の値を渡さないでください。 |
シングル・スレッド・アプリケーションでは、オペレーティング・システムがスレッド・セーフであるかどうかにかかわらず、 OCIEnvCreate()またはOCIEnvNlsCreate()にOCI_DEFAULT
の値を渡す必要があります。シングル・スレッド・アプリケーションをOCI_THREADED
モードで実行すると、パフォーマンスが低下する可能性があります。
マルチスレッド・アプリケーションがスレッド・セーフなオペレーティング・システム上で動作している場合は、OCIライブラリで、環境ハンドルごとにアプリケーションのmutexが管理されます。OCIEnvCreate()コールまたはOCIEnvNlsCreate()コールのいずれかのmode
パラメータでOCI_ENV_NO_MUTEX
の値を指定することで、アプリケーションではこの機能をオーバーライドし、独自のmutexスキームを維持できます。
想定されるのは、次のシナリオです。このシナリオは、各環境ハンドルに存在する接続の数、および各接続で作成されるスレッドの数によって変わります。
アプリケーションに複数の環境ハンドルがあっても、それぞれの環境ハンドルにスレッドが1つの場合は、mutexは必要ありません。
OCI_THREADED
モードで実行しているアプリケーションに1つ以上の環境ハンドルがあり、それぞれの環境ハンドルに複数の接続がある場合は、次の方法の中から選択できます。
OCIEnvNlsCreate()のmode
に対してOCI_ENV_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レイヤーを終了してコンテキストのメモリーを解放します。 |
|
アプリケーションがマルチスレッド環境とシングル・スレッド環境のどちらで動作しているかを、コール元に通知します。 |
OCIThread
関数をコールするときは、多くの場合、OCI環境またはユーザー・セッション・ハンドルをパラメータとして使用します。OCIThread
コンテキストは、OCI環境またはユーザー・セッション・ハンドルの一部なので、OCIThreadInit()をコールして初期化する必要があります。OCIThread
コンテキストの終了は、OCIThreadTerm()をコールして行います。
注意: OCIThread コンテキストは、不透明なデータ構造になっています。コンテキストの内容を検査する必要はありません。 |
非アクティブなスレッドの基本形は、mutex、スレッドID、およびスレッド固有のデータを扱います。これらの基本形の仕様では、複数のスレッドが存在する必要がないため、マルチスレッドとシングル・スレッドの両方のオペレーティング・システムで使用できます。表8-7には、非アクティブなスレッドの実装に使用する関数がリストされています。
表8-7 非アクティブなスレッドの基本形
関数 | 用途 |
---|---|
|
mutexの割当ておよび初期化を行います。 |
|
mutexの破棄および割当て解除を行います。 |
|
コールが行われたスレッドに対してmutexを取得します。 |
|
mutexを解放します。 |
|
新規の鍵の割当てと生成を行います。 |
|
キーの破棄および割当て解除を行います。 |
|
コール側スレッドのキーの現在の値を取得します。 |
|
コール側スレッドのキー値を設定します。 |
|
スレッドIDの割当ておよび初期化を行います。 |
|
スレッドIDの破棄および割当て解除を行います。 |
|
スレッドIDの設定を変更します。 |
|
スレッドIDをNULLにします。 |
|
コール側スレッドのスレッドIDを取り出します。 |
|
2つのスレッドIDが同じスレッドを表すかどうかを調べます。 |
|
スレッドIDが |
OCIThreadMutex
データ型はmutexの表現に使用されます。このmutexを使用すると、次のことを確認できます。
特定のデータ・セットに同時にアクセスできるのは1つのスレッドのみです。
コードの特定の重要セクションを同時に実行できるのは1つのスレッドのみです。
mutexポインタは、クライアント構造またはスタンドアロン変数の一部として宣言できます。これらは、使用する前にOCIThreadMutexInit()により初期化する必要があります。不要になった場合は、OCIThreadMutexDestroy()を使用して破棄する必要があります。
OCIThreadMutexAcquire()を使用して、スレッドからmutexを取得できます。この場合、同時に複数のスレッドによって特定のmutexが保持されることはありません。mutexを保持しているスレッドは、OCIThreadMutexRelease()をコールすると解放できます。
データ型OCIThreadKey
は、スレッド固有の値を持つプロセス全体の変数とみなすことができます。この場合、プロセス内のすべてのスレッドは指定したキーを使用できますが、各スレッドは他のスレッドとは独立してそのキーを調べたり変更したりできます。スレッドがキーを調べる際の値は、常にそのキーに対して最後に設定された値と同じです。他のスレッドがそのキーに対して設定した値は表示されません。キーに保持されている値のデータ型はvoid *
汎用ポインタです。
キーは、OCIThreadKeyInit()を使用して作成できます。キー値はすべてのスレッドに対して、NULL
に初期化されます。
スレッドからは、OCIThreadKeySet()を使用してキーの値を設定できます。また、OCIThreadKeyGet()を使用してキーの値を取得できます。
OCIThread
キー関数によって、スレッド固有のデータの保存および取出しを行うことができます。クライアントでスレッド・プールを管理し、スレッドごとに異なるタスクを割り当てる場合、OCIThread
キー関数を使用してタスクに関連付けられたデータを保存するのが適切でないことがあります。
ここでは、処理が失敗する状況を示すシナリオを示します。あるスレッドが、タスクの初期化を実行するように割り当てられているとします。初期化時、タスクによりOCIThread
キー関数を使用してスレッド内にデータが格納されます。初期化後、スレッドはスレッド・プールに戻されます。後に、スレッド・プール・マネージャは、タスクでいくつかの操作を実行するように別のスレッドを割り当てますが、タスクでは初期化時に格納されたデータを取り出す必要が生じます。タスクは別のスレッドで実行されているため、同じデータを取り出すことはできません。アプリケーション開発者がスレッド・プールを使用する場合は、この点に注意する必要があります。
OCIThreadKeyDestFunc
は、キーのデストラクタ・ルーチンへのポインタの型です。OCIThreadKeyInit()を使用してキーを作成すると、そのキーをデストラクタ・ルーチンに関連付けることができます。キーのデストラクタ・ルーチンは、キーに対してNULL
以外の値でスレッドが終了するときには常にコールされます。デストラクタ・ルーチンは何も戻さず、1つのパラメータのみを受け入れますが、その値はスレッドの終了時にキーに対して設定された値です。
デストラクタ・ルーチンは、スレッドの終了時およびプロセス終了前に、キー内のスレッドの値に応じてコールされることが保証されています。デストラクタ・ルーチンをコールするタイミングに関してこれ以上正確な保証はありません。プロセス内のコードは、デストラクタ・ルーチンの事後条件を想定できません。特に、デストラクタは、終了したスレッドでの結合コールが戻される前に実行することは保証されていません。
OCIThreadId
データ型は、スレッドを識別するのに使用します。指定した時間に、2つのスレッドが同じOCIThreadId
を持つことはできませんが、OCIThreadId
値はリサイクルできます。スレッドの終了後、同じOCIThreadId
値を持つ新規スレッドが作成されることがあります。特に、スレッドIDはプロセス内のスレッドT
を一意的に識別する必要があり、プロセスのすべてのスレッドU
内で一貫性があり有効である必要があります。この場合、T
がU
と同時に実行されることが保証されます。スレッドT
のスレッドIDは、スレッドT
内で取出し可能にしておく必要があります。これは、OCIThreadIdGet()を使用して実行します。
OCIThreadId
型は、NULL
スレッドIDの概念をサポートしています。NULL
スレッドIDは、実際のスレッドのIDと同じになることはありません。
アクティブなスレッドの基本形では、実際のスレッドの操作を行います。これら大部分の基本形の仕様には複数のスレッドが必要なため、有効なOCIThread
でのみ正しく機能します。有効になっていないOCIThread
では、常にエラーが戻されます。ただし、OCIThreadHandleGet()は例外で、シングル・スレッド環境でコールできますが、この場合は効果がありません。
アクティブ基本形は、マルチスレッド環境で実行されているコードからのみコールできます。OCIThreadIsMulti()をコールして、環境がマルチスレッドかシングル・スレッドかどうかを調べることができます。表8-8に、アクティブなスレッドの実装に使用する関数がリストされています。
表8-8 アクティブなスレッドの基本形
関数 | 用途 |
---|---|
|
スレッド・ハンドルの割当ておよび初期化を行います。 |
|
スレッド・ハンドルの破棄および割当て解除を行います。 |
|
新しいスレッドを作成します。 |
|
コール側スレッドを別のスレッドと結合します。 |
|
スレッド・ハンドルをクローズします。 |
|
スレッド・ハンドルを取り出します。 |
アクティブな基本形のOCIThreadJoin()およびOCIThreadClose()のスレッドを操作するには、OCIThreadHandle
データ型を使用します。OCIThreadCreate()によってオープンされたスレッド・ハンドルは、対応するOCIThreadClose()コールでクローズする必要があります。OCIThreadClose()のコール後は、スレッド・ハンドルは無効になります。