存储二进制数据
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)
来启动事务块。
例 7.1 “在 JDBC 中处理二进制数据” 包含有关如何使用 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
需要的长度参数必须正确。无法指示流长度未知。如果您处于这种情况,则必须自行将流读入临时存储并确定长度。现在,使用正确的长度,您可以将数据从临时存储发送到驱动程序。
检索图像更加容易。我们在这里使用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);
// 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 = conn.unwrap(org.postgresql.PGConnection.class).getLargeObjectAPI();
// Create a new large object
long oid = lobj.createLO(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.setLong(2, oid);
ps.executeUpdate();
ps.close();
fis.close();
// Finally, commit the transaction.
conn.commit();
从大对象中检索图像:
// All LargeObject API calls must be within a transaction block
conn.setAutoCommit(false);
// Get the Large Object Manager to perform operations with
LargeObjectManager lobj = conn.unwrap(org.postgresql.PGConnection.class).getLargeObjectAPI();
PreparedStatement ps = conn.prepareStatement("SELECT imgoid FROM imageslo WHERE imgname = ?");
ps.setString(1, "myimage.gif");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
// Open the large object for reading
long oid = rs.getLong(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();
// Finally, commit the transaction.
conn.commit();