31.7. バイナリデータの格納

PostgreSQL は、バイナリデータの格納方法として 2 つの別々の方法を用意しています。 バイナリデータはbyteaデータ型を使用してテーブル内に格納することも、ラージオブジェクト機能を使用して特殊な形式で別のテーブルに格納し、テーブル内に格納される oid 型の値で参照することもできます。

どちらの方法が適切かを決定するためには、それぞれの方法における制限を理解しなければなりません。 bytea データ型は非常に巨大なバイナリデータを格納するのには適していません。 bytea 型の列は 1 ギガバイトまでのバイナリデータを保存できますが、そういった巨大な値を処理する時、非常に多量のメモリが必要になります。 ラージオブジェクトによるバイナリデータの格納方法は、非常に巨大な値を格納するのに適していますが、こちらの場合も制限があります。 特に、ラージオブジェクトへの参照を含む行を削除しても、ラージオブジェクトは削除されません。 ラージオブジェクトを削除するためには、別途操作を行わなければなりません。 ラージオブジェクトにはまた、セキュリティに関する問題がいくつかあります。 データベースに接続した全てのユーザは、ラージオブジェクトを含む行を参照、変更する権限がなくても、全てのラージオブジェクトを参照、変更することができるからです。

バージョン7.2は byteaデータ型をサポートするJDBCドライバの最初のリリースです。 7.2におけるこの機能の導入により、以前のリリースの動作と違いが発生しています。 7.2 における getBytes()setBytes()getBinaryStream() および setBinaryStream() メソッドは bytea データ型に対して操作を行います。 7.1以前でのこれらのメソッドは、ラージオブジェクトに関連したoidデータ型に対して操作を行います。 Connectioncompatibleプロパティを7.1という値に設定することで、ドライバを古い 7.1 の動作に戻すことができます。

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

ラージオブジェクト機能を使用するためには、PostgreSQL JDBC ドライバで提供される LargeObjectクラスを使用、または、getBLOB()setBLOB() メソッドを使用して下さい。

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

注意: 将来の JDBC ドライバでは、getBLOB()setBLOB() メソッドはラージオブジェクトと関係せず、bytea データ型に対する操作になります。 ですから、ラージオブジェクトを使用する予定ならば、LargeObject API を使用することを推奨します。

例31-8で、PostgreSQL JDBC ドライバを使用したバイナリデータの処理方法の例を数例示します。

例 31-8. 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, file.length());
ps.executeUpdate();
ps.close();
fis.close();

ここで、setBinaryStream() はストリームから bytea 型の列へバイト集合を転送します。 また、画像の内容が既に byte[] 内にある場合、setBytes() メソッドを使用してもこれを行うことができます。

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

PreparedStatement ps = con.prepareStatement("SELECT img FROM images WHERE imgname = ?");
ps.setString(1, "myimage.gif");
ResultSet rs = ps.executeQuery();
if (rs != null) {
    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();

// 新規にラージオブジェクトを作成します。
int oid = lobj.create(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.setInt(2, oid);
ps.executeUpdate();
ps.close();
fis.close();

ラージオブジェクトから画像を取り出すには、以下のようにします。

// 全ての LargeObject API の呼び出しはトランザクション内部でなければなりません。
conn.setAutoCommit(false);

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

PreparedStatement ps = con.prepareStatement("SELECT imgoid FROM imageslo WHERE imgname = ?");
ps.setString(1, "myimage.gif");
ResultSet rs = ps.executeQuery();
if (rs != null) {
    while (rs.next()) {
	// 読み取り用にラージオブジェクトを開く
        int oid = rs.getInt(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();