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 データ型に対して操作を行います。 Connection の compatible プロパティを 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 8-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); // use the stream in some way here } rs.close(); } ps.close();
ここで、バイナリデータは byte[] として取り出されました。InputStream を使用しても可能です。
他の方法として、非常に巨大なファイルを格納するために、 LargeObject API を使用してファイルに格納することを考えます。
CREATE TABLE imagesLO (imgname text, imgOID OID);
画像を挿入するには、以下のようにします。
// 全ての LargeObject API の呼び出しはトランザクション内部でなければなりません。 conn.setAutoCommit(false); // 操作を実行するラージオブジェクトマネージャを入手します。 LargeObjectManager lobj = ((org.postgresql.Connection)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.Connection)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()) { //open the large object for reading int oid = rs.getInt(1); LargeObject obj = lobj.open(oid, LargeObjectManager.READ); //read the data byte buf[] = new byte[obj.size()]; obj.read(buf, 0, obj.size()); //do something with the data read here // Close the object obj.close(); } rs.close(); } ps.close();