From 2edfcea189fade9cc7f1c0eb56a3831768b88559 Mon Sep 17 00:00:00 2001 From: Martin Lopez Date: Mon, 27 Nov 2023 17:07:35 -0300 Subject: [PATCH] perf: increase performance when exporting large datasets increase performance by avoid calling sheet.shiftRows() by duplicating the sheet into a temporal one, exporting the data and then appending the last part of the sheet before continuing with the footers --- .../gridexporter/ExcelInputStreamFactory.java | 78 ++++++++++++++---- .../GridExporterBigDatasetDemo.java | 79 +++++++++++++++++++ .../gridexporter/GridExporterDemoView.java | 1 + 3 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 src/test/java/com/flowingcode/vaadin/addons/gridexporter/GridExporterBigDatasetDemo.java diff --git a/src/main/java/com/flowingcode/vaadin/addons/gridexporter/ExcelInputStreamFactory.java b/src/main/java/com/flowingcode/vaadin/addons/gridexporter/ExcelInputStreamFactory.java index f28acbf..c02c05c 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/gridexporter/ExcelInputStreamFactory.java +++ b/src/main/java/com/flowingcode/vaadin/addons/gridexporter/ExcelInputStreamFactory.java @@ -20,12 +20,6 @@ /** */ package com.flowingcode.vaadin.addons.gridexporter; -import com.vaadin.flow.component.ComponentUtil; -import com.vaadin.flow.component.grid.ColumnTextAlign; -import com.vaadin.flow.component.grid.Grid.Column; -import com.vaadin.flow.data.binder.BeanPropertySet; -import com.vaadin.flow.data.binder.PropertySet; -import com.vaadin.flow.data.provider.DataProvider; import java.io.IOException; import java.io.InputStream; import java.io.PipedInputStream; @@ -39,7 +33,6 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; - import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -58,6 +51,12 @@ import org.apache.poi.ss.util.CellRangeAddress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.vaadin.flow.component.ComponentUtil; +import com.vaadin.flow.component.grid.ColumnTextAlign; +import com.vaadin.flow.component.grid.Grid.Column; +import com.vaadin.flow.data.binder.BeanPropertySet; +import com.vaadin.flow.data.binder.PropertySet; +import com.vaadin.flow.data.provider.DataProvider; /** * @author mlope @@ -107,10 +106,17 @@ public InputStream createInputStream() { // initialize the data range with tne coordinates of tha data placeholder cell CellRangeAddress dataRange = new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex(), cell.getColumnIndex(), cell.getColumnIndex()); - fillData(sheet, cell, exporter.grid.getDataProvider(), dataRange, titleCell != null); + + Sheet tempSheet = wb.cloneSheet(exporter.sheetNumber); + + int lastRow = fillData(sheet, cell, exporter.grid.getDataProvider(), dataRange, titleCell != null); applyConditionalFormattings(sheet, dataRange); + copyBottomOfSheetStartingOnRow(wb, tempSheet, sheet, cell.getRowIndex()+1, lastRow); + + wb.removeSheetAt(exporter.sheetNumber + 1); + cell = findCellWithPlaceHolder(sheet, exporter.footersPlaceHolder); List>> footers = getGridFooters(exporter.grid); if (cell != null) { @@ -166,6 +172,54 @@ public void run() { return in; } + private void copyBottomOfSheetStartingOnRow(Workbook workbook, Sheet sourceSheet, + Sheet targetSheet, int rowIndex, int targetRow) { + int fRow = rowIndex; + int lRow = sourceSheet.getLastRowNum(); + for (int iRow = fRow; iRow <= lRow; iRow++) { + Row row = sourceSheet.getRow(iRow); + Row myRow = targetSheet.createRow(targetRow++); + if (row != null) { + short fCell = row.getFirstCellNum(); + short lCell = row.getLastCellNum(); + for (int iCell = fCell; iCell < lCell; iCell++) { + Cell cell = row.getCell(iCell); + Cell newCell = myRow.createCell(iCell); + newCell.setCellStyle(cell.getCellStyle()); + if (cell != null) { + switch (cell.getCellType()) { + case BLANK: + newCell.setCellValue(""); + break; + + case BOOLEAN: + newCell.setCellValue(cell.getBooleanCellValue()); + break; + + case ERROR: + newCell.setCellErrorValue(cell.getErrorCellValue()); + break; + + case FORMULA: + newCell.setCellFormula(cell.getCellFormula()); + break; + + case NUMERIC: + newCell.setCellValue(cell.getNumericCellValue()); + break; + + case STRING: + newCell.setCellValue(cell.getStringCellValue()); + break; + default: + newCell.setCellFormula(cell.getCellFormula()); + } + } + } + } + } + } + private void applyConditionalFormattings(Sheet sheet, CellRangeAddress targetCellRange) { SheetConditionalFormatting sheetCondFormatting = sheet.getSheetConditionalFormatting(); @@ -177,7 +231,7 @@ private void applyConditionalFormattings(Sheet sheet, CellRangeAddress targetCel } - private void fillData( + private int fillData( Sheet sheet, Cell dataCell, DataProvider dataProvider, CellRangeAddress dataRange, boolean titleExists) { Stream dataStream = obtainDataStream(dataProvider); @@ -188,11 +242,6 @@ private void fillData( t -> { if (notFirstRow[0]) { CellStyle cellStyle = startingCell[0].getCellStyle(); - int lastRow = sheet.getLastRowNum(); - sheet.shiftRows( - startingCell[0].getRowIndex() + (titleExists ? 1 : 0), - lastRow, - (titleExists ? 1 : 0)); Row newRow = sheet.createRow(startingCell[0].getRowIndex() + 1); startingCell[0] = newRow.createCell(startingCell[0].getColumnIndex()); startingCell[0].setCellStyle(cellStyle); @@ -205,6 +254,7 @@ private void fillData( // since we initialized the cell range with the data placeholder cell, we use // the existing 'getLastColumn' to keep the offset of the data range dataRange.setLastColumn(dataRange.getLastColumn() + exporter.getColumns().size() - 1); + return startingCell[0].getRowIndex(); } @SuppressWarnings("unchecked") diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridexporter/GridExporterBigDatasetDemo.java b/src/test/java/com/flowingcode/vaadin/addons/gridexporter/GridExporterBigDatasetDemo.java new file mode 100644 index 0000000..1b2650c --- /dev/null +++ b/src/test/java/com/flowingcode/vaadin/addons/gridexporter/GridExporterBigDatasetDemo.java @@ -0,0 +1,79 @@ +/*- + * #%L + * Grid Exporter Add-on + * %% + * Copyright (C) 2022 - 2023 Flowing Code + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package com.flowingcode.vaadin.addons.gridexporter; + +import java.io.IOException; +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.apache.poi.EncryptedDocumentException; +import com.flowingcode.vaadin.addons.demo.DemoSource; +import com.github.javafaker.Faker; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.grid.Grid.Column; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; + +@DemoSource +@PageTitle("Grid Exporter Addon Big Dataset Demo") +@Route(value = "gridexporter/bigdataset", layout = GridExporterDemoView.class) +@SuppressWarnings("serial") +public class GridExporterBigDatasetDemo extends Div { + + public GridExporterBigDatasetDemo() throws EncryptedDocumentException, IOException { + Grid grid = new Grid<>(Person.class); + grid.removeAllColumns(); + grid.setColumnReorderingAllowed(true); + Column nameCol = grid.addColumn("name").setHeader("Name"); + Column lastNameCol = grid.addColumn("lastName").setHeader("Last Name"); + Column budgetCol = grid.addColumn(item -> "$" + item.getBudget()).setHeader("Budget"); + BigDecimal[] total = new BigDecimal[1]; + total[0] = BigDecimal.ZERO; + List persons = + IntStream.range(0, 7000) + .asLongStream() + .mapToObj( + number -> { + Faker faker = new Faker(); + Double budget = faker.number().randomDouble(2, 10000, 100000); + total[0] = total[0].add(BigDecimal.valueOf(budget)); + budgetCol.setFooter("$" + total[0]); + return new Person( + faker.name().firstName(), + faker.name().lastName(), + faker.number().numberBetween(15, 50), + budget); + }).collect(Collectors.toList()); + grid.setItems(query->persons.stream().skip(query.getOffset()).limit(query.getLimit())); + grid.setWidthFull(); + this.setSizeFull(); + GridExporter exporter = GridExporter.createFor(grid); + exporter.setExportValue(budgetCol, item -> "" + item.getBudget()); + exporter.setColumnPosition(lastNameCol, 1); + exporter.setTitle("People information"); + exporter.setFileName( + "GridExport" + new SimpleDateFormat("yyyyddMM").format(Calendar.getInstance().getTime())); + add(grid); + } +} diff --git a/src/test/java/com/flowingcode/vaadin/addons/gridexporter/GridExporterDemoView.java b/src/test/java/com/flowingcode/vaadin/addons/gridexporter/GridExporterDemoView.java index 441bab6..fcd9db0 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/gridexporter/GridExporterDemoView.java +++ b/src/test/java/com/flowingcode/vaadin/addons/gridexporter/GridExporterDemoView.java @@ -38,6 +38,7 @@ public GridExporterDemoView() { addDemo(GridExporterCustomLinkDemo.class); addDemo(GridExporterCustomColumnsDemo.class); addDemo(GridExporterHierarchicalDataDemo.class); + addDemo(GridExporterBigDatasetDemo.class); setSizeFull(); } }