Redrock Postgres 文档
主页 切换暗/亮/自动模式 切换暗/亮/自动模式 切换暗/亮/自动模式 返回首页

存储二进制数据

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();