공공부하자개발 · 영어 학습 노트
자바
실무 확장Excel · 파일 업로드 · DB 연동0/22 완료
  • 01Excel(XLSX) 구조와 순수 JDK로 읽기/쓰기
  • 02Apache POI로 Excel 업로드/다운로드
  • 03파일 업로드/다운로드 서버 (HttpServer)
  • 04JDBC 기초와 트랜잭션 (H2)
  • 05MyBatis 어노테이션 매퍼로 쿼리 연동
  • 06MyBatis XML 매퍼 · Oracle 방언 · PageHelper · Spring Boot
  • 07REST API 서버와 JSON
  • 08Vue 3 SPA 와 Java 서버 연동
  • 09@Scheduled 운영
  • 10로깅 실무: 레벨·계층, MDC 추적, 예외·성능, 마스킹, 롤링, JSON 로그
  • 11외부 API 연동
  • 12테스트 실무
  • 13암호화·개인정보 보호
  • 14인코딩·한글 실무
  • 15@Transactional 심화
  • 16긴 작업 비동기 처리와 진행률
  • 17SFTP·FTP 파일 연계
  • 18로컬 캐시와 @Cacheable
  • 19메일·알림 발송
  • 20웹 보안 체크리스트
  • 21빌드 도구와 폐쇄망 의존성 반입
  • 22성능 측정: p50·p95·p99, 측정 계층, JFR, JMH 함정, 자체 부하 테스트, 병목 순위
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 실무 확장 › 01 / 22

Excel(XLSX) 구조와 순수 JDK로 읽기/쓰기

섹션 7진행 0 / 22
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

5. 자주 하는 실수 (Tip)

❌ 실수 1: 날짜를 문자열로 쓴다

java
w.row("김철수", "2026-01-05");                     // ❌ 문자열 셀. Excel 에서 정렬·필터·수식 불가
w.row("김철수", LocalDate.of(2026, 1, 5));        // ✅ 숫자 + 날짜 스타일. Excel 이 날짜로 인식

문자열 "2026-01-05" 는 Excel 에서 왼쪽 정렬된 텍스트로 보이고 =B2+30 같은 수식이 #VALUE! 가 됩니다. 반대로 읽을 때 사용자가 날짜를 텍스트로 입력했다면 Cell.date() 가 false 이므로 검증 오류로 잡아야 합니다(예제 5 의 7행).

❌ 실수 2: Double 을 toString 으로 쓴다

java
sb.append("<v>").append(12345678.0).append("</v>");                     // ❌ "1.2345678E7" → Excel 이 열긴 하지만 일부 도구가 거부
sb.append("<v>").append(BigDecimal.valueOf(12345678.0).toPlainString()); // ✅ "12345678"

Double.toString() 은 10^7 이상이나 10^-3 미만에서 지수 표기를 씁니다. 금액 계산은 처음부터 long(원 단위) 또는 BigDecimal 로 하고 double 을 셀에 쓰지 않는 것이 근본 해결입니다.

❌ 실수 3: 빈 행이 파일에 있다고 가정한다

java
int expected = 1;
r.forEachRow(0, row -> {
    if (row.num() != expected++) throw new IllegalStateException("행 누락");   // ❌ 사용자가 3행을 지웠으면 r=2 다음 r=4
});
// ✅ row.num() 을 그대로 사용자에게 보여 주는 행 번호로 쓴다. 연속성을 가정하지 않는다

Excel 에서 행을 지우면(내용만 지우면) <row> 자체가 저장되지 않습니다. 서식이 남은 경우에는 <row r="3"><c r="A3" s="1"/></row> 처럼 값 없는 셀만 있는 행이 오므로 row.isBlank() 로 걸러야 합니다.

❌ 실수 4: 열 위치를 cells 인덱스로 읽는다

java
String phone = row.cells().get(2).value();      // ❌ B 열이 비어 있으면 cells 는 [A, C, D] 라 C 가 1번에 온다
String phone = row.get(2);                       // ✅ 열 인덱스로 찾는다. 없으면 ""

cells 는 파일에 있는 셀만 담고, 빈 셀은 <c> 가 없습니다. 반드시 Cell.col() 기준으로 찾아야 합니다. Row.get(col), Row.cell(col), Row.values() 가 그 일을 합니다.

❌ 실수 5: XML 이스케이프를 빼먹는다

java
"<t>" + name + "</t>"                       // ❌ 이름이 "A&B <주식회사>" 면 XML 이 깨져 Excel 이 "손상" 판정
"<t>" + XlsxParts.escape(name) + "</t>"     // ✅ & < > " 치환 + 제어 문자(0x00~0x1F, 탭·개행 제외) 제거

특히 DB 에서 온 문자열에 \u0000 같은 제어 문자가 섞여 있으면 이스케이프해도 Excel 이 거부합니다. escape() 가 제어 문자를 제거하는 이유입니다. 시트 이름도 규칙이 있습니다(1~31자, []:*?/\ 금지). 작성기 생성자가 검사합니다.

❌ 실수 6: BufferedWriter 를 close 해서 zip 을 통째로 닫는다

java
try (var out = new BufferedWriter(new OutputStreamWriter(zip))) { … }   // ❌ out.close() → zip.close(). 뒤에 부품을 못 붙인다
out.flush(); zip.closeEntry();                                           // ✅ 시트 엔트리만 끝내고 다음 엔트리로

Writer.close() 는 감싼 스트림을 연쇄로 닫습니다. ZipOutputStream 위에 Writer 를 올렸을 때는 엔트리 전환 시 flush() 만 하고, 최종 close() 는 zip 에 한 번만 합니다.

❌ 실수 7: 업로드 XML 을 기본 설정 파서로 읽는다

java
XMLInputFactory.newFactory().createXMLStreamReader(in);   // ❌ DTD·외부 엔티티 허용 → XXE 로 서버 파일 유출
xmlFactory().createXMLStreamReader(in);                    // ✅ SUPPORT_DTD=false, IS_SUPPORTING_EXTERNAL_ENTITIES=false

XLSX 는 정상적으로는 DTD 를 쓰지 않으므로 끄는 것에 부작용이 없습니다. 함께 zip 엔트리 크기 상한과 행 수 상한을 두어 zip 폭탄과 무한 행을 막습니다.

자주 하는 실수 (Tip)
  • ❌ 실수 1: 날짜를 문자열로 쓴다
  • ❌ 실수 2: Double 을 toString 으로 쓴다
  • ❌ 실수 3: 빈 행이 파일에 있다고 가정한다
  • ❌ 실수 4: 열 위치를 cells 인덱스로 읽는다
  • ❌ 실수 5: XML 이스케이프를 빼먹는다
  • ❌ 실수 6: BufferedWriter 를 close 해서 zip 을 통째로 닫는다
  • ❌ 실수 7: 업로드 XML 을 기본 설정 파서로 읽는다
이전 섹션4 응용 변형 예제5 / 7다음 섹션6 연습 문제