Skip to content

Commit 4cff94b

Browse files
mlopezFCpaodb
authored andcommitted
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
1 parent 14e19b1 commit 4cff94b

File tree

3 files changed

+144
-14
lines changed

3 files changed

+144
-14
lines changed

src/main/java/com/flowingcode/vaadin/addons/gridexporter/ExcelInputStreamFactory.java

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,6 @@
2020
/** */
2121
package com.flowingcode.vaadin.addons.gridexporter;
2222

23-
import com.vaadin.flow.component.ComponentUtil;
24-
import com.vaadin.flow.component.grid.ColumnTextAlign;
25-
import com.vaadin.flow.component.grid.Grid.Column;
26-
import com.vaadin.flow.data.binder.BeanPropertySet;
27-
import com.vaadin.flow.data.binder.PropertySet;
28-
import com.vaadin.flow.data.provider.DataProvider;
2923
import java.io.IOException;
3024
import java.io.InputStream;
3125
import java.io.PipedInputStream;
@@ -39,7 +33,6 @@
3933
import java.util.List;
4034
import java.util.stream.Collectors;
4135
import java.util.stream.Stream;
42-
4336
import org.apache.commons.lang3.ArrayUtils;
4437
import org.apache.commons.lang3.StringUtils;
4538
import org.apache.commons.lang3.tuple.Pair;
@@ -58,6 +51,12 @@
5851
import org.apache.poi.ss.util.CellRangeAddress;
5952
import org.slf4j.Logger;
6053
import org.slf4j.LoggerFactory;
54+
import com.vaadin.flow.component.ComponentUtil;
55+
import com.vaadin.flow.component.grid.ColumnTextAlign;
56+
import com.vaadin.flow.component.grid.Grid.Column;
57+
import com.vaadin.flow.data.binder.BeanPropertySet;
58+
import com.vaadin.flow.data.binder.PropertySet;
59+
import com.vaadin.flow.data.provider.DataProvider;
6160

