存储二进制数据
PostgreSQL® 提供两种不同的方式来存储二进制数据。二进制数据可以使用 BYTEA 数据类型存储在表中,或者使用 Large Object 功能,该功能将二进制数据存储在单独的表中,并以特殊格式存储,并在您的表中存储 OID 类型的值来引用该表。
为了确定哪种方法适合您,您需要了解每种方法的局限性。BYTEA 数据类型不适合存储大量二进制数据。虽然 BYTEA 类型的列可以容纳高达 1 GB 的二进制数据,但处理如此大的值需要大量的内存。存储二进制数据的 Large Object 方法更适合存储非常大的值,但它也有自己的局限性。具体来说,删除包含 Large Object 引用的一行不会删除 Large Object。删除 Large Object 是一个单独的操作,需要单独执行。Large Object 还存在一些安全问题,因为任何连接到数据库的人都可以查看和/或修改任何 Large Object,即使他们没有权限查看/更新包含 Large Object 引用的一行。
版本 7.2 是第一个支持 BYTEA 数据类型的 JDBC 驱动程序版本。在 7.2 中引入此功能与之前的版本相比引入了行为上的变化。从 7.2 开始,getBytes()
、setBytes()
、getBinaryStream()
和 setBinaryStream()
方法在 BYTEA 数据类型上操作。在 7.1 及更早版本中,这些方法在与 Large Object 关联的 OID 数据类型上操作。可以通过将 Connection
对象上的属性 compatible
设置为值 7.1
来将驱动程序恢复到旧的 7.1 行为。有关连接属性的更多详细信息,请参阅名为 连接参数 的部分。
要使用 BYTEA 数据类型,您只需使用 getBytes()
、setBytes()
、getBinaryStream()
或 setBinaryStream()
方法。
要使用 Large Object 功能,您可以使用 PostgreSQL® JDBC 驱动程序提供的 LargeObject
类,或者使用 getBLOB()
和 setBLOB()
方法。
重要
您必须在 SQL 事务块内访问 Large Object。您可以通过调用
setAutoCommit(false)
来启动事务块。
示例 7.1,“在 JDBC 中处理二进制数据” 包含一些关于如何使用 PostgreSQL® JDBC 驱动程序处理二进制数据的示例。
示例 7.1。在 JDBC 中处理二进制数据
例如,假设您有一个包含图像文件名的表,并且您还想将图像存储在 BYTEA 列中
CREATE TABLE images (imgname text, img bytea);
要插入图像,您将使用
File file = new File("myimage.gif");
try (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();
}
这里,setBinaryStream()
将从流中传输一组字节到 BYTEA 类型的列中。如果图像内容已经存在于 byte[]
中,也可以使用 setBytes()
方法。
注意
setBinaryStream
的长度参数必须正确。没有办法指示流是未知长度的。如果您遇到这种情况,您必须自己读取流到临时存储中并确定长度。现在有了正确的长度,您可以将数据从临时存储发送到驱动程序。
检索图像更简单。(我们在这里使用 PreparedStatement
,但 Statement
类也可以使用。
try (PreparedStatement ps = conn.prepareStatement("SELECT img FROM images WHERE imgname = ?"); ) {
ps.setString(1,"myimage.gif");
try (ResultSet rs = ps.executeQuery();) {
while(rs.next()){
byte[] imgBytes = rs.getBytes(1);
// use the data in some way here
}
}
}
这里二进制数据被检索为 byte[]
。您也可以使用 InputStream
对象。
或者,您可能正在存储一个非常大的文件,并且想要使用 LargeObject
API 来存储文件
CREATE TABLE imageslo (imgname text, imgoid oid);
要插入图像,您将使用
// All LargeObject API calls must be within a transaction block
conn.setAutoCommit(false);
File inputFile = new File("myimage.gif");
// Now insert the row into imageslo
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO imageslo VALUES (?, ?)");
FileInputStream fis = new FileInputStream(inputFile); ) {
ps.setString(1,file.getName());
ps.setBlob(2, fis, inputFile.length());
ps.executeUpdate();
}
// Finally, commit the transaction.
conn.commit();
从大型对象中检索图像
// All LargeObject API calls must be within a transaction block
conn.setAutoCommit(false);
try (PreparedStatement ps = conn.prepareStatement("SELECT imgoid FROM imageslo WHERE imgname = ?"); ) {
ps.setString(1, "myimage.gif");
try (ResultSet rs = ps.executeQuery(); ) {
while (rs.next()) {
// Read all data at once
byte[] contents = rs.getBytes(1);
// Read all data as InputStream
Blob blob = rs.getBlob(1);
try (InputStream is = blob.getBinaryStream(); ) {
// Process the input stream. The input stream is buffered, so you don't need to
// wrap it in a BufferedInputStream
} finally {
blob.free();
}
}
}
}
// Finally, commit the transaction.
conn.commit();
更新大型对象的内容
// All LargeObject API calls must be within a transaction block
conn.setAutoCommit(false);
try (PreparedStatement ps = conn.prepareStatement("SELECT imgoid FROM imageslo WHERE imgname = ?"); ) {
ps.setString(1, "myimage.gif");
try (ResultSet rs = ps.executeQuery(); ) {
while (rs.next()) {
Blob blob = rs.getBlob(1);
try (OutputStream os = blob.setBinaryStream(0); ) {
// Write data to the output stream. The output stream is buffered, so you don't need to
// wrap it in a BufferedOutputStream
} finally {
blob.free();
}
}
}
}
// Finally, commit the transaction.
conn.commit();