От POI к DsExcel: руководство по интеграции SpreadJS

От POI к DsExcel: руководство по интеграции SpreadJS

1. Введение: почему снова «Excel»?

Одним из наиболее распространённых и одновременно сложных требований при разработке корпоративных решений, несомненно, является **«обработка Excel»**. Пользователям в реальной бизнес-среде недостаточно просто просматривать эффектные информационные панели на веб-экранах: им необходимо выполнять запросы к сложным табличным данным с несколькими уровнями заголовков, скачивать шаблоны, оформленные в виде древовидной структуры на основе групп, работать в автономном режиме, а затем массово загружать результаты обратно в систему.

Долгое время Apache POI фактически был стандартом обработки Excel в экосистеме Java backend. Благодаря большому количеству примеров в экосистеме открытого исходного кода и неоспоримому преимуществу в виде бесплатного использования большинство проектов начинали с POI. Однако при сочетании следующих сложных бизнес-требований команды разработчиков сталкиваются с огромным техническим барьером.

  • Создание шаблонов с групповыми полями и **сложными иерархическими многоуровневыми заголовками (Multi-Header)**, а также древовидными структурами

  • При внедрении высокопроизводительного табличного компонента, такого как SpreadJSв веб-интерфейсах между фронтендом и бэкендом могут возникать расхождения в форматировании и синхронизации данных

  • Помимо простого ввода и вывода значений, SUMPRODUCT, DSUM и других расширенных механизмов статистических вычислений и вычисления формулсерверная оценка в реальном времени

  • При обработке от десятков до сотен тысяч строк JVM OutOfMemoryError(OOM) и скачки GC

В этой статье рассматриваются реальные ограничения, с которыми мы столкнулись в среде Apache POI, а также описывается наш опыт модернизации архитектуры за счёт внедрения **DsExcel (Document Solutions for Excel, ранее GcExcel)**. В частности, рассматривается синергия нативной интеграции с JSON и SpreadJS на фронтенде, а также стратегии импорта шаблонов с древовидной структурой, экспорта таблиц с несколькими уровнями заголовков и обработки сложных формулкоторые мы ранее тщательно рассматривали на этапах моделирования предметной области и проектирования данных, подробно — на уровне кода и архитектуры.

2. Подробное сравнение серверных движков Excel: Apache POI и DsExcel

Ключевыми факторами, которые следует учитывать при выборе библиотеки Excel для бэкенда, являются архитектура памяти, механизм вычисления формул, эффективность работы с шаблонами и лицензионные затраты.

2.1 Архитектура и модели управления памятью

Двойственность Apache POI (XSSFWorkbook и SXSSFWorkbook)

  • XSSFWorkbook (DOM-модель): Загружает все листы, строки, ячейки и объекты стилей файла Excel (XLSX) в кучу JVM в виде полного графа объектов. После превышения объёма данных в несколько десятков тысяч записей для каждой ячейки создаётся множество объектов-обёрток Java, из-за чего потребление памяти растёт экспоненциально, быстро приводя к OOM.

  • SXSSFWorkbook (потоковая оконная модель): Чтобы избежать OOM, хранит в памяти только скользящее окно (например, последние 100 строк), а остальные данные выгружает во временные файлы на диске. Однако это связано с серьёзными ограничениями.

  • Невозможно изменять уже обработанные строки или обращаться к ним в обратном порядке.

  • Динамический пересчёт объединённых ячеек (Merged Region) или иерархических заголовков крайне затруднён.

  • Вычисление формул со ссылками на другие листы и поддержка сводных таблиц практически невозможны.

  • При создании отчётов на основе шаблонов сложно одновременно считывать форматирование существующего шаблона и выполнять потоковую запись (Read-Write Template).

Высокопроизводительная архитектура движка DsExcel

  • DsExcel идеально имитирует механизм вычислений и внутренние структуры данных Microsoft Excel, используя при этом высокооптимизированную структуру с отображением в память, подходящую для сред Java/.NET.

  • Оптимизация памяти на основе указателей и примитивных массивов: Вместо бесконечного создания тяжёлых экземпляров для каждой ячейки, как в POI, данные и стили кэшируются на основе внутренних индексов, что позволяет ограничить использование кучи примерно от 1/5 до 1/10 уровня POI.

  • Он способен полностью в памяти с высокой скоростью создавать и анализировать книги со ста тысячами строк и более без временного ввода-вывода на диск.

2.2 Различия в механизмах вычисления формул