6261
/**
6362
* @author mlopez
@@ -107,10 +106,17 @@ public InputStream createInputStream() {
107106

108107
// initialize the data range with tne coordinates of tha data placeholder cell
109108
CellRangeAddress dataRange = new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex(), cell.getColumnIndex(), cell.getColumnIndex());
110-
fillData(sheet, cell, exporter.grid.getDataProvider(), dataRange, titleCell != null);
109+
110+
Sheet tempSheet = wb.cloneSheet(exporter.sheetNumber);
111+
112+
int lastRow = fillData(sheet, cell, exporter.grid.getDataProvider(), dataRange, titleCell != null);
111113

112114
applyConditionalFormattings(sheet, dataRange);
113115

116+
copyBottomOfSheetStartingOnRow(wb, tempSheet, sheet, cell.getRowIndex()+1, lastRow);
117+
118+
wb.removeSheetAt(exporter.sheetNumber + 1);
119+
114120
cell = findCellWithPlaceHolder(sheet, exporter.footersPlaceHolder);
115121
List<Pair<String, Column<T>>> footers = getGridFooters(exporter.grid);
116122
if (cell != null) {
@@ -166,6 +172,54 @@ public void run() {
166172
return in;
167173
}
168174

175+
private void copyBottomOfSheetStartingOnRow(Workbook workbook, Sheet sourceSheet,
176+
Sheet targetSheet, int rowIndex, int targetRow) {
177+
int fRow = rowIndex;
178+
int lRow = sourceSheet.getLastRowNum();
179+
for (int iRow = fRow; iRow <= lRow; iRow++) {
180+
Row row = sourceSheet.getRow(iRow);
181+
Row myRow = targetSheet.createRow(targetRow++);
182+
if (row != null) {
183+
short fCell = row.getFirstCellNum();
184+
short lCell = row.getLastCellNum();
185+
for (int iCell = fCell; iCell < lCell; iCell++) {
186+
Cell cell = row.getCell(iCell);
187+
Cell newCell = myRow.createCell(iCell);
188+
newCell.setCellStyle(cell.getCellStyle());
189+
if (cell != null) {
190+
switch (cell.getCellType()) {
191+
case BLANK:
192+
newCell.setCellValue("");
193+
break;
194+
195+
case BOOLEAN:
196+
newCell.setCellValue(cell.getBooleanCellValue());
197+
break;
198+
199+
case ERROR:
200+
newCell.setCellErrorValue(cell.getErrorCellValue());
201+
break;
202+
203+
case FORMULA:
204+
newCell.setCellFormula(cell.getCellFormula());
205+
break;
206+
207+
case NUMERIC:
208+
newCell.setCellValue(cell.getNumericCellValue());
209+
break;
210+
211+
case STRING:
212+
newCell.setCellValue(cell.getStringCellValue());
213+
break;
214+
default:
215+
newCell.setCellFormula(cell.getCellFormula());
216+
}
217+
}
218+
}
219+
}
220+
}
221+
}
222+
169223
private void applyConditionalFormattings(Sheet sheet, CellRangeAddress targetCellRange) {
170224
SheetConditionalFormatting sheetCondFormatting = sheet.getSheetConditionalFormatting();
171225

@@ -177,7 +231,7 @@ private void applyConditionalFormattings(Sheet sheet, CellRangeAddress targetCel
177231

178232
}
179233

180-
private void fillData(
234+
private int fillData(
181235
Sheet sheet, Cell dataCell, DataProvider<T, ?> dataProvider, CellRangeAddress dataRange, boolean titleExists) {
182236
Stream<T> dataStream = obtainDataStream(dataProvider);
183237

@@ -188,11 +242,6 @@ private void fillData(
188242
t -> {
189243
if (notFirstRow[0]) {
190244
CellStyle cellStyle = startingCell[0].getCellStyle();
191-
int lastRow = sheet.getLastRowNum();
192-
sheet.shiftRows(
193-
startingCell[0].getRowIndex() + (titleExists ? 1 : 0),
194-
lastRow,
195-
(titleExists ? 1 : 0));
196245
Row newRow = sheet.createRow(startingCell[0].getRowIndex() + 1);
197246
startingCell[0] = newRow.createCell(startingCell[0].getColumnIndex());
198247
startingCell[0].setCellStyle(cellStyle);
@@ -205,6 +254,7 @@ private void fillData(
205254
// since we initialized the cell range with the data placeholder cell, we use
206255
// the existing 'getLastColumn' to keep the offset of the data range
207256
dataRange.setLastColumn(dataRange.getLastColumn() + exporter.getColumns().size() - 1);
257+
return startingCell[0].getRowIndex();
208258
}
209259

210260
@SuppressWarnings("unchecked")
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*-
2+
* #%L
3+
* Grid Exporter Add-on
4+
* %%
5+
* Copyright (C) 2022 - 2023 Flowing Code
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package com.flowingcode.vaadin.addons.gridexporter;
21+
22+
import java.io.IOException;
23+
import java.math.BigDecimal;
24+
import java.text.SimpleDateFormat;
25+
import java.util.Calendar;
26+
import java.util.List;
27+
import java.util.stream.Collectors;
28+
import java.util.stream.IntStream;
29+
import org.apache.poi.EncryptedDocumentException;
30+
import com.flowingcode.vaadin.addons.demo.DemoSource;
31+
import com.github.javafaker.Faker;
32+
import com.vaadin.flow.component.grid.Grid;
33+
import com.vaadin.flow.component.grid.Grid.Column;
34+
import com.vaadin.flow.component.html.Div;
35+
import com.vaadin.flow.router.PageTitle;
36+
import com.vaadin.flow.router.Route;
37+
38+
@DemoSource
39+
@PageTitle("Grid Exporter Addon Big Dataset Demo")
40+
@Route(value = "gridexporter/bigdataset", layout = GridExporterDemoView.class)
41+
@SuppressWarnings("serial")
42+
public class GridExporterBigDatasetDemo extends Div {
43+
44+
public GridExporterBigDatasetDemo() throws EncryptedDocumentException, IOException {
45+
Grid<Person> grid = new Grid<>(Person.class);
46+
grid.removeAllColumns();
47+
grid.setColumnReorderingAllowed(true);
48+
Column<Person> nameCol = grid.addColumn("name").setHeader("Name");
49+
Column<Person> lastNameCol = grid.addColumn("lastName").setHeader("Last Name");
50+
Column<Person> budgetCol = grid.addColumn(item -> "$" + item.getBudget()).setHeader("Budget");
51+
BigDecimal[] total = new BigDecimal[1];
52+
total[0] = BigDecimal.ZERO;
53+
List<Person> persons =
54+
IntStream.range(0, 7000)
55+
.asLongStream()
56+
.mapToObj(
57+
number -> {
58+
Faker faker = new Faker();
59+
Double budget = faker.number().randomDouble(2, 10000, 100000);
60+
total[0] = total[0].add(BigDecimal.valueOf(budget));
61+
budgetCol.setFooter("$" + total[0]);
62+
return new Person(
63+
faker.name().firstName(),
64+
faker.name().lastName(),
65+
faker.number().numberBetween(15, 50),
66+
budget);
67+
}).collect(Collectors.toList());
68+
grid.setItems(query->persons.stream().skip(query.getOffset()).limit(query.getLimit()));
69+
grid.setWidthFull();
70+
this.setSizeFull();
71+
GridExporter<Person> exporter = GridExporter.createFor(grid);
72+
exporter.setExportValue(budgetCol, item -> "" + item.getBudget());
73+
exporter.setColumnPosition(lastNameCol, 1);
74+
exporter.setTitle("People information");
75+
exporter.setFileName(
76+
"GridExport" + new SimpleDateFormat("yyyyddMM").format(Calendar.getInstance().getTime()));
77+
add(grid);
78+
}
79+
}

src/test/java/com/flowingcode/vaadin/addons/gridexporter/GridExporterDemoView.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public GridExporterDemoView() {
4040
addDemo(GridExporterCustomLinkDemo.class);
4141
addDemo(GridExporterCustomColumnsDemo.class);
4242
addDemo(GridExporterHierarchicalDataDemo.class);
43+
addDemo(GridExporterBigDatasetDemo.class);
4344
setSizeFull();
4445
}
4546
}

0 commit comments

Comments
 (0)