7 LOBによるプログラミング

この章では、LOBおよびOCCIでの使用方法の概要を説明します。

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

関連項目:

LOBの広範な情報については、『Oracle Database SecureFilesおよびラージ・オブジェクト開発者ガイド』を参照してください

7.1 LOBの概要

Oracle C++ Call Interfaceには、ラージ・オブジェクト(LOB)に対して操作を実行するためのクラスおよびメソッドが含まれます。LOBは、データベースに対する位置に応じて、内部または外部のいずれかとなります。

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

7.1.1 内部LOBの概要

内部LOBは、領域を最適化して効率的なアクセスを提供するようにデータベース表領域の内部に格納されます。内部LOBはコピー・セマンティクスを使用し、サーバーのトランザクション・モデルに関与します。トランザクションまたはメディアに障害が発生した後に内部LOBをリカバリでき、内部LOB値に対する変更はすべてコミットまたはロールバックできます。内部LOBのインスタンスの決定には、次に示す3種類のSQLデータ型があります。

  • BLOB: 値が非構造化バイナリ(RAW)データで構成されているLOB

  • CLOB: Oracleデータベースに対して定義されたデータベース・キャラクタ・セットに対応する文字データで値が構成されているLOB

  • NCLOB: Oracleデータベースに対して定義された各国語キャラクタ・セットに対応する文字データで値が構成されているLOB

LOBのコピー・セマンティクスは、LOBを挿入する際や、同じ表内の別の行に含まれるLOBでLOBを更新する際に、LOBロケータおよびLOB値の両方がコピーされるよう指示します。つまり、各行にLOB値のコピーが置かれます。

7.1.2 外部LOBの概要

BFILEは、データベース表領域外のオペレーティング・システム・ファイル内に格納される大規模なバイナリ(raw)・データ・オブジェクトです。それゆえ、外部LOBと呼ばれます。これらのファイルは参照セマンティクスを使用し、そこでは同じ表内での挿入時や更新時にLOBのロケータのみが生成されます。ハードディスクなど従来型のセカンダリ・ストレージ・デバイスの他に、BFILEはCD-ROM、PhotoCD、DVDなどの3次ブロック・ストレージ・デバイス上に配置することもできます。BFILEデータ型を使用すると、データベース・サーバーのファイル・システムにあるファイルに読取り専用バイト・ストリームによるアクセスが可能となります。OracleからBFILEにアクセスできるのは、基礎となるサーバーのオペレーティング・システムが、ストリーム・モードによるこれらのファイルへのアクセスをサポートしている場合です。

外部LOBは、トランザクションには関係ありません。整合性および耐久性は、基礎となるファイルおよびオペレーティング・システムによってサポートされる必要があります。外部LOBは単一デバイス上に存在する必要があります。ディスク・アレイ上でストライプ化することはできません。

7.1.3 LOBの格納について

LOB値の格納場所を決定するのは、主にそのサイズです。LOB値は、行データとインラインに、または行の外に格納されます。

  • ロケータ格納: LOB値の実際の場所へのポインタであるLOBロケータは、行データとインラインに格納されており、LOB値の格納場所を示します。

    内部LOBの場合、データベース表領域に格納されたLOB値へのロケータがLOB列に格納されます。各内部LOB列および特定の行の属性には、それぞれ一意のLOBロケータと、データベース表領域に格納されたLOB値の個別コピーが含まれます。

    外部LOBの場合、BFILEが格納された外部オペレーティング・システム・ファイルへのロケータがLOB列に格納されます。各外部LOB列および指定行の属性には、それぞれのBFILEロケータがあります。ただし、2つの異なる行に、同じオペレーティング・システム・ファイルを指すBFILEロケータが含まれる場合があります。

  • インライン記憶域: LOBに格納されたデータはLOB値と呼ばれます。内部LOBの値は、他の行データとともにインラインに格納される場合とされない場合があります。DISABLE STORAGE IN ROWを設定せずに、内部LOB値が約4,000バイトを下回ると、値はインラインに格納されます。それ以外の場合には、行の外部に格納されます。

    LOBはラージ・オブジェクトであるため、アプリケーションで小規模のLOBと大規模のLOBが混在する場合、インライン格納のみが関係します。LOB値は、約4,000バイトを超えると行の外に自動的に移動されます。

