存储二进制数据

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 驱动程序处理二进制数据的示例。

例如,假设您有一个包含图像文件名的表,并且您还想将图像存储在 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();