excel-import

excel-import v0.1.0

Потоковый импорт `.xlsx` в PostgreSQL — память не растёт от числа строк, ошибки возвращаются с цветовой разметкой.

Начать Открыть на GitHub Maven Central

Возможности

  • Потоковое SAX-чтение

    Потребление heap не зависит от числа строк — импортируются файлы, не помещающиеся в память.

  • Маппинг по аннотациям

    Строки на POJO через @ExcelSheet / @ExcelColumn / @TargetTable.

  • Jakarta Bean Validation

    Построчная валидация: @NotNull, @Size, @PastOrPresent и др.

  • Батчинг multi-row INSERT

    Один INSERT на батч, одна транзакция на батч, рекурсивная бисекция при сбое.

  • Excel-отчёт с разметкой

    Вставленные строки зелёные, отклонённые красные, причина — в добавленной колонке.

  • ON CONFLICT + Spring Boot starter

    Стратегии конфликтов (doNothing/doUpdate) и опциональная Spring Boot autoconfiguration.

Как это работает

Файл читается дважды. Проход 1 стримит данные через маппинг, валидацию и батч-вставку; между проходами в памяти живёт только компактная карта исходов (~1 МБ на миллион строк; сообщения вытесняются во временный файл). Проход 2 перечитывает файл и пишет цветной отчёт через SXSSF.

Проход 1: данные карта исходов Проход 2: отчёт

Установка

// Gradle (Kotlin DSL)
implementation("org.novgorodtsev.excelimport:excel-import-core:0.1.0")
// Spring Boot starter (core pulled transitively)
implementation("org.novgorodtsev.excelimport:excel-import-spring-boot-starter:0.1.0")
<!-- Maven -->
<dependency>
<groupId>org.novgorodtsev.excelimport</groupId>
<artifactId>excel-import-core</artifactId>
<version>0.1.0</version>
</dependency>
<dependency>
<groupId>org.novgorodtsev.excelimport</groupId>
<artifactId>excel-import-spring-boot-starter</artifactId>
<version>0.1.0</version>
</dependency>

Быстрый старт

@ExcelSheet(name = "Employees", headerRow = 0)
@TargetTable(schema = "hr", name = "employee")
public class EmployeeRow {
@ExcelColumn(header = "Personnel No") @Column("personnel_no") @NotNull
private Long personnelNo;
@ExcelColumn(header = "Full Name") @Column("full_name") @NotBlank @Size(max = 200)
private String fullName;
@ExcelColumn(header = "Hired At", formats = {"dd.MM.yyyy", "yyyy-MM-dd"})
@Column("hired_at") @PastOrPresent
private LocalDate hiredAt;
@ExcelColumn(header = "Salary") @Column("salary") @DecimalMin("0.00")
private BigDecimal salary;
@ExcelColumn(header = "Department", required = false) @Column("department")
private String department;
// getters/setters
}
ImportConfig config = ImportConfig.builder()
.batchSize(1_000)
.conflictStrategy(ConflictStrategy.doNothing("personnel_no"))
.reportPath(Path.of("/var/reports/employees-report.xlsx"))
.maxErrors(10_000)
.build();
ExcelImporter<EmployeeRow> importer = ExcelImporter.builder(EmployeeRow.class)
.dataSource(dataSource)
.config(config)
.build();
ImportReport report = importer.importFile(Path.of("employees.xlsx"));