7.2 OCCIアプリケーションでのLOBの作成

アプリケーションでLOBを使用するには、次の手順を実行します。

  • データベースで新規LOBロケータを初期化します。

  • LOBに値を割り当てます。BFILEの場合、有効な外部ファイルへの参照を割り当てます。

  • LOBにアクセスして操作するには、アプリケーションでLOBを使用するためのメソッドを実装したOCCIクラスを参照してください。すべての詳細は、「OCCIアプリケーション・プログラミング・インタフェース」に記載されています。

  • 書込み、コピー、切捨ておよび類似した操作によって内部LOB列または属性を変更する場合は、必ずターゲットLOBが含まれる行をロックする必要があります。SELECT...FOR UPDATE文を使用して、LOBロケータを選択します。

  • LOB書込みコマンドを成功させるには、トランザクションをオープンしておく必要があります。したがって、トランザクションをコミットする前にデータを書き込む必要があります(COMMITによってトランザクションがクローズするため)。オープンしておかないと、SELECT...FOR UPDATE文を再発行して行を再ロックする必要があります。OCCIの各LOBクラス実装には、open()メソッドおよびclose()メソッドがあります。LOBがオープンされているかどうかをチェックするには、クラスのisOpen()メソッドをコールします。

  • open()メソッド、close()メソッドおよびisOpen()メソッドは、一連のLOB操作の最初と最後をマークする際にも使用する必要があります。LOBが変更された場合は、常に拡張可能索引の更新がトリガーされます。これらの変更がopen() ... close()コード・ブロック内で行われた場合、個々のトリガーはclose()コール後まで無効化され、その後同時に発行されます。この実装によって、LOBがクローズされているときに、索引の更新などのメンテナンス操作を効率的に処理できます。ただし、これはopen() ... close()コード・ブロックの実行中は拡張可能索引が無効であることも意味します。

    内部LOBの場合、オープンの概念はLOBロケータではなくLOBに関連付けられている点に注意してください。LOBロケータには、LOBロケータが参照するLOBがオープンしているかどうかの情報は格納されていません。複数のLOBロケータが、オープンされている同一のLOBをポイントできます。ただし、外部LOBの場合、オープンは特定の外部LOBロケータに関連付けられています。したがって、異なる外部LOBロケータを使用して、同じBFILEに対し複数のopen()コールを実行できます。

7.3 LOBのオープンおよびクローズの制限

オープンLOB値をクローズする必要があるトランザクションの定義は、次のいずれかとなります。

  • SET TRANSACTIONおよびCOMMITの間

  • DATA MODIFYING DMLおよびCOMMITの間

  • SELECT...FOR UPDATEおよびCOMMITの間

  • 自律型トランザクション・ブロック内

LOBのオープンおよびクローズのメカニズムには、次の制限があります。

  • トランザクションをコミットする前に、アプリケーションでオープンしているすべてのLOBをクローズする必要があります。さもないとエラーが発生します。トランザクションがロールバックされた場合、オープンLOBはすべて変更とともに廃棄されるため、関連付けられたトリガーは起動されません。

  • オープン内部LOBの数には制限はありませんが、オープン・ファイルの数には制限があります。オープンしているロケータを別のロケータに割り当てた場合、新規LOBのオープンとみなされない点に注意してください。

  • ロケータが異なるかどうかにかかわらず、同じトランザクション内で同じ内部LOBを2回オープンまたはクローズするとエラーになります。

  • オープンしていないLOBをクローズするとエラーになります。

7.4 LOBの読取りおよび書込みについて

