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

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

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

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 API を使用、または、getBLOB()setBLOB() メソッドを使用して下さい。

Important: PostgreSQL では、SQL トランザクション内でラージオブジェクトをアクセスしなければなりません。 入力パラメータとして false を指定した setAutoCommit() メソッドを使用して、トランザクションを開くことができます。

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

Example 5-4. バイナリデータの例

例として、画像のファイル名を持つテーブルがあり、また、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();