如何从 Oracle 数据库中读取块中的大型 blob 以避免 Java 堆空间 OutOfMemoryError?

How to Read a Large Blob from an Oracle Database in Chunks to Avoid Java Heap Space OutOfMemoryError?

提问人:Calu River 提问时间:11/7/2023 最后编辑:Calu River 更新时间:11/9/2023 访问量:46

问:

我正在开发一个 Java Spring 服务,该服务从 Oracle 数据库中读取 BLOB 类型的字段。此 BLOB 字段包含一个 zip 文件,我的服务应该在响应正文中将此文件返回给客户端。

我实现了以下代码:

public void datasetDownload(String id, HttpServletResponse response) throws SQLException, IOException {
    try (Connection connection = dataSource.getConnection()) {
        String sql = "SELECT CONTENT FROM MY_TABLE_NAME WHERE ID = ?";
        try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            preparedStatement.setString(1, id);
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                if (resultSet.next()) {
                    Blob blob = resultSet.getBlob("CONTENT");

                    try (InputStream inputStream = blob.getBinaryStream();
                         OutputStream outputStream = response.getOutputStream()) {
                        byte[] buffer = new byte[1024];

                        int bytesRead;
                        while ((bytesRead = inputStream.read(buffer)) != -1) {
                            outputStream.write(buffer, 0, bytesRead);
                        }
                    }

                    deleteRecord(id, connection);
                }
            }
        }
    }
}

但是,当我调用此服务时,我遇到以下异常:

java.lang.OutOfMemoryError:Java 堆空间

我怀疑该问题是由于将整个 blob 从 Oracle 数据库读取到内存中引起的。为了解决这个问题,我想我需要以较小的块读取 blob 以避免耗尽 Java 堆空间,但我不知道该怎么做。

谢谢你的帮助!

编辑:我尝试添加一些日志:

    public void datasetDownload(String id, HttpServletResponse response) throws SQLException, IOException {
    try (Connection connection = dataSource.getConnection()) {
        String sql = "SELECT CONTENT FROM MY_TABLE_NAME WHERE ID = ?";
        try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
            preparedStatement.setString(1, id);
            try (ResultSet resultSet = preparedStatement.executeQuery()) {
                if (resultSet.next()) {
                    log.info("Start downloading the BLOB");
                    log.info("Total memory available: " + (int) (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " MB");
                    log.info("Max memory available: " + (int) (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " MB");

                    try (InputStream inputStream = resultSet.getBinaryStream("CONTENT");
                         OutputStream outputStream = response.getOutputStream()) {
                        byte[] buffer = new byte[1024 * 1024];

                        int bytesRead;

                        while ((bytesRead = inputStream.read(buffer)) != -1) {
                            outputStream.write(buffer, 0, bytesRead);

                            log.info("Memory available: " + (int) (Runtime.getRuntime().freeMemory() / (1024 * 1024)) + " MB");
                        }
                    }

                    deleteRecord(id, connection);
                    log.info("BLOB download completed");
                }
            }
        }
    }
}

发生的情况是我可以看到最后一个日志“BLOB 下载完成” 然后我得到OutOfMemoryError。

此外,最后一个可用内存日志为: 可用内存: 570 MB

编辑:实际上我找到了一个解决方案,我用 ZipOutputStream 压缩了 blob,机器不会出现 OutOfMemory。我仍然无法弄清楚问题出在哪里

Java Spring Oracle Blob

评论

0赞 Abra 11/7/2023
哪一行代码导致了错误?是这个吗:?Blob blob = resultSet.getBlob("CONTENT");
0赞 Calu River 11/7/2023
你好!我编辑了我的帖子,我不确定我到底在哪里得到错误,如果你能帮我调试,我可以试试。
1赞 Stephen C 11/7/2023
向我们展示OutOfMemoryError
0赞 Abra 11/8/2023
我从未使用过 Spring,但是堆栈跟踪(的)是否记录在任何地方?也许在日志文件中?另外,您添加的所有行都会被执行吗?有问题的线将介于最后一个执行的线和第一个未执行的线之间。OutOfMemoryErrorlog.info(...log.info(...

答:

0赞 Adrian Bucher 11/7/2023 #1

错误发生在哪一行?使用 resultSet.getBinaryStream() 可以保存一些行,它应该使用更少的内存,因为它不会先加载 blob。

评论

0赞 Calu River 11/7/2023
你好!我尝试了您的解决方案,但似乎没有任何改变。日志中可用的内存似乎减少了 200 MB,即文件的维度 (BLOB)。
0赞 Community 11/8/2023
您的答案可以通过其他支持信息进行改进。请编辑以添加更多详细信息,例如引文或文档,以便其他人可以确认您的答案是正确的。您可以在帮助中心找到有关如何写出好答案的更多信息。