На практике поддержка часто запрашиваемых формул, таких как SUMPRODUCT, DSUM, INDEX/MATCH, XLOOKUP и динамические массивы (Spill), является решающим показателем зрелости движка.

  • Apache POI: Хотя FormulaEvaluator встроен, список поддерживаемых функций ограничен. В частности, при вычислении SUMPRODUCT, выполняющей операции с массивами по нескольким условиям, или функций базы данных (DSUM, DCOUNT) реализации часто отсутствуют, либо при разборе сложных вложенных скобок регулярно возникает NotImplementedException.

  • DsExcel: Он включает независимый высокопроизводительный механизм вычисления формул, на 100% совместимый со спецификациями Microsoft Excel, и способен безупречно вычислять более 450 стандартных функций Excel в автономном режиме (на стороне сервера). Это гарантирует полную согласованность формул без установки Excel Desktop или Office Interop (Interop).

2.3 Эффективность привязки шаблонов

  • Подход POI: После загрузки разработанного формата Excel разработчики должны вручную подсчитывать индексы строк и столбцов (rowNum++, cellNum++) и задавать через код Java стили шрифтов, границ и цвета фона. Даже если специалист по планированию или дизайнер изменит всего одну строку форматирования, вся логика вычисления индексов на бэкенде нарушится.

  • Подход DsExcel: Мощная Декларативный механизм шаблоновпредоставляется по умолчанию. Если непосредственно в файле Excel записать такой синтаксис, как {{ds.fieldName}} и {{ds.group(R=T)}}, серверная часть сможет автоматически выполнять развертывание повторяющихся строк, вставку промежуточных и общих итогов, а также динамическое объединение ячеек — достаточно передать Java POJO или набор данных JSON.

2.4. Сводная сравнительная таблица

Критерий сравнения

Apache POI (XSSF / SXSSF)

DsExcel (Document Solutions for Excel)

Лицензия

Открытый исходный код (Apache License 2.0, бесплатно)

Коммерческая лицензия (MESCIUS, лицензия разработчика/основная лицензия)

Объем используемой памяти

XSSF: очень высокий / SXSSF: низкий

Очень низкий и стабильный благодаря оптимизации в памяти

Ограничения потоковой обработки

Ограничения на чтение и изменение, отсутствие вычисления формул

Полная поддержка интерфейса (доступен произвольный доступ)

Интеграция со SpreadJS

Нестандартная (требуется разбор двоичных данных XLSX)

Встроенная поддержка (полная совместимость с fromJson и toJson)

Поддержка вычисления формул

В основном поддерживаются базовые функции, при этом имеются ограничения для сложных формул

Поддерживает более 450 функций Excel и динамические массивы

Механизм шаблонов

Отсутствует (требуется ручное программирование)

Встроенный декларативный синтаксис шаблонов

Возможности преобразования документов

Требуется интеграция с отдельными инструментами

Прямая поддержка преобразования в PDF, HTML и изображения

3. Синергия с внешним интерфейсом (SpreadJS): полноценный конвейер Excel для всего стека

Для систем, которые используют SpreadJSв качестве компонента сетки во внешнем интерфейсе, выбор DsExcel в качестве серверного механизма является не просто альтернативой, а важным архитектурным решением.

3.1. Обмен исходным JSON без потерь (нулевая потеря точности)

Как правило, экспорт или импорт содержимого веб-экрана (сетки) в серверный файл Excel или из него требует прохождения следующих сложных узких мест.

  • Существующий подход (SpreadJS + Apache POI):

a. Сгенерировать двоичный blob XLSX на клиенте (SpreadJS)

b. Передать по сети несколько МБ двоичных данных

c. На серверной стороне POI распаковывает ZIP-архив и анализирует тысячи XML-файлов

d. После изменения снова сжать данные в ZIP-архив и вернуть его клиенту

—Проблемы: Перегрузка ЦП, напрасный расход пропускной способности сети и потеря расширенных стилей CSS/тем/условного форматирования, которые POI не может интерпретировать.

  • Современная архитектура (SpreadJS + DsExcel):

—SpreadJS и DsExcel используют **одинаковую схему данных и спецификацию JSON (формат JSON SpreadJS)**.

—Клиент передает только сериализованную строку JSON сетки (spread.toJSON()).

—Серверная часть (DsExcel) считывает ее одной строкой.

Workbook workbook = new Workbook();
workbook.fromJson(spreadJsonString);

—После выполнения бизнес-логики, крупномасштабного объединения данных и маскирования на сервере результат можно либо вернуть с помощью workbook.toJson(), либо немедленно преобразовать в файл .xlsx или .pdf для конечного распространения.

3.2. Сохранение стилей, множественных объединений и условного форматирования без потерь

Многоуровневые объединения заголовков (Span), иерархии шрифтов, гистограммы данных (Data Bar), цветовые шкалы и правила проверки данных с раскрывающимися списками, настроенные пользователями в SpreadJS, отражаются в серверной части DsExcel со 100%-ной точностью. Структурные проблемы, возникающие при использовании POI — необходимость индивидуального сопоставления CellStyle и беспокойство по поводу переполнения индексов, — полностью устраняются.

4. Практический пример: решение сложных задач в корпоративной предметной области

