Java 使用Spring MVC的CSV导出方法内存不足错误
我们的应用程序在生成CSV文件时出现内存不足的问题。特别是在超过10k行的大型CSV文件上。我们使用的是Spring Boot 2.0.8和SuperCSV 2.4.0 处理这些情况的正确方法是什么,这样我们的SpringMVCAPI就不会因为Java 使用Spring MVC的CSV导出方法内存不足错误,java,spring-mvc,supercsv,Java,Spring Mvc,Supercsv,我们的应用程序在生成CSV文件时出现内存不足的问题。特别是在超过10k行的大型CSV文件上。我们使用的是Spring Boot 2.0.8和SuperCSV 2.4.0 处理这些情况的正确方法是什么,这样我们的SpringMVCAPI就不会因为OutOfMemoryException而崩溃 超级SV会是这个问题的原因吗?我想这不是为了以防万一 我一直在读关于@Async,在这个方法中使用它来打开一个单独的线程是一个好主意吗 假设我在控制器中有以下方法: @RequestMapping(value
OutOfMemoryException
而崩溃
超级SV会是这个问题的原因吗?我想这不是为了以防万一
我一直在读关于@Async
,在这个方法中使用它来打开一个单独的线程是一个好主意吗
假设我在控制器中有以下方法:
@RequestMapping(value = "/export", method = RequestMethod.GET)
public void downloadData(HttpServletRequest request,HttpServletResponse response) throws SQLException, ManualException, IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
List<?> data = null;
data = dataFetchService.getData();
ICsvBeanWriter csvWriter = new CsvBeanWriter(response.getWriter(), CsvPreference.STANDARD_PREFERENCE);
//these next lines handle the header
String[] header = getHeaders(data.get(0).getClass());
String[] headerLocale = new String[header.length];
for (int i = 0; i < header.length; i++)
{
headerLocale[i] = localeService.getLabel(this.language,header[i]);
}
//fix for excel not opening CSV files with ID in the first cell
if(headerLocale[0].equals("ID")) {
//adding a space before ID as ' ID' also helps
headerLocale[0] = headerLocale[0].toLowerCase();
}
csvWriter.writeHeader(headerLocale);
//the next lines handle the content
for (Object line : data) {
csvWriter.write(line, header);
}
csvWriter.close();
response.getWriter().flush();
response.getWriter().close();
}
@RequestMapping(value=“/export”,method=RequestMethod.GET)
public void downloadData(HttpServletRequest请求、HttpServletResponse响应)引发SQLException、ManualException、IOException、NoSuchMethodException、InvocationTargetException、IllegaAccessException{
列表数据=null;
data=dataFetchService.getData();
ICsvBeanWriter csvWriter=新的CsvBeanWriter(response.getWriter(),CsvPreference.STANDARD_首选项);
//接下来的几行处理标题
String[]header=getHeaders(data.get(0.getClass());
String[]headerLocale=新字符串[header.length];
对于(int i=0;i
代码:
data = dataFetchService.getData();
看起来它可能会消耗大量内存。此列表的大小可能有数百万条记录。或者,如果多个用户同时导出,这将导致内存问题
由于dataFetchService由Spring数据存储库支持,因此您应该获取它将返回的记录量,然后一次获取一个可分页的数据
示例:如果表中有20000行,则每次应获得1000行数据20次,然后慢慢建立CSV
您还应该以某种顺序请求数据,否则您的CSV可能以随机顺序结束
看看在您的存储库上实现
示例应用程序
Product.java
ProductRepository.java
2)下载CSV
curl http://localhost:8080/csv
您应该尝试使用在数据库端使用游标一次只带来有限行的方法来获取数据块中的数据。这增加了网络往返次数,但由于我正在对下载进行流式传输,因此对用户来说并不重要,因为他们不断地获取文件。我还使用Servlet3.0Async特性释放容器工作线程,并将此任务交给另一个Spring管理的线程池。我用它来处理Postgresql数据库,它工作起来很有魅力。MySQL和Oracle jdbc驱动程序也支持这一点。我使用raw进行数据访问,并使用自定义结果集转换为csv converter plus动态zip转换器。
要在Spring数据存储库上使用此功能,请检查此处 代码'data=dataFetchService.getData();'看起来可能会消耗大量内存。您需要从服务中获取页面数据,而不仅仅是获取所有内容。dataFetchService.getData()返回的列表的大小是多少?没错,它是存储库中的一个
findAll()
,但是当您希望将所有数据写入csv时,您将如何管理分页?您可以返回光标并动态解决实体问题?我的问题是速度。我会说,每页做100行可能太小了。我将运行一系列测试,看看是否有显著的改进,但页面大小也不同。可能需要将其调整到250行甚至1000行,但我认为,当代码写入http客户端使用的流时,为数据库获取块会让您感到惊讶。用户不应该注意到差异。
import org.springframework.data.repository.PagingAndSortingRepository;
public interface ProductRepository extends PagingAndSortingRepository<Product, Integer> {
}
import java.io.IOException;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.supercsv.io.CsvBeanWriter;
import org.supercsv.io.ICsvBeanWriter;
import org.supercsv.prefs.CsvPreference;
@RestController
@RequiredArgsConstructor
public class MyRest {
@Autowired
private ProductRepository repo;
private final int PAGESIZE = 1000;
@RequestMapping("/")
public String loadData() {
for (int record = 0; record < 10_000; record += 1) {
repo.save(new Product(record, "Product " + record));
}
return "Loaded Data";
}
@RequestMapping("/csv")
public void downloadData(HttpServletResponse response) throws IOException {
response.setContentType("text/csv");
String[] header = {"id", "name"};
ICsvBeanWriter csvWriter = new CsvBeanWriter(response.getWriter(), CsvPreference.STANDARD_PREFERENCE);
csvWriter.writeHeader(header);
long numberRecords = repo.count();
for (int fromRecord = 0; fromRecord < numberRecords; fromRecord += PAGESIZE) {
Pageable sortedByName = PageRequest.of(fromRecord, PAGESIZE, Sort.by("name"));
Page<Product> pageData = repo.findAll(sortedByName);
writeToCsv(header, csvWriter, pageData.getContent());
}
csvWriter.close();
response.getWriter().flush();
response.getWriter().close();
}
private void writeToCsv(String[] header, ICsvBeanWriter csvWriter, List<Product> pageData) throws IOException {
for (Object line : pageData) {
csvWriter.write(line, header);
}
}
}
curl http://localhost:8080
curl http://localhost:8080/csv