0

我在 Spring MVC 应用程序中有一些代码可以查询数据库并从结果集中构建 CSV 文件。总体思路是这样的:

@RequestMapping
(value = "/path/to/the/data")
public ModelAndView getDataAsCsv(...) {
    List<RowObject> bigObject; // can be > 1GB in memory
    bigObject = dataService.getData(...);
    ModelAndView mav = new ModelAndView("dataCsvView");
    mav.addObject("bigObject", bigObject);
    return mav;
}

然后我们有一个DataCsvView扩展AbstractView和覆盖的类renderMergedOutputModel。它所做的第一件事就是:

List<RowObject> bigObject = (ArrayList<RowObject>)model.get("bigObject");

然后它继续迭代这个东西,将每一行转换为一个 CSV 字符串,并将其写入BufferedWriter来自 SpringHttpServletResponse#getWriter方法的那个。

问题

如果数据集足够大(超过约 500k 行),那么bigObject内存中的内容将 > 1GB。一旦renderMergedOutputModel被调用并尝试将对象从模型中取出,应用程序就会抛出 OutOfMemoryError。或者,错误可能在renderMergedOutputModel被调用之前发生。无论哪种方式,将对象放入mav地图的行为都不是失败点(我已经检查过)。

一种解决方案是增加 Tomcat 进程可用的内存,但这显然不是很可扩展……(这些bigObjects 将来可能会变得更大)

在 Spring MVC 中执行此操作的“正确”方法是什么?是否有一个不错的架构可以让我们流式传输响应而无需先将其全部存储在内存中?

4

1 回答 1

0

我不会为此使用View实现,而是写给OutputStream我自己,我会做这个流式传输而不是将所有内容加载到内存中。如何做到这一点取决于您的底层技术,但使用 aJdbcTemplate我会直接实现RowCallBackHandler写入Writer(可能通过将其传递给服务方法)。

public class CvsWritingRowCallbackhandler implements RowCallbackHandler {

    private final Writer writer;

    public CvsWritingRowCallbackhandler(Writer writer) {
        this.writer=writer;
    }

    public void processRow(ResultSet rs) throws SQLException {      
        String line = // do something with current row to create a Comma Seperated line
        writer.write(line);
                    writer.flush();
    }

}

public void someServiceMethod(final Writer writer) {
    getJdbcTemplate().query(query, new CvsWritingRowCallbackhandler(writer)); 
}

@RequestMapping
public void requestHandlingMethod(Writer writer) {
    someService.someServiceMethod(writer);
}

或者在加载期间将 CSV 数据流式传输到文件,然后创建一个视图,将该文件流式传输到客户端(这样您就不必一直将所有内容都保存在内存中)。您只需要以这种方式传递文件名。

于 2013-09-11T11:53:55.593 回答