第7章 バイナリデータの保存

PostgreSQL™には、バイナリデータを保存する2つの異なる方法があります。 バイナリデータはbyteaデータ型を使用して、または、ラージオブジェクト機能を使用して、テーブル内に格納することができます。 ラージオブジェクト機能では、バイナリデータを特別な書式で別のテーブルにバイナリデータを格納し、oid型の値をテーブルに保管することで参照します。

どちらの方法が適切かを決めるためには、各方法の制限を理解する必要があります。 byteaデータ型は、非常に大きなバイナリデータを格納するのには適していません。 bytea型の列には1GBまでのバイナリデータを維持できますが、こうした巨大な値を処理するために必要なメモリが大きくなります。 バイナリデータ格納用のラージオブジェクト方法は、非常に大きな値を格納するのに適していますが、別の制限があります。 特に、ラージオブジェクトへの参照を持つ行を削除しても、ラージオブジェクトは削除されません。 ラージオブジェクトの削除を行うためには別の操作が必要です。 また、ラージオブジェクトには、データベースに接続できれば、たとえラージオブジェクトへの参照を持つ行を参照/変更する権限を持たなくても、誰でもラージオブジェクトを参照、変更できますので、セキュリティ上の問題があります。

バージョン7.2が、byteaデータ型をサポートするJDBCドライバの最初のリリースです。 7.2におけるこの機能の導入により、以前のリリースと比べて動作の違いが出てきました。 7.2から、getBytes()setBytes()getBinaryStream()setBinaryStream()メソッドはbyteaデータ型に対して処理します。 7.1以前では、これらのメソッドはラージオブジェクトに関連したoidデータ型に対して処理していました。 Connectionオブジェクトのcompatibleプロパティを7.1という値に設定することで、ドライバを古い7.1の動作に戻すことができます。 Connectionのプロパティに関する詳細は接続パラメータ項を参照してください。

byteaデータ型を使用するには、単にgetBytes()setBytes()getBinaryStream()setBinaryStream()メソッドを使用してください。

ラージオブジェクト機能を使用するには、PostgreSQL™ JDBCドライバで提供されるLargeObjectクラス、あるいは、getBLOB()およびsetBLOB()メソッドを使用してください。

重要項目

SQLトランザクションの内部でラージオブジェクトにアクセスしなければなりません。 setAutoCommit(false)メソッドを呼び出してトランザクションブロックを開始することができます。

例 7.1に、PostgreSQL™ JDBCドライバを使用したバイナリデータを処理方法に関する例が幾つかあります。

例 7.1. JDBCによるバイナリデータ処理

例として、画像ファイル名を持つテーブルがあり、画像をbytea型の列として格納したいものと仮定します。

CREATE TABLE images (imgname text, img bytea);

画像を挿入するには、以下のように使います。

File file = new File("myimage.gif");
FileInputStream fis = new FileInputStream(file);
PreparedStatement ps = conn.prepareStatement("INSERT INTO images VALUES (?, ?)");
ps.setString(1, file.getName());
ps.setBinaryStream(2, fis, (int)file.length());
ps.executeUpdate();
ps.close();
fis.close();
ここでは、setBinaryStream()は、ストリームからバイト列の集合をbytea型の列に転送します。 これはまた、画像の内容がすでにbyte[]内にあれば、setBytes()メソッドを使用して行うこともできます。

注意

setBinaryStreamへのlengthパラメータは正確でなければなりません。 ストリーム長が未知であることを通知する方法はありません。 こうした状況の場合、一時的な格納領域にストリームを自身で読み込み、その長さを確認しなければなりません。 そして、この正確な長さを使って、一時的な格納領域からドライバにデータを送信することができます。

画像の取り出しはいっそう簡単です。 (ここではPreparedStatementを使用していますが、Statementクラスも同様に使用することができます。)

PreparedStatement ps = conn.prepareStatement("SELECT img FROM images WHERE imgname = ?");
ps.setString(1, "myimage.gif");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
    byte[] imgBytes = rs.getBytes(1);

    // ここでデータを何らかの形で使用します。
}
rs.close();
ps.close();

これにより、バイナリデータがbyte[]として取り出されました。 代わりにInputStreamオブジェクトを使用することもできます。

この他に、非常に巨大なファイルを格納する場合には LargeObject APIを使用してそのファイルを格納したいでしょう。

CREATE TABLE imageslo (imgname text, imgoid oid);

画像を挿入するには以下のように使用します。


// すべてのLargeObject APIはトランザクションブロック内で呼び出さなければなりません。
conn.setAutoCommit(false);


// 操作を行うためのラージオブジェクトマネージャを入手します。
LargeObjectManager lobj = ((org.postgresql.PGConnection)conn).getLargeObjectAPI();


// 新しいラージオブジェクトを作成します。
long oid = lobj.createLO(LargeObjectManager.READ | LargeObjectManager.WRITE);


// 書き込み用にラージオブジェクトを開きます。
LargeObject obj = lobj.open(oid, LargeObjectManager.WRITE);


// ここでファイルを開きます。
File file = new File("myimage.gif");
FileInputStream fis = new FileInputStream(file);


// ファイルからラージオブジェクトにデータをコピーします。
byte buf[] = new byte[2048];
int s, tl = 0;
while ((s = fis.read(buf, 0, 2048)) > 0) {
    obj.write(buf, 0, s);
    tl += s;
}


// ラージオブジェクトを閉じます。
obj.close();


// ここでimagesloに行を挿入します。
PreparedStatement ps = conn.prepareStatement("INSERT INTO imageslo VALUES (?, ?)");
ps.setString(1, file.getName());
ps.setLong(2, oid);
ps.executeUpdate();
ps.close();
fis.close();


// 最後にトランザクションをコミットします。
conn.commit();

ラージオブジェクトから画像を取り出します。


// すべてのLargeObject APIはトランザクションブロック内で呼び出さなければなりません。
conn.setAutoCommit(false);


// 操作を行うためのラージオブジェクトマネージャを入手します。
LargeObjectManager lobj = ((org.postgresql.PGConnection)conn).getLargeObjectAPI();

PreparedStatement ps = conn.prepareStatement("SELECT imgoid FROM imageslo WHERE imgname = ?");
ps.setString(1, "myimage.gif");
ResultSet rs = ps.executeQuery();
while (rs.next()) {

    // 読み込み用にラージオブジェクトを開きます。
    long oid = rs.getLong(1);
    LargeObject obj = lobj.open(oid, LargeObjectManager.READ);


    // データを読み込みます。
    byte buf[] = new byte[obj.size()];
    obj.read(buf, 0, obj.size());

    // ここで読み込んだデータを使用して何らかの処理を行います。


    // オブジェクトを閉じます。
    obj.close();
}
rs.close();
ps.close();


// 最後にトランザクションをコミットします。
conn.commit();