LOBの読取りおよび書込みには、非ストリームおよびストリームという一般的な2つのメソッドがあります。

7.4.1 LOBの読取り

例7-1は、非ストリーム・メソッドを使用して、NULL以外の内部LOBからデータを取得する方法を示しています。このメソッドでは、読取りオフセットおよび残りの読取り量を追跡し、これらの値をread()メソッドに渡す必要があります。

例7-2は類似しているもので、BFILEロケータがNULLでない場合に、非ストリーム読込みを使用して、BFILEからデータを読み込む方法を示しています。

例7-1および例7-2とは異なり、例7-3に示しているNULL以外のBLOBでのストリーム読取りでは、オフセットの追跡は必要ありません。

例7-1 非ストリームBLOBの読取り方法

ResultSet *rset=stmt->executeQuery("SELECT ad_composite FROM print_media
                                    WHERE product_id=6666");
while(rset->next())
{
   Blob blob=rset->getBlob(1);
   if(blob.isNull())
     cerr <<"Null Blob"<<endl;
   else
   {
      blob.open(OCCI_LOB_READONLY);
 
      const unsigned int BUFSIZE=100;
      char buffer[BUFSIZE];
      unsigned int readAmt=BUFSIZE;
      unsigned int offset=1;
 
      //reading readAmt bytes from offset 1
      blob.read(readAmt,buffer,BUFSIZE,offset);
 
      //process information in buffer
      ...
      blob.close();
   }
}
stmt->closeResultSet(rset);

例7-2 非ストリームBFILEの読取り方法

ResultSet *rset=stmt->executeQuery("SELECT ad_graphic FROM print_media
                                    WHERE product_id=6666");
while(rset->next())
{
   Bfile file=rset->getBfile(1);
   if(bfile.isNull())
      cerr <<"Null Bfile"<<endl;
   else
   {
      //display the directory alias and the file name of the BFILE
      cout <<"File Name:"<<bfile.getFileName()<<endl;
      cout <<"Directory Alias:"<<bfile.getDirAlias()<<endl;
 
      if(bfile.fileExists())
      {
         unsigned int length=bfile.length();
         char *buffer=new char[length];
         bfile.read(length, buffer, length, 1);
         //read all the contents of the BFILE into buffer, then process
         ...
         delete[] buffer;
      }
      else
         cerr <<"File does not exist"<<endl;
   }
}
stmt->closeResultSet(rset);

例7-3 ストリームBLOBの読取り方法

ResultSet *rset=stmt->executeQuery("SELECT ad_composite FROM print_media
                                    WHERE product_id=6666");
while(rset->next())
{
   Blob blob=rset->getBlob(1);
   if(blob.isNull())
      cerr <<"Null Blob"<<endl;
   else
   {
      Stream *instream=blob.getStream(1,0);
      //reading from offset 1 to the end of the BLOB
 
      unsigned int size=blob.getChunkSize();
      char *buffer=new char[size];
 
      while((unsigned int length=instream->readBuffer(buffer,size))!=-1)
      {
         //process "length" bytes read into buffer
         ...
      }
      delete[] buffer;
      blob.closeStream(instream);
   }
}
stmt->closeResultSet(rset);

7.4.2 LOBの書込み

例7-4は、非ストリーム書込みを使用して内部の非NULL LOBにデータを書き込む方法を示しています。writeChunk()メソッドはopen()メソッドとclose()メソッドで囲まれています。このメソッドは現在オープンされているLOBで動作し、チャンクの読取りのたびにトリガーが起動しないことを保証します。write()メソッドはwriteChunk()メソッドに使用できます。ただし、write()メソッドは暗黙的にLOBのオープンとクローズを行います。

例7-5は、ストリーム書込みを使用して移入された内部LOBにデータを書き込む方法を示しています。

例7-4 非ストリームBLOBの書込み方法

ResultSet *rset=stmt->executeQuery("SELECT ad_composite FROM print_media
                                    WHERE product_id=6666 FOR UPDATE");
while(rset->next())
{
   Blob blob=rset->getBlob(1);
   if(blob.isNull())
      cerr <<"Null Blob"<<endl;
   else
   {
      blob.open(OCCI_LOB_READWRITE);
 
      const unsigned int BUFSIZE=100;
      char buffer[BUFSIZE];
      unsigned int writeAmt=BUFSIZE;
      unsigned int offset=1;
 
      //writing writeAmt bytes from offset 1
      //contents of buffer are replaced after each writeChunk(),
      //typically with an fread()
      while(<fread "BUFSIZE" bytes into buffer succeeds>)
      {
         blob.writeChunk(writeAmt, buffer, BUFSIZE, offset);
         offset += writeAmt;
      }
      blob.writeChunk(<remaining amt>, buffer, BUFSIZE, offset);
 
      blob.close();
   }
}
stmt->closeResultSet(rset);
conn->commit();

例7-5 ストリームBLOBの書込み方法

ResultSet *rset=stmt->executeQuery("SELECT ad_composite FROM print_media
                                    WHERE product_id=6666 FOR UPDATE");
while(rset->next())
{
   Blob blob=rset->getBlob(1);
   if(blob.isNull())
      cerr <<"Null Blob"<<endl;
   else
   {
      char buffer[BUFSIZE];
      Stream *outstream=blob.getStream(1,0);
 
      //writing from buffer beginning at offset 1 until 
      //a writeLastBuffer() method is issued.
      //contents of buffer are replaced after each writeBuffer(),
      //typically with an fread()
      while(<fread "BUFSIZE" bytes into buffer succeeds>)
         ostream->writeBuffer(buffer,BUFSIZE);
      ostream->writeLastBuffer(buffer,<remaining amt>);
      blob.closeStream(outstream);
   }
}
stmt->closeResultSet(rset);
conn->commit();

7.4.3 LOBの読込みおよび書込みのパフォーマンス向上について

内部LOBの読取りおよび書込みは、いずれかのgetChunkSize()メソッドを使用してパフォーマンスを改善できます。

この項には次のトピックが含まれます: 「getChunkSize()メソッドの使用について」

7.4.3.1 getChunkSize()メソッドの使用について

getChunkSize()メソッドでは、使用可能なチャンク・サイズについて、BLOBではバイト数で戻し、CLOBおよびNCLOBでは文字数で戻します。使用可能なチャンク・サイズの倍数で読取りまたは書込みが開始されており、要求サイズも使用可能なチャンク・サイズの倍数である場合に、パフォーマンスが向上します。LOBが含まれる表を作成する場合、LOB列についてチャンク・サイズを指定できます。

getChunkSize()メソッドをコールすると、LOBの使用可能なチャンク・サイズが戻されます。アプリケーションは、同じチャンクを操作する複数のLOB書込みコールを発行せずに、チャンク全体の書込みが可能になるまで一連の書込み操作をバッチ処理できます。

LOBの最後まで読み取るには、データ量を4GBに指定したread()メソッドを使用します。これにより、データ量を4GBに指定したread()メソッドがLOBの最後に到達するまで読取りを実行するため、getLength()メソッドの最初のコールに関係するラウンドトリップが回避されます。

可変幅文字が格納されたLOBの場合、GetChunkSize()メソッドによって、LOBチャンクに適合するUnicode文字数が戻されます。

7.4.4 LOBの更新

データベースでLOBの値を更新するには、新しい値をLOBに割り当て、データベースでSQL UPDATEコマンドを実行し、トランザクションをコミットする必要があります。例7-6は、既存のCLOBの更新方法(この場合は空白に設定)を示し、例7-7BFILEの更新方法を示しています。

例7-6 CLOB値の更新

Clob clob(conn);
clob.setEmpty();
stmt->setSQL("UPDATE print_media SET ad_composite = :1
              WHERE product_id=6666");
stmt->setClob(1, clob);
stmt->executeUpdate();
conn->commit();

例7-7 BFILE値の更新

Bfile bfile(conn);
bfile.setName("MEDIA_DIR", "img1.jpg");
stmt->setSQL("UPDATE print_media SET ad_graphic = :1 
              WHERE product_id=6666");
stmt->setBfile(1, bfile);
stmt->executeUpdate();
conn->commit();

7.4.5 複数のLOBの読取りおよび書込みについて

Oracle Database 10gリリース2では、複数のLOB (BfileBlobClobおよびNClobなど)の読取りおよび書込み時のアプリケーションのパフォーマンスを向上する新しいインタフェースがOCCIに追加されました。

これらのインタフェースには、一度に1つのLOB読取りと書込みを行う標準の方法に比べ、いくつかの利点があります。

  • OCCIで複数のLOBの読取りと書込みを1回のサーバー・ラウンドドリップで行うことで、アプリケーションとバック・エンドの間のI/O時間が短縮され、パフォーマンスが向上します。

  • 新しいAPIでは、以前の限度である4GBより大きなLOBをサポートしています。新しいインタフェースでは、容量、オフセット、バッファおよび長さパラメータについてoraub8データ型に対応します。これらのパラメータは、コンパイラとオペレーティング・システムに応じた64ビットのネイティブ・データ型にマッピングされています。

  • Clobに関するメソッドでは、ユーザーは読み取るまたは書き込むデータ量を文字数またはバイト数で指定できます。

この機能のための新しいAPIは、「OCCI Application Program Interface (API)」「Connectionクラス」の項で説明します。これには、readVectorOfBfiles()readVectorOfBlobs()readVectorOfClobs() (一般的なキャラクタ・セット、特にUTF16をサポートするためのオーバーロード)、writeVectorOfBlobs()およびwriteVectorOfClobs() (一般的なキャラクタ・セット、特にUTF16をサポートするためのオーバーロード)があります。

この項には次のトピックが含まれます。「複数のLOBの読取りと書込みのためのインタフェースの使用方法について」

7.4.5.1 複数のLOBの読取りと書込みのためのインタフェースの使用方法について

readVectorOfxxx()インタフェースとwriteVectorOfxxx()インタフェースはそれぞれ、次のパラメータを使用します。

  • conn: Connectionクラス・オブジェクト

  • vec: LOBオブジェクト(BfileBlobClobまたはNClob)のベクター

  • byteAmts: 読取りまたは書込みのバイト数の配列

  • charAmts: 読取りまたは書込みの文字数の配列(ClobNClobのみに適用可能)

  • offsets: オフセットの配列(BfileBlobはバイト数、ClobNClobは文字数)

  • buffers: バッファ・ポイントの配列

  • bufferLengths: バッファの長さの配列

ベクター内のLOBの読取りまたは書込みのいずれかでエラーが発生すると、操作全体がキャンセルされます。byteAmtsまたはcharAmtsパラメータをチェックし、読み書きされる実際のバイト数または文字数を指定する必要があります。

7.5 LOB属性を含むオブジェクトの使用

OCCIアプリケーションは、演算子new()を使用して、LOB属性を含む永続オブジェクトを作成できます。デフォルトでは、LOB属性はすべてデフォルト・コンストラクタを使用して構成され、NULLに初期化されます。

例7-8は、内部LOB属性を含む永続オブジェクトの作成および使用方法を示しています。例7-9は、外部LOB属性を含む永続オブジェクトの作成および使用方法を示しています。

例7-8 BLOB属性を含む永続オブジェクトの使用方法

  1. BLOB属性を含む永続オブジェクトを作成します。

    Person *p=new(conn,"PERSON_TAB")Person();
    p->imgBlob = Blob(conn);
    
  2. Blobオブジェクトを空に初期化します。

    p->imgBlob.setEmpty();
    

    または、既存の値に設定します。

  3. Blobオブジェクトに使用済のマークを付けます。

    p->markModified();
    
  4. オブジェクトをフラッシュします。

    p->flush();
    
  5. オブジェクトへのREFを取得した後にこのオブジェクトを再確保し、これによって、データベースからリフレッシュしたオブジェクト・バージョンを取得し、初期化されたLOBを取得します。

    Ref<Person> r = p->getRef();
    delete p;
    p = r.ptr();
    
  6. データを書き込みます。

    p->imgBlob.write( ... );

例7-9 BFILE属性を含む永続オブジェクトの使用方法

  1. BFILE属性を含む永続オブジェクトを作成します。

    Person *p=new(conn,"PERSON_TAB")Person();
    p->imgBFile = BFile(conn);
    
  2. Bfileオブジェクトを初期化します。

    p->setName(directory_alias, file_name);
    
  3. Bfileオブジェクトに使用済のマークを付けます。

    p->markModified();
    
  4. オブジェクトをフラッシュします。

    p->flush();
    
  5. データを読み取ります。

    p->imgBfile.read( ... );

7.6 SecureFilesの使用について

Oracle Database 11gリリース1で導入されたSecureFiles LOBでは、LOB圧縮、暗号化および重複除外に関する強力な新機能が追加されています。

7.6.1 SecureFileの圧縮の使用について

SecureFilesの圧縮では、LOBデータのサーバー側の圧縮が可能であり、これはアプリケーションに対して透過的に行われます。SecureFilesの圧縮を使用すると、SecureFiles LOBデータの読取りおよび更新のパフォーマンスに対する影響を最小限に抑えて、記憶領域を節減できます。

7.6.2 SecureFilesの暗号化の使用について

SecureFilesでは、LOBデータの新規暗号化機能が導入されています。また、透過的データ暗号化が拡張され、暗号化されたSecureFiles LOBへの効率的かつランダムな読取りおよび書込みアクセスが有効化されています。

7.6.3 SecureFilesの重複除外の使用について

SecureFilesの重複除外により、Oracle Databaseでは重複したLOBデータを自動的に検出し、SecureFiles LOBの単一コピーを格納して領域を節約できます。

7.6.4 SecureFilesの圧縮、暗号化および重複除外の結合について

圧縮、暗号化および重複除外を任意に組み合せることができます。Oracle Databaseでは、次の規則に従ってこれらの機能が適用されます。

  • 重複除外検出(有効化されている場合)は、圧縮および暗号化の前に実行されます。これにより、重複するSecureFiles LOBに対する不要およびコストが高い可能性のある圧縮および暗号化操作が防止されます。

  • 圧縮は、最大限の圧縮率を実現するために、暗号化の前に実行されます。

7.6.5 SecureFilesのLOBの型および定数

次のSecureFiles LOB型は、圧縮、暗号化および重複除外の柔軟性を高めます。表7-1ではLobOptionTypeのオプション、表7-2ではLobOptionValueのオプションを示しています。

表7-1 LobOptionType型の値

説明
OCCI_LOB_OPT_COMPRESS

圧縮オプション型

OCCI_LOB_OPT_ENCRYPT

暗号化オプション型

OCCI_LOB_OPT_DEDUPLICATE

重複除外オプション型

表7-2 LobOptionValue型の値

説明
OCCI_LOB_COMPRESS_OFF

SecureFiles圧縮をオフにします。

OCCI_LOB_COMPRESS_ON

SecureFiles圧縮をオンにします。

OCCI_LOB_ENCRYPT_OFF

SecureFiles暗号化をオフにします。

OCCI_LOB_ENCRYPT_ON

SecureFiles暗号化をオンにします。

OCCI_LOB_DEDUPLICATE_OFF

SecureFiles重複除外をオフにします。

OCCI_LOB_DEDUPLICATE_ON

LOB重複除外をオフにします。