31.7. 存储二进制数据 #
PostgreSQL 提供两种不同的存储二进制数据的方式。二进制数据可以使用
bytea 数据类型存储在一个表中,或者使用大对象特性——它把二进制数据以一种特殊格式存储在一个单独的表中,并通过在你的表中存储一个
oid 类型的值来引用该表。
为了确定哪种方法合适,你需要理解每种方法的限制。bytea 数据类型并不适合存储非常大量的二进制数据。虽然
bytea 类型的列最多可以容纳 1 GB 的二进制数据,但处理这样大的值需要大量的内存。用大对象方法存储二进制数据更适合存储非常大的值,但它也有自己的限制。具体来说,删除一个包含大对象引用的行并不会删除该大对象。删除大对象是一个需要单独执行的操作。大对象还有一些安全性问题,因为任何连接到数据库的人都可以查看和/或修改任何大对象,即使他们没有查看/更新包含该大对象引用的行的权限。
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 类,或者使用
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);
// use the data in some way here
}
rs.close();
}
ps.close();
这里二进制数据是作为
byte[] 检索的。你也可以改用一个
InputStream 对象。
又或者,你要存储一个非常大的文件,并想使用
LargeObject API 来存储该文件:
CREATE TABLE imageslo (imgname text, imgoid oid);
要插入一幅图像,你可以使用:
// All LargeObject API calls must be within a transaction block
conn.setAutoCommit(false);
// Get the Large Object Manager to perform operations with
LargeObjectManager lobj = ((org.postgresql.PGConnection)conn).getLargeObjectAPI();
// Create a new large object
int oid = lobj.create(LargeObjectManager.READ | LargeObjectManager.WRITE);
// Open the large object for writing
LargeObject obj = lobj.open(oid, LargeObjectManager.WRITE);
// Now open the file
File file = new File("myimage.gif");
FileInputStream fis = new FileInputStream(file);
// Copy the data from the file to the large object
byte buf[] = new byte[2048];
int s, tl = 0;
while ((s = fis.read(buf, 0, 2048)) > 0) {
obj.write(buf, 0, s);
tl += s;
}
// Close the large object
obj.close();
// Now insert the row into imageslo
PreparedStatement ps = conn.prepareStatement("INSERT INTO imageslo VALUES (?, ?)");
ps.setString(1, file.getName());
ps.setInt(2, oid);
ps.executeUpdate();
ps.close();
fis.close();
从大对象中检索该图像:
// All LargeObject API calls must be within a transaction block
conn.setAutoCommit(false);
// Get the Large Object Manager to perform operations with
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()) {
// 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();