4.1. Пример 1: импорт и экспорт Excel без потерь для иерархической веб-сетки с многоуровневыми заголовками (Multi-Header)

  • Формулировка проблемы:Веб-таблица на экране имеет структуру сгруппированных заголовков с тремя и более уровнями. Чтобы обработать её с помощью POI, нам пришлось рекурсивно вычислять координаты горизонтальных и вертикальных объединений с использованием CellRangeAddress, при этом координаты искажались всякий раз, когда столбцы изменялись динамически.

  • Решение на основе DsExcel + SpreadJS:Передавая структуру заголовков столбцов SpreadJS непосредственно в DsExcel с помощью fromJson, можно идеально сохранить внутренние координаты объединений. Это устраняет несоответствия между схемами заголовков на сервере и во внешнем интерфейсе.

4.2 Случай 2: шаблон импорта данных с иерархической структурой групповых полей

  • Постановка проблемы:Модель сущностей содержала подкатегории внутри основных категорий, а сгруппированные под ними поля динамических атрибутов образовывали иерархическую структуру.

  • Применение шаблонизатора DsExcel:При задании в шаблоне XLSX декларативных маркеров, таких как {{ds.groupName(M=T)}}, и простой передаче набора данных автоматически выполнялись динамическое расширение строк и объединение ячеек. Логика ручного вычисления указателей была устранена, что максимально повысило удобство сопровождения.

4.3 Случай 3: обработка сложных формул (SUMPRODUCT, DSUM) и вычисления на стороне сервера

  • Постановка проблемы:Нам требовалось немедленно проверять на сервере результаты сложных условных расчётов с весовыми коэффициентами в загружаемых файлах.

  • Стратегия решения:При вызове DsExcel's workbook.calculate() встроенный механизм, совместимый с Excel, определяет все зависимые ячейки и вычисляет точные результаты, позволяя избежать дублирования математической логики в коде Java.

4.4 Случай 4: архитектура доменной модели для управления вложениями и данными

  • Проектирование архитектуры:Чтобы предотвратить исчерпание HTTP-потоков при обработке больших объёмов данных, мы внедрили асинхронный конвейер с очередью (Spring Task / RabbitMQ) и использовали try-with-resources для немедленного освобождения ресурсов книги DsExcel, благодаря чему удалось поддерживать стабильное потребление памяти JVM.

5. Практическое руководство по коду: POI и DsExcel

5.1 Пример реализации на Apache POI (ручное сопоставление координат)

Workbook poiWorkbook = new XSSFWorkbook();
Sheet sheet = poiWorkbook.createSheet("계층형데이터");
Row headerRow1 = sheet.createRow(0);
headerRow1.createCell(0).setCellValue("조직 인프라 정보");
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 1));
int rowIndex = 2;
for (DepartmentDto dto : dataList) {
    Row row = sheet.createRow(rowIndex++);
    row.createCell(0).setCellValue(dto.getDivision());
    // ... 인덱스 수동 관리 및 스타일 설정 생략
}

5.2 Пример реализации конвейера DsExcel + SpreadJS

public byte[] generateExcelWithDsExcel(String spreadJsJson, List<DepartmentDto> dataList) {
    try (Workbook workbook = new Workbook()) {
        workbook.fromJson(spreadJsJson); // 프론트 디자인 로드
        IWorksheet sheet = workbook.getActiveSheet();
        sheet.addDataSource("dept", dataList); // 데이터 바인딩
        workbook.calculate(); // 수식 재계산

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        workbook.save(out, SaveFileFormat.Xlsx);
        return out.toByteArray();
    }
}

6. Заключение и руководство по принятию решения о переходе

6.1 Когда можно продолжать использовать POI

  • Экспорт необработанных данных в простом формате одномерной таблицы

  • Создание небольших файлов Excel с фиксированным форматом (не более нескольких тысяч записей)

  • Проекты с открытым исходным кодом, которые не могут выделить средства на дополнительные лицензионные расходы

6.2 Когда настоятельно рекомендуется перейти на DsExcel

  • SpreadJSПроекты, которым требуется идеальная среда WYSIWYG за счёт внедрения

  • Управление шаблонами, содержащими сложные многоуровневые заголовки, иерархические данные и большое количество объединённых ячеек

  • Когда требуется проверка на стороне сервера корректности сложных формул, таких как SUMPRODUCT и XLOOKUP

  • Когда стабильность системы при обработке больших объёмов данных оказывается под угрозой из-за сбоев POI, вызванных нехваткой памяти (OOM)

Заключительные замечания

В современных корпоративных архитектурах Excel — это не просто файл, а набор ключевых бизнес-правил. Сочетание DsExcel и SpreadJSпозволит разработчикам отказаться от утомительных вычислений координат и сосредоточиться на бизнес-логике.

BigJumbo

Site footer