Отчёт по заказам за месяц экспортировали в Excel через стриминговую запись — SXSSFWorkbook, без накопления строк в памяти. Все были уверены, что память под контролем. При выгрузке за пиковый месяц сервис падал с OutOfMemoryError.

Причина нашлась не в библиотеке записи, а в шаге перед ней. Для каждой строки заказа код подгружал справочник — категорию товара, склад, менеджера — отдельными запросами без кэша:

for (Order order : orders) { Category category = categoryRepo.findById(order.getCategoryId()); Warehouse warehouse = warehouseRepo.findById(order.getWarehouseId()); row.write(order, category, warehouse); }

Hibernate кэшировал сущности в рамках сессии (persistence context), и за сотни тысяч строк в сессии накопились сотни тысяч объектов Category и Warehouse — хотя уникальных категорий и складов было от силы несколько десятков. Сама запись в файл была потоковой, но объекты из первого уровня кэша Hibernate никуда не девались до конца сессии.

Почему не упал раньше на тестах: тестовые данные включали десяток заказов и три категории — persistence context весил килобайты. Проблема проявилась только на реальном объёме, и профилировщик показал рост heap не в буфере записи, а в куче объектов сессии Hibernate.

Исправили в две строки: загрузили справочники один раз в Map перед циклом и вызывали entityManager.clear() каждые N строк, чтобы сессия не копила ссылки. Потоковая запись файла не спасает от утечки, если источник данных перед ней держит персистентный контекст открытым на весь экспорт.

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки