공공부하자개발 · 영어 학습 노트
자바
실무 배치대용량 데이터 처리0/6 완료
  • 01배치 프로세스 개념과 아키텍처
  • 02대용량 파일 I/O (NIO, Buffered)
  • 03Chunk 단위 처리와 OOM 방지
  • 04트랜잭션: 커밋과 롤백 시뮬레이션
  • 05Skip과 Retry 로직 구현
  • 06메서드 활용 패턴 (배치 유틸)
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 실무 배치 › 06 / 6

메서드 활용 패턴 (배치 유틸)

섹션 6진행 0 / 6
1왜 배우는가2핵심 원리3패턴 모음4자주 하는 실수 (Tip)5연습 문제6정리‹ 이전다음 ›

2. 핵심 원리

2.1 리소스 헬퍼의 구조 — "열고 닫기"와 "본문"의 분리

java
public static <T> T withReader(Path file, Function<BufferedReader, T> body) {
    try (BufferedReader r = Files.newBufferedReader(file, UTF_8)) {    // 열기
        return body.apply(r);                                          // 본문은 호출자의 람다
    } catch (IOException e) {                                          // 닫기는 try-with-resources
        throw new UncheckedIOException(e);
    }
}
부분 누가 몇 번 작성
열기, 인코딩 지정, 닫기, 예외 변환 헬퍼 1번
무엇을 읽어 무엇을 돌려줄지 잡(람다) 잡마다

호출자는 BufferedReader를 닫는 것을 잊을 수 없습니다. 람다가 끝나면 try-with-resources가 닫습니다. 람다 안에서 예외가 나도 닫힙니다. 이것이 스프링의 JdbcTemplate, TransactionTemplate이 하는 일과 같은 원리(execute-around 패턴)입니다.

2.2 체크 예외와 람다

Function, Consumer는 체크 예외를 던질 수 없습니다. BufferedReader.readLine()은 IOException을 던지므로 람다 안에서 그냥 부르면 컴파일 오류입니다. 두 가지 해결책:

방법 형태 장단점
체크 예외를 던지는 자체 함수형 인터페이스 IOAction<T> { T run() throws IOException; } + io() 래퍼 io(() -> r.readLine()) 로 감쌈. 명시적
헬퍼가 UncheckedIOException으로 변환 withReader 안 catch (IOException e) 호출자는 언체크만 봄. 복구가 필요하면 getCause()

배치에서 IOException은 대개 복구 불가(파일이 없거나 디스크 문제)이므로 언체크로 바꿔 잡 최상위에서 한 번에 처리하는 것이 일반적입니다.

2.3 청크 헬퍼 — 메모리와 경계

text
파일 1,000,000줄, size=1000
┌───────────┐  ┌───────────┐         ┌───────────┐
│ chunk 1   │  │ chunk 2   │   ...   │ chunk 1000│
│ 1~1000    │  │ 1001~2000 │         │ ...       │
└───────────┘  └───────────┘         └───────────┘
   handler        handler               handler

forEachLineChunk는 Files.lines(지연 스트림)에서 한 줄씩 버퍼에 담다가 size가 차면 handler를 부르고 버퍼를 새로 만듭니다. 전체를 List로 올리지 않으므로 파일 크기와 무관하게 메모리는 청크 하나 분량입니다. 마지막 자투리(buf가 비어 있지 않음)를 잊으면 마지막 청크가 유실됩니다. 03 레슨의 원리를 메서드 하나로 캡슐화한 것입니다.

2.4 데코레이터 — 함수를 감싸는 함수

text
timed("job", () -> retry(3, () -> job.run(params)))
  │            │
  │            └ 실패하면 3번까지
  └ 전체 시간 측정

timed, retry, processWithSkip은 원래 작업을 모르고, 원래 작업도 이들을 모릅니다. 겹치는 순서가 의미를 정합니다. timed(retry(...))는 재시도 전체 시간을, retry(timed(...))는 시도별 시간을 잽니다.

2.5 배치 파라미터와 record

args는 String[]이라 순서에 의존하고 타입이 없습니다. --job=daily --date=20240315 형식으로 받아 record로 변환하면 타입(LocalDate, int)이 생기고, compact 생성자에서 검증하며, toString이 로그에 그대로 찍힙니다. 기본값은 파싱 단계에서 채웁니다. 스프링 배치의 JobParameters가 하는 일의 핵심입니다.

2.6 멱등성과 정리 — 언제든 죽어도 안전하게

위험 대응 메서드
쓰다가 죽어 반쪽 파일이 남음 writeAtomically: .tmp에 쓰고 Files.move(ATOMIC_MOVE)
같은 날짜로 두 번 실행되어 결과가 중복 출력 파일 존재 확인 후 스킵
임시 디렉터리·락 파일이 남음 finally + Runtime.addShutdownHook
실패 건이 어디 갔는지 모름 BatchReport.error + 에러 로그 파일 분리

finally는 그 메서드가 정상/예외로 끝날 때 실행되고, shutdown hook은 JVM이 종료될 때(정상 종료, System.exit, SIGTERM) 실행됩니다. kill -9에는 둘 다 무력하므로 "재실행하면 정리되는" 멱등 설계가 최후의 방어선입니다.

핵심 원리
  • 2.1 리소스 헬퍼의 구조 — "열고 닫기"와 "본문"의 분리
  • 2.2 체크 예외와 람다
  • 2.3 청크 헬퍼 — 메모리와 경계
  • 2.4 데코레이터 — 함수를 감싸는 함수
  • 2.5 배치 파라미터와 record
  • 2.6 멱등성과 정리 — 언제든 죽어도 안전하게
이전 섹션1 왜 배우는가2 / 6다음 섹션3 패턴 모음