Ошибка нехватки памяти в методах экспорта CSV с Spring MVC - PullRequest
1 голос
/ 20 марта 2019

У нас возникли проблемы с нашим приложением, в котором не хватает памяти при создании файла CSV.Специально для больших CSV-файлов, в которых более 10 тыс. Строк.Мы используем Spring Boot 2.0.8 и SuperCSV 2.4.0.

. Как правильно подходить к этим случаям, чтобы наш Spring MVC API не зависал из-за OutOfMemoryException.

Может ли SuperCSV быть причиной этой проблемы?Я думаю, что это не так, но на всякий случай.

Я читал о @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();
}

Ответы [ 2 ]

1 голос
/ 20 марта 2019

Код:

data = dataFetchService.getData();

выглядит так, как будто он может занимать много памяти.Этот список может содержать миллионы записей.Или, если многие пользователи экспортируют одновременно, это приведет к проблемам с памятью.

Поскольку dataFetchService поддерживается хранилищем данных Spring, вы должны получить количество возвращаемых им записей, а затем получить данные по одной странице за раз.

Пример: если в строке 20 000 строкВ таблице вы должны получать 1000 строк данных за один раз 20 раз и медленно создавать свой CSV.

Вам также следует запросить данные в некотором порядке, иначе ваш CSV может оказаться в случайном порядке.

Посмотрите на реализацию PagingAndSortingRepository в вашем хранилище

Пример приложения

Product.java

import javax.persistence.Entity;
import javax.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {

    @Id
    private long id;
    private String name;
}

ProductRepository.java

import org.springframework.data.repository.PagingAndSortingRepository;

public interface ProductRepository extends PagingAndSortingRepository<Product, Integer> {
}

MyRest.java

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);
        }
    }

}

1) Загрузить данные по телефону

curl http://localhost:8080

2) Загрузить CSV

curl http://localhost:8080/csv
0 голосов
/ 25 марта 2019

Вы должны попытаться извлечь данные в чанках, используя setFetchSize, который выводит только ограниченные строки за один раз, используя курсоры в конце базы данных.Это увеличивает количество обращений к сети, но поскольку я загружаю потоковую передачу, для пользователя это не имеет большого значения, поскольку они постоянно получают файл.Я также использую функцию сервлета 3.0 Async для освобождения рабочего потока контейнера и передачи этой задачи другому пулу управляемых потоков Spring.Я использую это для базы данных Postgresql, и она работает как шарм.Драйверы MySQL и Oracle jdbc также поддерживают это.Я использую raw JDBCTemplate для доступа к данным и свой собственный набор результатов в csv конвертер плюс конвертер zip на лету.Для использования этого в хранилище данных Spring, пожалуйста, проверьте здесь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...