この章では、TTClassesの概要とベスト・プラクティスについて説明します。内容は次のとおりです。
TimesTen C++ Interface Classesライブラリ(TTClasses)は、データベースへのアクセスに使用する最も一般的なODBC機能のラッパーを提供します。これは、ODBCよりも使用しやすく、パフォーマンスも低下させないAPIに対する需要に応えるために開発されました。
TimesTenは次をサポートしています。
ODBC 2.5、拡張レベル1および一部の拡張レベル2機能
ODBC 3.51コア・インタフェース適合
TTClassesの実装は、ODBC 2.5に基づいています。
詳細は、『Oracle TimesTen In-Memory Database C開発者ガイド』のTimesTenのODBCサポートを参照してください。ODBCの一般情報は、ODBC APIのリファレンス・マニュアルを参照してください。
TTClassesは、TimesTenのODBCインタフェースにC++インタフェースを提供するだけでなく、TimesTen ClassicでサポートされているTimesTenのトランザクション・ログAPI(XLA)へのインタフェースも提供します。XLAではデータベース内の1つ以上の表を監視できます。その表が他のアプリケーションによって変更されると、その変更がXLAを介して監視アプリケーションに通知されます。XLA機能が最も一般的に使用される局面に対して、TTClassesは便利なインタフェースを提供します。XLAの一般的な情報については、『Oracle TimesTen In-Memory Database C開発者ガイド』のXLAおよびTimesTenのイベント管理に関する説明を参照してください。
TTClassesは、TimesTen Data Managerを使用するアプリケーション・ソフトウェアを作成する際のベスト・プラクティスの推進も目的としています。このライブラリは、TimesTenを最適な方法で使用します。たとえば、自動コミットはデフォルトでは無効化されています。パラメータ化されたSQLが推奨されており、TTClassesでのその使用方法は、手動でコーディングされたODBCよりはるかに簡単になっています。
TTClassesは様々な方法で使用可能ですが、次の一般的な方法は成功実績があり、様々なアプリケーションで簡単に適用できます。
リアルタイム・アプリケーションでは、最適なパフォーマンスを得るために、準備済のSQL文を使用する必要があります。アプリケーションで使用されるすべてのSQL文について、文ごとに個別のTTCmd
オブジェクトを使用して、アプリケーションの開始時に準備するのが理想的です。ODBCでは(したがってTTClassesでも)、文が特定の接続にバインドされるため、アプリケーションで使用される文の完全なセットが、データベースとのすべての接続に関連付けられるということがよく起こります。
これを実現する簡単な方法は、TTConnection
から導出されるアプリケーション固有のクラスを開発することです。たとえば、XYZ
という名前のアプリケーションには、XYZConnection
というクラスを作成できます。XYZConnection
クラスには、アプリケーションで使用する準備済のSQL文を表すプライベートTTCmd
メンバーを含め、これでは、これらのプライベートTTCmd
メンバーを介してアプリケーション固有のデータベース機能を実装する新しいパブリック・メソッドを提供します。
TTCmd
オブジェクトは、使用前にSQL文(SELECT
、INSERT
、UPDATE
またはDELETE
)と関連付ける必要があります。関連付けはPrepare()
メソッドを使用して行います。これによってSQL文がコンパイルおよび最適化され、SQL文が効率的に実行されるようになります。Prepare()
メソッドは文を準備するだけで、文を実行しない点に注意してください。
通常、TimesTenでは、性能の向上のために文はパラメータ化されます。次のようなSQL文を考えてみます。
SELECT col1 FROM table1 WHERE C = 10; SELECT col1 FROM table1 WHERE C = 11;
これよりも、パラメータ化された1つの文を準備し、それを複数回実行した方がより効率的です。
SELECT col1 FROM table1 WHERE C = ?;
「?」
の値は、TTCmd::setParam()
メソッドを使用して実行時に指定します。
ODBCを直接使用する場合と違い、列またはパラメータを明示的にSQL文にバインドする必要はありません。TTCmd
は、準備する際にすべての必要な列を自動的に定義しバインドします。パラメータは実行時にバインドされます。
準備は比較的負荷の高い操作であることに注意してください。アプリケーションは、(TTConnection::Connect()
を使用して)TimesTenへの接続を確立する際に、その接続に関連付けられたすべてのTTCmd
オブジェクトを準備する必要があります。アプリケーションのこのメイン実行ループの前に、すべてのSQL文を準備してください。
TTClassesメソッドでエラーまたは警告が発生すると、TTStatus
オブジェクトが例外としてスローされます。この例外はアプリケーション側で捕捉して正しく処理する必要があります。詳細は、TTStatusを参照してください。この方法については、TimesTen Classicクイック・スタートのサンプル・アプリケーション内の例でも確認できます。TimesTenクイック・スタートおよびサンプル・アプリケーションを参照してください。
ノート: 必要な特定のgetまたはsetメソッドがTTConnection またはTTCmd にない場合は、TTConnection::getHdbc() およびTTCmd::getHandle() メソッドを使用して、基礎となるODBC接続ハンドルおよび文ハンドルに直接アクセスできます。同様に、ODBC環境ハンドルにはTTGlobal::sqlhenv() メソッドでアクセスします。 |
TTConnection
から継承されるクラスの例を次に示します。
class XYZConnection : public TTConnection { private: TTCmd updateData; TTCmd insertData; TTCmd queryData; public: XYZConnection(); ~XYZConnection(); virtual void Connect (const char* connStr, const char* user, const char* pwd); void updateUser (); void addUser (char* nameP); void queryUser (const char* nameP, int* valueP); };
この例では、XYZConnection
オブジェクトがTimesTenへの接続であり、アプリケーション固有の3つの操作(addUser()
、updateUser()
およびqueryUser()
)の実行に使用できるようになっています。これらの操作はXYZ
アプリケーションに固有です。これらの3つのメソッドの実装では、アプリケーションのデータベース操作を実装するために、updateData
、insertData
およびqueryData
TTCmd
オブジェクトを使用できます。
アプリケーションのSQL文を準備するために、XYZConnection
クラスは、TTConnection
基本クラスから提供されるConnect()
メソッドをオーバーロードします。XYZConnection::Connect()
メソッドは、データベース接続を確立するために、基本クラスのConnect()
メソッドをコールし、さらに、TTCmd
オブジェクトごとにPrepare()
メソッドもコールして、後で使用するSQL文を準備します。
次に、XYZConnection::Connect()
メソッドの実装例を示します。
void XYZConnection::Connect(const char* connStr, const char* user, const char* pwd) { try { TTConnection::Connect(connStr, user, pwd); updateData.Prepare(this, "update mydata v set foo = ? where bar = ?"); insertData.Prepare(this, "insert into mydata values(?,0)"); queryData.Prepare(this, "select i from mydata where name = ?"); } catch (TTStatus st) { cerr << "Error in XYZConnection::Connect: " << st << endl; } return; }
このConnect()
メソッドにより、XYZConnection
オブジェクトとそのアプリケーション固有のメソッドが完全に動作可能になります。
この方法は、TTConnectionPool
クラスの設計でも非常に有効です。アプリケーションは、XYZConnection
型のオブジェクトを多数作成し、それらをTTConnectionPool
オブジェクトに追加できます。TTConnectionPool
::ConnectAll()
がコールされると、アプリケーションはプール内のデータベースへのすべての接続を接続し、すべてのSQL文を準備します。切断するには、TTConnectionPool::DisconnectAll()
を使用します。「TTConnectionPool」の使用方法の説明を参照してください(重要な情報があります)。
このアプリケーションの設計により、データベースへのアクセスがアプリケーションのビジネス・ロジックから簡単に分離できます。XYZConnection
クラスのみに、データベース固有のコードが含まれるようになります。
このアプリケーション設計の例は、TimesTen Classicクイック・スタートで提供されるいくつかのTTClassesのサンプル・アプリケーションで確認できます。TimesTenクイック・スタートおよびサンプル・アプリケーションを参照してください。
他の構成が可能なことに注意してください。アプリケーションで使用されるSQL文が、アプリケーション自体にハードコードされるのではなく、データベース内の表にリストされるよう、このスキームをさらに拡張したユーザーもいます。このようにすれば、アプリケーションの変更ではなくデータベースの変更によって、データベースの機能の変更を実装できます。
この項では、データベースへの接続について、次の内容で説明します。
TimesTen Scaleoutについては、Oracle TimesTen In-Memory Database Scaleoutユーザーズ・ガイドにデータベースの作成と直接接続またはクライアント/サーバー接続を使用したデータベース接続について詳細が記載されています。データベースの作成およびデータベースへの接続を参照してください。
TimesTen Classicについては、『Oracle TimesTen In-Memory Databaseオペレーション・ガイド』にデータベースのDSN(データソース名)の作成に関する説明があります。作成するDSNのタイプは、アプリケーションがデータベースに直接接続するか、クライアント接続するかによって異なります。データベースに直接接続する場合は、TimesTenデータベースの管理を参照してください。データベースへのクライアント接続を作成する場合は、TimesTenクライアントおよびサーバーの操作を参照してください。
ノート:
|
「TTCmd、TTConnectionおよびTTConnectionPoolの使用」
で説明しているXYZConnectionクラスを使用すると、次の例のようにTimesTenに対して切断および接続できます。
例2-4 TimesTenに対する接続および切断
... XYZConnection conn; char connStr[256]; char user[30]; char pwd[30]; ... try { conn.Connect(connStr, user, pwd); } catch (TTWarning st) { cerr << "Warning connecting to TimesTen: " << st << endl; } catch (TTError st) { cerr << "Error connecting to TimesTen " << st << endl; exit(1); } // ... Work with the database connection... try { conn.Disconnect(); } catch (TTStatus st) { cerr << "Error disconnecting from TimesTen: " << st << endl; exit(1); }
TTConnection
、TTConnectionPool
およびTTXlaPersistConnection
クラスには、次のメソッド・シグネチャが定義されています。
virtual void TTConnection::Connect(const char* connStr) virtual void TTConnection::Connect(const char* connStr, const char* username, const char* password) virtual void TTConnection::Connect(const char* connStr, DRIVER_COMPLETION_ENUM driverCompletion) void TTConnectionPool::ConnectAll(const char* connStr) void TTConnectionPool::ConnectAll(const char* connStr, const char* username, const char* password) virtual void TTXlaPersistConnection::Connect(const char* connStr, const char* username, const char* password, const char* bookmarkStr, bool createBookmarkFlag) virtual void TTXlaPersistConnection::Connect(const char* connStr, DRIVER_COMPLETION_ENUM driverCompletion, const char * bookmarkStr, bool createBookmarkFlag) virtual void TTXlaPersistConnection::Connect(const char* connStr, const char* username, const char* password, const char* bookmarkStr) virtual void TTXlaPersistConnection::Connect(const char* connStr, DRIVER_COMPLETION_ENUM driverCompletion, const char * bookmarkStr)
ノート:
|
この項では、データの操作に関して、次の内容で説明します。
この項では、SQL文のパラメータのバインドについて説明します。TTCmd
クラスには、パラメータをバインドするメソッドsetParam()
とBindParameter()
(バッチ操作用)が用意されています。また、出力および入力/出力パラメータをサポートするため、またはデフォルトのバインド・タイプを上書きするためのregisterParam()
も用意されています。
ノート: TimesTenのバインド・メカニズム(アーリー・バインディング)はOracle Databaseのバインド・メカニズム(レイト・バインディング)とは異なります。TimesTenは、問合せの準備の前にデータ型を必要とします。そのため、各バインド・パラメータのデータ型が指定されていないかSQL文から推測できないと、エラーが発生します。たとえば次のような文が、これに該当します。SELECT 'x' FROM DUAL WHERE ? = ?; この問題には、たとえば次のように対処できます。 SELECT 'x' from DUAL WHERE CAST(? as VARCHAR2(10)) = CAST(? as VARCHAR2(10)); |
内容は、次のとおりです。
ノート: TimesTen開発者ガイドで使用される「バインド・パラメータ」という用語(ODBC用語に準拠)は、TimesTenのPL/SQLのマニュアルで使用される「バインド変数」という用語(Oracle Database PL/SQL用語に準拠)と同じです。 |
バッチ以外の操作では、TTCmd
::setParam()
メソッドを使用しSQL文の入力パラメータをバインドします(パラメータの位置とバインドする値を指定します)。バッチ操作では、TTCmd::BindParameter()
メソッドを使用します。(バッチ操作の使用例については、例3-4「ExecuteBatch()メソッドの使用方法」を参照してください。)
バッチ以外の操作については、例2-5で、表に行を挿入するためにパラメータをバインドするクラスSampleConnection
のスニペットを示しています。(この例は、TimesTen Classicクイック・スタートで提供されているbasics.cpp
サンプル・アプリケーションのものです。TimesTenクイック・スタートおよびサンプル・アプリケーションを参照してください。)この例ではConnect()
メソッドの実装については省略されているため、Connect()の実装については例2-2
を参照してください。
次のように定義される表basics
があるとします。
create table basics (name char(10) not null primary key, i tt_integer);
例2-5 行を挿入するためのパラメータのバインド(バッチ以外の場合)
class SampleConnection : public TTConnection { using TTConnection::Connect; private: TTCmd insertData; ... protected: public: SampleConnection(); ~SampleConnection(); virtual void Connect(const char* connStr, DRIVER_COMPLETION_ENUM driverCompletion); void insert(char* nameP); ... ... // Assume a Connect() method implemented with the following: // insertData.Prepare(this, "insert into basics values(:name, :value)"); ... } //---------------------------------------------------------------------- void SampleConnection::insert(char* nameP) { static long i = 0; insertData.setParam(1, nameP); insertData.setParam(2, i++); insertData.Execute(); } //---------------------------------------------------------------------- ... int main(int argc, char** argv) { ... char name[10]; SampleConnection conn; ... // Assume conn is an open connection. sprintf(name, "Robert"); try { conn.insert(name); } catch (TTStatus st) { cerr << "Error inserting row " << name << ":" << st << endl; conn.Rollback(); } }
TTCmd
クラスには、SQLデータ型、精度、パラメータのスケール(該当する場合)およびパラメータが入力、出力または入力/出力であるかどうかを指定できるメソッドregisterParam()
が用意されています。registerParam()
コールは、出力または入力/出力パラメータに関して必要です(REF CURSOR(出力のみ)、あるいはPL/SQL RETURNING INTO
句(出力のみ)、プロシージャまたは関数からのパラメータが考えられます)。
入力パラメータについては、TTClassesは、デフォルトでsetParam()
またはBindParameter()
コールに対して、表2-1のマッピングに従って、バインドされたCデータ型からSQLデータ型を導出します。通常、入力パラメータにregisterParam()
コールは不要ですが、特定のSQLデータ型、精度またはスケールを使用する必要がある場合は、コールすることが可能です。
表2-1 TTClasses Cデータ型のSQLデータ型へのマッピング
Cデータ型 | SQLデータ型 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
重要: ドライバ・マネージャを使用している場合、前述の表のすべてのCデータ型がサポートされている訳ではありません。「ODBCドライバ・マネージャ使用時の考慮事項(Windows)」を参照してください。 |
registerParam()
コールは、関連するsetParam()
またはBindParameter()
コールの前後どちらかで使用可能であり、そのSQLデータ型、精度、スケール(該当する場合)に関しては優先されます。
メソッドのシグネチャは、次のように指定します。
inline void TTCmd::registerParam(int pno, int inputOutputType, int sqltype, int precision = 0, int scale = 0)
pno
は文内のパラメータの位置を示します。
inputOutputType
は、TTCmd::PARAM_IN
、TTCmd::PARAM_OUT
またはTTCmd::PARAM_INOUT
です。
sqltype
はSQLデータ型のデータです(たとえば、SQL_INTEGER
)。
precision
とscale
(いずれもオプション)は、ODBC SQLBindParameter
コールの場合と同様に使用します。プリミティブ型(int
など)については、precision
とscale
の設定は無視されます。
TTClassesでは、REF CURSOR(出力のみ)などの出力および入/出力パラメータ、OUT
またはIN OUT
パラメータを持つPL/SQLプロシージャまたはファンクションからのパラメータ、またはRETURNING INTO
句からのパラメータ(出力のみ)がサポートされています。
TTClassesにSQL文内のパラメータが出力または入力/出力であるかを通知するには、前述の項で説明したTTCmd::registerParam()
メソッドを使用する必要があります。このメソッド・コールでは、intputOutputType
の設定に適宜TTCmd::PARAM_OUT
またはTTCmd::PARAM_INOUT
を使用します。
バッチ以外の操作では、SQL文の実行後に、適切なTTCmd::getParam()
メソッドを使用し出力値を取得します(パラメータの位置および値の配置先の変数を指定します)。各データ型にはシグネチャがあります。
バッチ操作では、TTCmd::BindParameter()
を、出力パラメータ、入力/出力パラメータまたは入力パラメータに使用します。これは文が実行される前にコールされます。文の実行後、出力値用のデータはBindParameter()
コールで指定したバッファ内に配置されます。BindParameter()
には、データ型ごとにシグネチャがあります。バッチ操作の入力/出力パラメータ用には、文の実行前にBindParameter()
が1回のみコールされます。指定したバッファには、実行前には入力が含まれ、文の実行後には出力が含まれます。
次の例は、出力および入力/出力パラメータの使用方法を示す部分的なコードです。
例2-6 入力および入力/出力パラメータの使用(バッチ以外の場合)
この例では、入力および出力パラメータを使用します。setParam()
コールで、入力パラメータ:a
の値をバインドされます。getParam()
コールで、出力パラメータ:b
の値を取得されます。この出力パラメータも、必要に応じて登録されます。
... // t1 has a single TT_INTEGER column cmd.Prepare(&conn, "insert into t1 values (:a) returning c1 into :b"); cmd.setParam(1, 99); cmd.registerParam(2, TTCmd::PARAM_OUT, SQL_INTEGER); cmd.Execute(); SQLINTEGER outval; if (cmd.getParam(2, &outval)) cerr << "The output value is null." << endl; else cerr << "The output value is " << outval << endl; ...
例2-7 入力および出力パラメータの使用(バッチの場合)
この例では、バッチ操作で入力パラメータおよび出力パラメータを使用します。最初のBindParameter()
コールは、最初のパラメータ:a
の入力データを提供します。2番目のBindParameter()
コールは、2番目のパラメータ:b
の出力データ用にバッファを提供します。
... #define BATCH_SIZE 5 int input_int_array[BATCH_SIZE] = { 91, 92, 93, 94, 95 }; int output_int_array[BATCH_SIZE] = { -1, -1, -1, -1, -1 }; int numrows; cmd.PrepareBatch(&conn, "insert into t1 values (:a) returning c1 into :b", BATCH_SIZE); cmd.BindParameter(1, BATCH_SIZE, input_int_array); cmd.BindParameter(2, BATCH_SIZE, output_int_array); cmd.registerParam(2, TTCmd::PARAM_OUT, SQL_INTEGER); numrows = cmd.ExecuteBatch(BATCH_SIZE); ...
例2-8 入力/出力パラメータの使用
この例では、入力/出力パラメータを使用します。これは必要に応じて登録できます。setParam()
コールはその入力値をバインドし、getParam()
コールはその出力値を取得します。
... cmd.Prepare(&conn, "begin :x := :x + 1; end;"); cmd.registerParam(1, TTCmd::PARAM_INOUT, SQL_INTEGER); cmd.setParam(1, 99); cmd.Execute(); SQLINTEGER outval; if (cmd.getParam(1, &outval)) cerr << "The output value is null." << endl; else cerr << "The output value is " << outval << endl; ...
例2-9 出力および入力/出力パラメータの使用
この例では出力および入力/出力パラメータを使用します。次のPL/SQLプロシージャがあるとします。
create or replace procedure my_proc ( a in number, b in number, c out number, d in out number ) as begin c := a + b; d := a + b - d; end my_proc;
このプロシージャの入力パラメータは、この例ではバインド・パラメータとしてではなく定数として取られるため、OUT
パラメータおよびIN OUT
パラメータのみがバインドされます。必要に応じて両方が登録されます。setParam()
コールは、IN OUT
パラメータ:var1
の入力値を提供します。最初のgetParam()
コールは、OUT
パラメータ:sum
の値を取得します。2番目のgetParam()
コールは、IN OUT
パラメータ:var1
の出力値を取得します。
... cmd.Prepare(&conn, "begin my_proc (10, 5, :sum, :var1); end;"); cmd.registerParam (1, TTCmd::PARAM_OUT, SQL_DECIMAL, 38); cmd.registerParam (2, TTCmd::PARAM_INOUT, SQL_DECIMAL, 38); cmd.setParam(2, "99"); cmd.Execute(); SQLINTEGER outval1, outval2; if (cmd.getParam(1, &outval1)) cerr << "The first output value is null." << endl; else cerr << "The first output value is " << outval << endl; if (cmd.getParam(2, &outval2)) cerr << "The second output value is null." << endl; else cerr << "The second output value is " << outval << endl; ...
TimesTenでは、SQL文内に出現する複数の同名パラメータはそれぞれ異なるパラメータとみなされます。(これはOracle Databaseの重複パラメータのバインディングに関するサポートと一貫しています。)
ノート:
|
次の問合せについて考えてみます。
SELECT * FROM employees WHERE employee_id < :a AND manager_id > :a AND salary < :b;
パラメータの位置番号が割り当てられるとき、名前の重複に関係なく、パラメータの出現ごとに番号が与えられます。アプリケーションは、少なくとも各パラメータ名の最初の出現時に値をバインドする必要があります。特定のパラメータ名がそれ以降に出現した場合は、アプリケーションはその出現に対して別の値をバインドすることも、そのパラメータの出現をバインドしないこともできます。後者の場合、最初の出現時と同じ値がそれ以降の出現時に使用されます。いずれの場合も、出現ごとに異なるパラメータ位置番号が付けられます。
例2-10 重複パラメータ
この例では、前述のSQL文のa
の2回目の出現に対して異なる値を使用しています。
mycmd.setParam(1, ...); // first occurrence of :a mycmd.setParam(2, ...); // second occurrence of :a mycmd.setParam(3, ...); // occurrence of :b
a
の両方の出現に対して同じ値を使用するには、次のように指定します。
mycmd.setParam(1, ...); // both occurrences of :a mycmd.setParam(3, ...); // occurrence of :b
パラメータb
は、位置3に存在するとみなされ、パラメータ数は3とみなされます。
REF CURSORはPL/SQLの概念で、SQLの結果セット上のカーソルに対するハンドルであり、PL/SQLとアプリケーションの間で渡すことができます。TimesTenでは、このカーソルをPL/SQL内で開くことができるため、REF CURSORをアプリケーションに渡して処理することができます。この使用方法は、OUT
REF CURSORであり、これはPL/SQLに関するOUT
パラメータです。すべての出力パラメータと同様に、これはTTCmd::registerParam()
メソッドで登録する必要があります。(「パラメータの登録」および「出力または入力/出力パラメータのバインド」を参照してください。)
TimesTenの実装では、REF CURSORは別の文ハンドルにアタッチされています。アプリケーションは、1つの文ハンドルにREF CURSORパラメータを持つSQL文を準備し、次に、この文の実行前に、2つ目の文ハンドルをREF CURSORの値としてバインドします。この文の実行後、アプリケーションはこの結果の記述、バインドおよびフェッチを、結果セットの場合と同じAPIを使用して実行することができます。
TTCmd
オブジェクトは単一のSQL文をカプセル化するため、TTClassesでは2つのTTCmd
オブジェクトが使用してこのREF CURSORモデルをサポートします。
REF CURSORの詳細は、『Oracle TimesTen In-Memory Database PL/SQL開発者ガイド』のPL/SQL REF CURSORに関する説明を参照してください。
重要:
|
次に説明する、TTClassesでREF CURSORを使用するステップを、以降の例2-11で示します。
REF CURSORを返すPL/SQL文に対して、TTCmd
オブジェクトを宣言します(この例ではcmdPLSQL
)。
TTCmd*
ポインタを宣言して、REF CURSOR用の2つ目のTTCmd
オブジェクトをポイントします(この例ではcmdRefCursor
)。
最初のTTCmd
オブジェクト(cmdPLSQL
)を使用してPL/SQL文を準備します。
最初のTTCmd
オブジェクトのTTCmd::registerParam()
メソッドを使用して、このREF CURSORを出力パラメータとして登録します。
最初のTTCmd
オブジェクトを使用して、文を実行します。
最初のTTCmd
オブジェクトのTTCmd::getParam()
メソッドを使用して、REF CURSORを2つ目のTTCmd
オブジェクト(&cmdRefCursor
を使用)に取得します。REF CURSORには、getParam(int
paramNo
、TTCmd**
rcCmd
)
のシグネチャがあります。
REF CURSORのTTCmd
オブジェクトから結果をフェッチして、必要に応じて処理します。
最初のTTCmd
オブジェクトを削除します。
REF CURSORのTTCmd
オブジェクトへのポインタを削除します。
delete
文を発行して、REF CURSORのTTCmd
オブジェクトを削除します。
例2-11 REF CURSORの使用
この例では、PL/SQL無名ブロックからREF CURSORを取得および処理します。説明については、前述のステップを参照してください。
... TTCmd cmdPLSQL; TTCmd* cmdRefCur; TTConnection conn; ... // c1 is a TT_INTEGER column. cmdPLSQL.Prepare(&conn, "begin open :rc for select c1 from t; end;") cmdPLSQL.registerParam(1, TTCmd::PARAM_OUT, SQL_REFCURSOR); cmdPLSQL.Execute(); if (cmdPLSQL.getParam(1, &cmdRefCur) == false) { SQLINTEGER fetchval; while (!cmdRefCursor->FetchNext()) { cmdRefCur->getColumn(1, &fetchval); } cmdRefCursor->Drop(); delete cmdRefCursor; } cmdPLSQL.Drop();
TTClassesでREF CURSORを使用する場合は、次のような使用上のノートがあります。
PL/SQLとアプリケーションの間でREF CURSORを渡す場合、TimesTenでは、PL/SQLからアプリケーションへのOUT
REF CURSORのみと、単一のREF CURSORのみを返す文がサポートされます。
他のデータ型に対するTTCmd::getParam()
コールとは異なり、REF CURSOR用のTTCmd**
パラメータを使用したgetParam()
コールは1回のみコールできます。それ以降のコールではNULL
が返されます。REF CURSORを2度取得する必要がある場合は、文を再実行する必要があります。
文を複数回実行する場合は、そのたびにREF CURSORパラメータを登録する必要があります。たとえば、ループ内で文の実行、REF CURSORパラメータの取得およびREF CURSORからのフェッチを行うには、次のようにパラメータの登録もループ内で行う必要があります。
cmdPLSQL.Prepare(...); loop cmdPLSQL.registerParam(...); cmdPLSQL.Execute(); cmdPLSQL.getParam(...); fetch loop end loop
これを次の例2-12に示します。
どのTTCmd
オブジェクト(REF CURSOR用のものを含む)も、ODBC文ハンドルが割り当てられます。REF CURSOR文ハンドルは、Drop()
文の実行時に削除され、そのリソースはdelete
文の後に解放されます。
例2-12 ループでのREF CURSORの使用
この例では、ループでREF CURSORを使用します。次の宣言とTTConnection
インスタンスconn
を想定しています。
... TTCmd query; TTCmd* ref_cur; ...
ループは次のとおりです。
... cerr << "Selecting values using cursor" << endl; query.Prepare(&conn, "begin open :rc for select c1 from t1; end;"); for (int round = 0; round < ROUNDS; round++) { cerr << "executing ref cursor round# " << (round+1) << endl; query.registerParam(1, TTCmd::PARAM_OUT, SQL_REFCURSOR); query.Execute(); query.getParam(1, &ref_cur); while(true) { fetch_next = ref_cur -> FetchNext(); if (fetch_next == 1) break; ref_cur -> getColumn(1, &val); cerr << "val = " << val << endl; } ref_cur->Drop(); delete ref_cur; } conn.Commit(); query.Drop(); ...
表の各行には、ROWIDと呼ばれる一意の識別子があります。アプリケーションでは、ROWID
疑似列から行のROWIDを取得します。ROWIDはバイナリまたは文字形式で表すことができます。
アプリケーションは、リテラルのROWID値を、SQL文のWHERE
句などで、一重引用符で囲んだCHAR
定数として指定できます。
ODBC SQLデータ型のSQL_ROWID
は、SQLデータ型のROWID
と対応します。
パラメータおよび結果セット列では、ROWIDは、Cデータ型のSQL_C_BINARY
、SQL_C_WCHAR
およびSQL_C_CHAR
との間で双方向に変換可能です。SQL_C_CHAR
は、ROWIDのデフォルトのCデータ型です。ROWIDのサイズは、SQL_C_BINARY
としては12バイト、SQL_C_CHAR
としては18バイトおよびSQL_C_WCHAR
としては36バイトです。
通常、TTClassesでは文字列としてのROWIDがサポートされていますが、TTClassesアプリケーションでは、ROWIDを文字列としてではなくROWID
型としてPL/SQL無名ブロックに渡せるようになったことに注意してください。これを行うには、例2-13
のように、TTCmd::registerParam()
メソッドを使用してROWID入力パラメータをSQL_ROWID型として登録する必要があります。
例2-13 ROWIDの使用
... TTConnection conn; TTCmd cmd; ... cmd.Prepare(&conn, "begin delete from t1 where rowid = :x; end;"); cmd.registerParam(1, TTCmd::PARAM_IN, SQL_ROWID); cmd.setParam(1, rowid_string); cmd.Execute(); ...
ROWIDおよびROWID
データ型(使用方法および有効期間を含む)の詳細は、『Oracle TimesTen In-Memory Database SQLリファレンス』のROWIDデータ型およびROWID擬似列を参照してください。
ノート: TimesTenでは、PL/SQL型のUROWID はサポートされません。 |
TimesTen ClassicではLOB(ラージ・オブジェクト)がサポートされています。これには、CLOB(Character LOB)、NCLOB(各国語LOB)およびBLOB(バイナリLOB)が含まれます。
LOBの概要およびCおよびC++でのLOBプログラミング・インタフェースの詳細は、『Oracle TimesTen In-Memory Database C開発者ガイド』のLOBの使用に関する説明を参照してください。LOBの簡易データ・インタフェースのみをTTClassesに適用可能です。
この項では、TTClassesでのLOBの使用について、次の内容で説明します。
重要: TimesTenでは、アプリケーションで使用されるLOBは、トランザクションが終了すると無効になります。 |
次の情報も参照できます。
TimesTenでのLOBに関する詳細は、『Oracle TimesTen In-Memory Database SQLリファレンス・ガイド』のLOBのデータ型に関する説明を参照してください。
LOBでのプログラミングに関する一般的な情報については、『Oracle Database SecureFilesおよびラージ・オブジェクト開発者ガイド』を参照してください(TimesTenの機能のみには限定されていません)。
TimesTenとOracle DatabaseのLOBサポートを比較する場合は、次の点に注意してください。
LOBのTimesTenでの実装とOracle Databaseでの実装の主要な違いは、TimesTenでは、アプリケーションで使用されるLOBは、トランザクションが終了すると無効になるということです。そのようなLOBは、コミットまたはロールバック後、明示または暗黙にかかわらず、無効になります。DDL文の後も同様です。
TimesTenでは、BFILE、SecureFile、LOBに対する配列の読取りおよび書込みおよびLOBのコールバック関数はサポートされていません。
TimesTenでは、LOBの配列のバインドはサポートされていません。
TimesTenでは、LOBのバッチ処理はサポートされていません。
BLOBの場合、TimesTenでの16進リテラルの使用に違いがあります(詳細は、『Oracle TimesTen In-Memory Database SQLリファレンス』の定数
のHexadecimalLiteralに関する説明を参照してください)。
アプリケーションは、他のスカラー型同様、簡易データ・インタフェースでバインドおよび定義することによってLOBデータにアクセスできます。TTClassesの簡易データ・インタフェースでは、パラメータのバインドにgetParam()
とsetParam()
を使用し、結果行の定義にはgetColumn()
またはgetColumnNullable()
を使用します。アプリケーションは、SQLデータ型を使用してバインドまたは定義することが可能で、このSQLデータ型は次に示すように対応する変数型と互換性があります。
BLOBデータには、SQLデータ型のSQL_LONGVARBINARY
とCデータ型のSQL_C_BINARY
を使用します。
CLOBデータには、SQLデータ型のSQL_LONGVARCHAR
とCデータ型のSQL_C_CHAR
を使用します。
NCLOBデータには、SQLデータ型のSQL_WLONGVARCHAR
とCデータ型のSQL_C_WCHAR
を使用します。
ノート:
|
例2-14に、TTClassesでのLOBの簡易データ・インタフェースの使用例を示します。
例2-14 TTClassesでのLOBの使用
この例は、作成および移入済のNCLOB
、BLOB
およびCLOB
列がある表を前提としています。これらのLOB型に対して実行されるメソッドは、NCHAR
、BINARY
およびCHAR
の場合とそれぞれ同じです。
#ifdef _WIN32 #include <ttcommon.h> #endif #include "TTInclude.h" #define LOB_COL_SIZE 4194304 int main(int argc, char** argv) { TTConnection conn; TTCmd query; char conn_str[100] = "... your connection string ..."; char tbl_name[20] = "... test table name ..."; int num_rows = 0; char query_stmt[1000]; int fetch_next; int value_is_null = 0; int column_type; SQLWCHAR * unicode_val; u_char * binary_val; char * alfanum_val; int b_len; int u_len; cerr << "Connecting to TimesTen <" << conn_str << ">" << endl; try { conn.Connect(conn_str); sprintf(query_stmt, "select * from %s", tbl_name); query.Prepare(&conn, query_stmt); query.Execute(); const int num_result_cols = query.getNColumns(); while (true) { // loop until no rows found // fetch a row; if no more rows, break out of loop // FetchNext returns 0 for success, 1 for SQL_NO_DATA_FOUND fetch_next = query.FetchNext(); if (fetch_next == 1) break; for (int col = 1; col <= num_result_cols; col++) { value_is_null = 0; column_type = query.getColumnType(col); switch (column_type) { case SQL_WLONGVARCHAR: value_is_null = query.getColumnNullable(col, (SQLWCHAR**) & unicode_val, &u_len); if (value_is_null) { cerr << "NCLOB value is NULL"; } else { cerr << "NCLOB value length = " << u_len << endl; // do something with NCLOB value } break; case SQL_LONGVARBINARY: value_is_null = query.getColumnNullable(col, (void**) & binary_val, &b_len); if (value_is_null) { cerr << "BLOB value is NULL"; } else { cerr << "BLOB value length = " << b_len << endl; // do something with BLOB value } break; case SQL_LONGVARCHAR: alfanum_val = (char*) malloc(LOB_COL_SIZE + 1); value_is_null = query.getColumnNullable(col, alfanum_val); if (value_is_null) { cerr << "CLOB value is NULL"; } else { cerr << "CLOB value length = " << strlen(alfanum_val) << endl; // do something with BLOB value } free(alfanum_val); break; default: break; } } num_rows++; cerr << "row " << num_rows << " fetched" << endl; } cerr << num_rows << " rows returned" << endl; } catch (TTError err) { cerr << "\nError" << err << endl; } query.Drop(); conn.Disconnect(); return 0; }
TimesTenには、SQL文またはプロシージャ・コールの実行時間を制限する方法として、タイムアウト値の設定と、しきい値の設定の2つがあります。前者の場合、タイムアウト期間に達すると、文の実行が停止し、エラーがスローされます。値0はタイムアウトがないことを示します。後者の場合、しきい値に達すると、警告がサポート・ログに書き込まれますが実行は継続されます。値0は警告がないことを意味します。
問合せのタイムアウト制限は、SQL文がアクティブに実行されている場合にのみ有効です。コミット中またはロールバック中にはタイムアウトは発生しません。
TTCmd
オブジェクトに対し、これらを設定するには、TTCmdメソッドの
setQueryTimeout()および
setQueryThreshold()を指定します。これらのメソッドは、TimesTenの接続属性
SQLQueryTimeout
(またはSQLQueryTimeoutMsec
)およびQueryThreshold
のそれぞれの設定をオーバーライドする点に注意してください。これらの各接続属性のデフォルト値は0で、タイムアウトまたはしきい値が設定されていません。
getQueryThreshold()
メソッドでは、現在のしきい値の設定を読み取ることもできます。
これらの機能は、TTClassesでは、文レベルのみで使用でき、接続レベルでは使用できません。
関連情報については、『Oracle TimesTen In-Memory Database C開発者ガイド』のSQL文の実行のためのタイムアウトまたはしきい値の設定に関する説明を参照してください。タイムアウト値の関係の詳細は、Oracle TimesTen In-Memory Databaseオペレーション・ガイドのSQLおよびPL/SQLのタイムアウト値の選択を参照してください。
ノート: ロック・タイムアウト値およびSQL問合せのタイムアウト値の両方が指定されている場合は、まず、2つの値の小さい方の値によってタイムアウトが発生します。ロック・タイムアウトについては、『Oracle TimesTen In-Memory Databaseリファレンス』のttLockWait(組込みプロシージャ)またはLockWait(一般接続属性)に関する説明、またはOracle TimesTen In-Memory Databaseトラブルシューティング・ガイドのデッドロックとタイムアウトの確認に関する説明を参照してください。 |
TTClassesには、独自の自動クライアント・フェイルオーバー機能はありませんが、ODBCアプリケーションの場合と同様にTTClassesアプリケーションでTimesTen自動クライアント・フェイルオーバーを構成できます。これについては、『Oracle TimesTen In-Memory Database C開発者ガイド』の自分のアプリケーションでの自動クライアント・フェイルオーバーの使用に関する説明を参照してください。TimesTen Scaleoutについては、Oracle TimesTen In-Memory Database Scaleoutユーザーズ・ガイドのクライアント接続フェイルオーバーも参照してください。TimesTen Classicについては、『Oracle TimesTen In-Memory Databaseオペレーション・ガイド』の自動クライアント・フェイルオーバーの使用を参照してください。
TTClassesには、アプリケーションでデバッグ情報を取得するためのログ機能が用意されています。TTClassesのログは、プロセスと関連付けられます。特定のプロセスに対してロギングを有効にし、そのプロセスに対して1つの出力ログのストリームを作成できます。
TTClassesでは、様々なレベルのロギング情報をサポートしています。各ログ・レベルでの出力については、例2-16を参照してください。
ログ・レベルTTLOG_WARN
は、TTClassesアプリケーションの開発時に非常に便利です。また、このログ・レベルではデータベースへの問合せ計画が生成されるため、本番アプリケーションにも適しています。
冗長度の高いログ・レベル(TTLOG_INFO
およびTTLOG_DEBUG
)では大量のログ・データが生成されるため、アプリケーションのパフォーマンスに悪影響がある場合があります。これらのログ・レベルは本番環境では使用しないでください。
TTClassesロギングではstdout
またはstderr
のいずれかに出力することもできますが、TTClassesログ・ファイルに直接書き込むことをお薦めします。TTClassesのログ情報を、TTLOG_WARNのログ・レベルで/tmp/ttclasses.log
出力ファイルに出力する方法を例2-15
に示します。
ノート: デフォルトでは、TTClassesロギングは無効になっています。 |
例2-15 TTClassesログ情報の出力
ofstream output; output.open("/tmp/ttclasses.log"); TTGlobal::setLogStream(output); TTGlobal::setLogLevel(TTLog::TTLOG_WARN);
TTClassesを初めて使用するユーザーは、TTClassesロギングを試して、ログ・レベルTTLOG_ERROR
でエラーがどのように出力されるか、ログ・レベルTTLOG_INFO
およびTTLOG_DEBUG
で生成される情報の量を確認してください。
TTGlobalクラスを使用してロギングを行う方法の詳細は、「TTGlobal」
を参照してください。
TimesTen Classicでサポートされているトランザクション・ログAPI(XLA)とは、指定したデータベース表への変更についてTimesTenを監視し、これらの変更の通知をリアルタイムで受信するアプリケーションを実装できる関数のセットです。
XLAの主な目的は、トリガーに対する、高性能で非同期の代替手段を提供することです。
XLAは、データベース内の特定の表への変更の通知を返すとともに、これらのデータベース変更のトランザクション境界に関する情報も返します。この項では、トランザクション境界を使用しない例および使用する例を使用して、トランザクション境界のみでの更新(XLAアプリケーションでの一般的な要件)を確認する方法を示します。
この項のトピックは、次のとおりです。
XLAの詳細は、『Oracle TimesTen In-Memory Database C開発者ガイド』のXLAおよびTimesTenイベント管理を参照してください。また、TimesTen Classicクイック・スタートで提供されるTTClassesサンプル・アプリケーションにXLAアプリケーションが含まれています。TimesTenクイック・スタートおよびサンプル・アプリケーションを参照してください。
重要:
|
例2-16に、トランザクション境界を使用しないXLAの基本的な使用方法を示します。
HandleChange()
メソッド内では、レコードがinsertか、updateか、deleteかに応じて、HandleInsert()
、HandleUpdate()
、HandleDelete()
のいずれかの適切なメソッドがコールされます。
XLAレコードが特定のトランザクションにおける最後のレコードかどうかを示すフラグにユーザーがアクセスできるのは、HandleChange()
内です。したがって、例2-16のループ内で、HandleChange()
メソッドが、トランザクション境界に関する情報をループに渡して、この情報がconn.ackUpdates()
をコールするタイミングに影響を与えることができるようにする方法はありません。
1つのトランザクションにつきレコードが数個しかない一般的な状況では、これは問題になりません。fetchUpdatesWait()
コールで最大1000個のレコードを返すようXLAに要求しても、通常は数個のレコードが返されるだけです。XLAはできるだけ速くレコードを返し、データベース内で膨大な数のトランザクションが発生していても、通常はXLAレコードを一度に数個ずつ、短時間で取得することができ、XLAは最後に返されたレコードがトランザクション境界上にあることを確認します。たとえば、XLAに1000個のレコードを要求して、15個しか返されない場合は、15番目のレコードがほぼ確実にトランザクションの終了に当たります。
XLAによって、次のいずれかが保証されます。
完了したトランザクション(おそらくXLAレコードの単一バッチにおける複数のトランザクション)でレコードのバッチが終了すること。
または
レコードのバッチに部分トランザクションが含まれ、同じバッチ内に完了したトランザクションがないこと、およびトランザクション境界に達するまで、その単一トランザクションの後続のXLAレコードのバッチが返されること。
例2-16 TTClassesのXLAプログラム
次の例に、TTClassesのXLAプログラムの一般的なメイン・ループを示します。(また、シグナル・ハンドラも存在すると仮定しています。)
TTXlaPersistConnection conn; // XLA connection TTXlaTableList list(&conn); // tables being monitored ttXlaUpdateDesc_t ** arry; // pointer to returned XLA records int records_fetched; // ... while (!signal_received) { // fetch the updates conn.fetchUpdatesWait(&arry, MAX_RECS_TO_FETCH, &records_fetched, ...); // Interpret the updates for(j=0;j < records_fetched;j++){ ttXlaUpdateDesc_t *p; p = arry[j]; list.HandleChange(p, NULL); } // end for each record fetched // periodically call ackUpdates() if (/* some condition is reached */) { conn.ackUpdates(); } }
XLAアプリケーションでは、XLAレコードのバッチにおける最後のレコードにトランザクション境界があるかどうか検証し、このトランザクション境界のみでackUpdates()
をコールする必要があります。このように、アプリケーション、システムまたはデータベースが失敗した場合、システムのリカバリ後はXLAブックマークがトランザクションの始めにあることなります。これは、操作に多数の行が関連する場合に特に重要です。データベースに対して挿入、更新、削除のバルク操作を実行する場合に、XLAアプリケーションが1000個のレコードを要求すると、1000個のレコードすべてが受信される場合もされない場合もあります。XLAを介して最後に返されるレコードには、おそらくトランザクション終了フラグが設定されていません。実際、トランザクションによって10,000個のレコードが変更された場合、トランザクション境界に達するまでに1000個のXLAレコードのブロックを最低でも10個フェッチする必要があります。
ただし、ackUpdates()
は相対的に負荷の高い操作であるため、トランザクション境界ごとにackUpdates()
をコールすることはお薦めしません。ユーザーは、リカバリ時間およびファイル・システム領域の要件において、システムの全体的なスループットのバランスをとる必要があります。(XLAにそのログ・ファイルを参照するブックマークがある場合、チェックポイント操作ではTimesTenのトランザクション・ログ・ファイルを削除できない点に注意してください。関連情報は、『Oracle TimesTen In-Memory Databaseリファレンス』のttLogHoldsに関する説明を参照してください。)システムのスループット、リカバリ時間およびファイル・システム領域の要件に応じ、アプリケーションによっては、1分ごとにackUpdates()
を1回または複数回コールすることが適切である場合もあれば、1時間に1回または数回しかコールする必要がない場合もあります。
HandleChange()
メソッドには、HandleChange()
とメインXLAループとの間で情報の受渡しを可能にする第2のパラメータがあります。前述の例2-16と例2-17の特にdo_acknowledge
の設定とHandleChange()
コールの&do_acknowledge
パラメータを比較してください。
例2-17 トランザクション境界を使用するTTClassesのXLAプログラム
この例では、ackUpdates()
は、このXLAレコードのバッチがトランザクション境界にあることがdo_acknowledge
フラグによって示された場合にのみコールされます。(この例では、シグナル・ハンドラも存在すると仮定しています。)
TTXlaPersistConnection conn; // XLA connection TTXlaTableList list(&conn); // tables being monitored ttXlaUpdateDesc_t ** arry; // ptr to returned XLA recs int records_fetched; int do_acknowledge; int j; // ... while (!signal_received) { // fetch the updates conn.fetchUpdatesWait(&arry, MAX_RECS_TO_FETCH, &records_fetched, ...); do_acknowledge = FALSE; // Interpret the updates for(j=0;j < records_fetched;j++){ ttXlaUpdateDesc_t *p; p = arry[j]; list.HandleChange(p, &do_acknowledge); } // end for each record fetched // periodically call ackUpdates() if (do_acknowledge == TRUE /* and some other conditions ... */ ) { conn.ackUpdates(); } }
XLAのメイン・ループに対するこの変更だけではなく、HandleChange()
メソッドをオーバーロードして、2つパラメータを持つ(ttXlaUpdateDesc_t*、void*
pData
)
ようにする必要があります。「HandleChange()」を参照してください。TimesTen Classicクイック・スタートのxlasubscriber1
サンプル・アプリケーションでpData
パラメータの使用方法を確認できます。(TimesTenクイック・スタートおよびサンプル・アプリケーションを参照してください。)
システム権限XLA
は、XLAリーダーとしてTimesTenに接続する場合(CREATE SESSION
権限も必要)、XLA関連のTimesTen C関数を実行する場合、およびXLA関連のTimesTen組込みプロシージャを実行する場合など、すべてのXLA機能で必要になります。
詳細は、『Oracle TimesTen In-Memory Database C開発者ガイド』のアクセス制御がXLAに与える影響に関する説明を参照してください。
ノート: XLA 権限を持つユーザーは、データベースで実行されるすべてのDML文の通知を受信できます。その結果、XLA 権限を持つユーザーは、この権限がない場合にアクセスが許可されていなかったデータベース・オブジェクトに関する情報を取得可能になります。実際、XLA 権限は、実質的にSELECT ANY TABLE 、SELECT ANY VIEW およびSELECT ANY SEQUENCE 権限と同じです。 |