Apache POI와 씨름을 그만—Java용 한 줄 Excel 라이브러리 Sheetz
Source: Dev.to
If you’ve ever processed an Excel file in Java, you know the drill:
- Apache POI를 가져옵니다.
- 컬럼 헤더를 읽기 위해 수십 줄의 보일러플레이트 코드를 작성합니다.
- 누군가 셀을 비워두어 행 14에서
NullPointerException을 디버깅하는 데 한 시간을 소비합니다.
Sound familiar?
Sheetz is an open‑source Java library that collapses all of that pain into a single line of code:
List products = Sheetz.read("products.xlsx", Product.class);
No boiler‑plate. No ceremony. Just data.
왜 Sheetz인가?
Apache POI는 강력하지만, 개발자 편의성을 위해 설계된 것은 아닙니다.
EasyExcel, FastExcel, Poiji가 더 나은 편이지만, 여전히 꽤 많은 설정이 필요합니다.
Sheetz는 다른 철학을 취합니다: 기본적으로 설정이 전혀 없고, 필요할 때는 완전한 제어.
기본 제공 기능
| ✅ | Feature |
|---|---|
| 🚀 | 원‑라인 API – Sheetz.read(), Sheetz.write(), 끝 |
| 📊 | 진정한 SAX 스트리밍 – 백만 행 파일을 약 10 MB 메모리로 일정하게 처리 |
| 🔄 | 자동 타입 변환 – 19개의 내장 변환기(날짜, 열거형, BigDecimal, UUID, …) |
| 🧵 | 스레드‑안전 – 동시 사용에 안전 |
| ✅ | 내장 검증 – 행/열 컨텍스트와 함께 상세 오류 보고 |
| 📝 | 어노테이션 매핑 – 사용자 정의 헤더, 필수 필드, 기본값 등을 위한 @Column |
의존성
Maven
io.github.chitralabs.sheetz
sheetz-core
1.0.1
Gradle
implementation 'io.github.chitralabs.sheetz:sheetz-core:1.0.1'
간단한 POJO
public class Product {
public String name;
public Double price;
public Boolean inStock;
public LocalDate releaseDate;
public Product() {} // Required no‑arg constructor
}
읽기 및 쓰기
// Excel 읽기
List products = Sheetz.read("products.xlsx", Product.class);
// CSV 읽기
List productsCsv = Sheetz.read("products.csv", Product.class);
// 모델이 없나요? 맵으로 읽기
List<Map<String, Object>> data = Sheetz.readMaps("products.xlsx");
// Excel 쓰기
Sheetz.write(products, "output.xlsx");
// CSV 쓰기
Sheetz.write(products, "output.csv");
스트리밍 – 상수 메모리
1 백만 행의 파일을 처리하시나요? stream()을 사용하면 파일 크기에 관계없이 메모리 사용량이 약 10 MB 수준으로 유지됩니다.
// 행별로 처리 – 상수 메모리
Sheetz.stream("huge-file.xlsx", Product.class)
.forEach(product -> process(product));
// 배치 처리 – 대량 DB 삽입에 최적
Sheetz.stream("huge-file.xlsx", Product.class)
.batch(1000)
.forEach(batch -> database.bulkInsert(batch));
// 전체 Java Streams 지원
long count = Sheetz.stream("huge-file.xlsx", Product.class)
.stream()
.filter(p -> p.price > 100)
.count();
⚠️ 경험 법칙: 100 K 행 이상의 파일은 항상
stream()을 사용하세요. 백만 행 파일을read()로 모두 로드하면 곧OutOfMemoryError가 발생합니다.
@Column – 유연한 매핑
헤더가 필드 이름과 일치하지 않나요? 열 순서가 엉망인가요? 빈 셀에 대한 기본값이 필요하신가요? @Column이 모든 것을 처리합니다.
public class Product {
@Column("Product Name") // Map to a different header
public String name;
@Column(index = 1) // Map by column index (0‑based)
public Double price;
@Column(required = true) // Fail validation if empty
public String sku;
@Column(defaultValue = "pending") // Default for empty cells
public String status;
@Column(format = "dd/MM/yyyy") // Custom date format
public LocalDate orderDate;
@Column(converter = MoneyConverter.class) // Custom converter
public BigDecimal amount;
@Column(ignore = true) // Skip this field entirely
public String internalId;
@Column(width = 20) // Column width in chars (write‑only)
public String description;
}
풍부한 오류 세부 정보를 포함한 검증
ValidationResult result = Sheetz.validate("products.csv", Product.class);
System.out.println("Valid rows: " + result.validCount());
System.out.println("Errors: " + result.errorCount());
System.out.println("Success rate: " + result.successRate() + "%");
for (ValidationResult.RowError error : result.errors()) {
System.out.println("Row " + error.row() +
", Col '" + error.column() +
"': " + error.message());
}
// Clean rows only
List validProducts = result.validRows();
Fluent Builder – 전체 제어
// Reader Builder
List products = Sheetz.reader(Product.class)
.file("products.xlsx")
.sheet("Inventory") // By name
.headerRow(1) // Header on row 2 (0‑based)
.delimiter(';') // For semicolon‑delimited CSVs
.read();
// Writer Builder
Sheetz.writer(Product.class)
.data(products)
.file("output.xlsx")
.sheet("Products")
.autoSize(true) // Auto‑fit column widths
.freezeHeader(true) // Freeze the header row
.write();
다중 시트 워크북
Sheetz.workbook()
.sheet("Products", products)
.sheet("Employees", employees)
.sheet("Orders", orders)
.write("report.xlsx");
Custom Converter Example
public class MoneyConverter implements Converter {
@Override
public BigDecimal fromCell(Object value, ConvertContext ctx) {
String str = value.toString()
.replace("$", "")
.replace(",", "")
.trim();
return new BigDecimal(str);
}
@Override
public Object toCell(BigDecimal value) {
return "$" + value.setScale(2, RoundingMode.HALF_UP);
}
}
필드별로 어노테이션 사용
@Column(converter = MoneyConverter.class)
public BigDecimal price;
또는 모든 BigDecimal 필드에 대해 전역으로 등록
Sheetz.register(BigDecimal.class, new MoneyConverter());
지원되는 유형 및 예시 값
| 유형 | 예시 값 |
|---|---|
| String | Any text |
| Integer, Long, Double, Float | 42, 3.14 |
| BigDecimal, BigInteger | 99.99, 999999999 |
| Boolean | true, yes, y, 1, on (대소문자 구분 없음) |
| LocalDate, LocalDateTime | 2024-01-15, 2024-01-15 10:30:00 |
| ZonedDateTime, Instant | 2024-01-15T10:30:00Z |
| UUID | 550e8400-e29b-41d4-a716-446655440000 |
| Enum | ACTIVE, active (대소문자 구분 없음) |
포맷 지원
| 포맷 | 읽기 | 쓰기 | 스트리밍 |
|---|---|---|---|
.xlsx (Excel 2007+) | ✅ | ✅ | ✅ SAX / SXSSF |
.xls (Excel 97‑2003) | ✅ | ✅ | ❌ |
.csv | ✅ | ✅ | ✅ Buffered |
전역 구성
앱 전체에서 기본 날짜 형식이나 CSV 인코딩을 변경하고 싶으신가요? 단일 구성 객체를 생성하고 전역으로 적용하세요:
SheetzConfig config = SheetzConfig.builder()
.dateFormat("dd/MM/yyyy")
.dateTimeFormat("dd/MM/yyyy HH:mm")
.trimValues(true)
.skipEmptyRows(true)
.streamingThreshold(10_000) // Auto‑stream above this row count
.charset(StandardCharsets.ISO_8859_1)
.build();
Sheetz.configure(config);
시작하기
git clone https://github.com/chitralabs/sheetz.git
cd sheetz
mvn clean install
요구 사항
- Java 11+
- Apache POI 5.2.5
- OpenCSV 5.9
라이선스: Apache 2.0
벤치마크 및 예제
- 벤치마크 –
sheetz-benchmarks저장소에는 Apache POI, EasyExcel, FastExcel, Poiji와 비교한 JMH 성능 비교가 나란히 포함되어 있으며, 전체 소스 코드를 제공하므로 직접 실행할 수 있습니다. - 예제 –
sheetz-examples저장소는 엔드‑투‑엔드 사용 사례를 다루는 8개의 실행 가능한 데모를 제공합니다.
Contribute
Sheetz는 막 출시된 상태이며, 적극적으로 기여자, 이슈, 피드백을 찾고 있습니다. POI 설정에 일주일을 들이지 않고 바로 작동하는 Excel/CSV 처리를 원하셨다면, 한번 사용해 보시고 유용하다고 생각되면 ⭐를 남겨 주세요.
👉 GitHub: https://github.com/chitralabs/sheetz
Discussion
현재 Java에서 Excel 또는 CSV 처리를 위해 주로 사용하는 라이브러리는 무엇인가요? 댓글에 알려 주세요—커뮤니티가 이 문제를 어떻게 해결하고 있는지 궁금합니다!