↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

已结束支持的版本: 7.4 / 7.3 / 7.2
历史版本。 PostgreSQL 7.3 已结束支持。 请参阅 当前版本手册.

5.6. 存储二进制数据 #

PostgreSQL 提供两种不同的存储二进制数据的方式。二进制数据可以使用 PostgreSQL 的二进制数据类型 bytea 存储在一个表中,或者使用大对象特性——它把二进制数据以一种特殊格式存储在一个单独的表中,并通过在你的表中存储一个 OID 类型的值来引用该表。

为了确定哪种方法合适,你需要理解每种方法的限制。bytea 数据类型并不适合存储非常大量的二进制数据。虽然 bytea 类型的列最多可以容纳 1 GB 的二进制数据,但处理这样大的值需要大量的内存(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() 方法。

重要

对于 PostgreSQL,你必须在一个 SQL 事务内访问大对象。你可以通过以 false 作为输入参数调用 setAutoCommit() 方法来打开一个事务。

注意

在 JDBC 驱动的一个未来版本中,getBLOB() 和 setBLOB() 方法可能不再与大对象交互,而是改为处理 bytea 数据类型。因此,如果你打算使用大对象,建议使用 LargeObject API。

例 5.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);

要插入一幅图像,你可以使用:

// All LargeObject API calls must be within a transaction
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 write
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
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();


报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.