データベース操作実験

InterBase / FireBird + dbExpress (with C++ Builder) 編
SQL 埋め込み風味の低レベルアクセス実験3
- SQL+パラメータによる BLOB 操作 -
2004/08/04 HTML更新



Firebird/InterBaseの部屋へ  実験工房へ  総合HOMEPAGEへ戻る

 "InterBase / Firebird" + "C++ Builder 6" + "dbExpress" を使用した、埋め込み SQL 風味 の低レベルアクセス実験その3です。
 SQL 発行時に、該当フィールドに '?' を設定しておくことにより、長いデータや SQL 文 では取り扱いが難しい BLOB の取り扱いについて書いてみたいと思います。
 C++ Builder 6 を対象にしていますが、Kylix や Delphi でも基本的には同様だと思います。
 試してはいないのですが、dbExpress で MySQL や Oracle などの BLOB がある他データベースを 扱う場合も、この程度の処理なら基本的には同じでしょう。

− はじめに −

 その1で、dbExpress のTSQLConnection コンポーネントのみを使用してデータベース を自在にアクセスする方法について述べました。  今回は、その1で予告していた、パラメータを使用して BLOB を取り扱う方法に ついて書きたいと思います。

 バイナリデータや、サイズの大きなデータを取りあつかう場合に BLOB データタイプ は非常に便利ですが、制御コードとおなじ文字を含むバイナリデータを SQL 文に 含めるのは、いろいろ問題が出る場合があります。
 そのような場合は、TSQLConnection コンポーネントの SQL 発行メソッド (Execute) にあるパラメータ指定を使用します。
 パラメータ生成時のタイプ指定によっては BLOB 以外のデータも渡せますので、SQL を 固定文字列にしてデータフィールドのみを随時変更するような使用方法も可能です。

 文字列型のサブタイプを持つ BLOB の場合、InterBase / FireBird の場合は SQL 上に直接データを記述して取り扱えるようですが、サイズが大きいと適当に切り捨て られる現象が発生しましたので、パラメータ指定のほうが無難な模様です。

 ちなみに、テストはサーバ側 (データベース本体)は LAN上の Linux + Firebird 1.0.3、 クライアント側は Windows 2000 + Apache 1.3.29 + WebBroker + 自作 Apache DSO の 環境で行いました。データベースの文字コードは、最終クライアントに合わせてシフトJIS を指定しています。
 また、Linux + kylix + Apache 環境への移植を睨んでいたため、VCL ではなく CLX を前提にしています。


− SQL 上の指定 −

 パラメータ本体はメソッドの引数で指定しますが、どのフィールド(列)がパラメータ 経由で与えられるかを SQL 上で記述しておく必要があります。
 具体的には、VALUES 指定の該当フィールドに '?' を記述します。
 例として、DATA1(INTEGER), DATA2(BLOB), DATA3(INTEGER) の列がある SPTINFO テーブルに行を挿入する場合は、以下のように SQL 文を設定します。 (DATA1=100, DATA2=パラメータ指定, DATA3=200)



INSERT INTO SPTINFO (DATA1, DATA2, DATA3) VALUES (100, ?, 200)


− パラメータ作成と値の設定 −

 パラメータは、TParams コンポーネントで TSQLConnection::Execute メソッドに 受け渡しを行います。

 まず、TParams コンポーネントは静的には作成できないようなので、new 演算子で 作成します。当然のことながら、使用後は delete を行うのを忘れないように注意して 下さい。

 各項目の設定は、TParams::CreateParam メソッドにより TParam 型のコンポーネント を1項目毎に生成し、生成後に値を設定します。フィ−ルド名 (列名) は CreateParam メソッドの第2引数で生成時に指定します。
 値本体の設定は、項目生成時に指定したタイプに合ったメソッドで行います。  BLOB の場合は、TParam::SetBlobData (TParams::でないことに注意!) メソッドで 設定を行います。他のタイプに関しては、適宜 C++ Builder 付属のヘルプを参照して ください。

− 使用例 −

 上述の SPTINFO テーブルに、新行を追加する一連のソースリストは次のようになります。
 ちなみに、SQLConnectionコンポーネント(SQLCntオブジェクト)の設定とデータベースへの 接続は、事前に完了していることを前提としています。また、data オブジェクトは AnsiString 型のデータで、これも外部から与えられるものとしています。
 トランザクションに関しては、明示的な指定を行っていないので、InterBase/Firebird の 場合はデフォルトのトランザクションを使用していることになります。 そのため、変更を確実に反映するための COMMIT / ROLLBACK 処理も入っています。
 CreateParamメソッドの ftBlob は、パラメータが BLOB 型であることを示しています。 また、ptInput は入力用のパラメータであることを示しています。詳細に関しては、 C++ Builder のヘルプを参照してください。


    // SQL文作成
    AnsiString  sql1 = "INSERT INTO SPTINFO (DATA1, DATA2, DATA3) VALUES(100, ?, 200)";

    // SQL発行 & リトライループ
    bool    ret = false;
    for (int retry = 20 ; retry >= 0 ; retry--) {
        // パラメータセットアップ
        TParams* prm = new TParams;
        TParam*  np = prm->CreateParam(ftBlob, "DATA2", ptInput);
        np->SetBlobData(data.c_str(), data.Length()+1);     // サイズを +1 しているのは安全策
        try {
            try {
                ret = false;
                // SQL 発行
                if (SQLcnt->Execute(sql1, prm, NULL) > 0) {
                    ret = true;
                }
                // 正常終了
                SQLcnt->ExecuteDirect("COMMIT");
            }
            catch (...)
            {
                SQLcnt->ExecuteDirect("ROLLBACK");
            }
        }
        __finally {
            // エラーが出てもパラメータオブジェクトを削除
            delete prm;
            // 何度かリトライが続く場合、切断・再接続を試みてみる
            // 注) エラーでも処理中断しないように、例外処理をハンドルしている
            try {
                if (ret != true && retry%10 == 5) {
                    SQLcnt->Close();
                    Sleep(100);
                    SQLcnt->Open();
                }
            }
            catch (...)
            {
            }
        }
        if (ret == false) {
            // 成功していない場合、少し休んでリトライ
            Sleep(200);     // 200msec
        } else {
            // 正常終了
            break;
        }
    }




−了−
Firebird/InterBaseの部屋へ
実験工房へ
総合HOMEPAGEへ戻る