공공부하자개발 · 영어 학습 노트
자바
실무 배치대용량 데이터 처리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정리‹ 이전다음 ›

5. 연습 문제

문제 1

BatchUtils에 static <T> T withRetryAndTiming(String label, int maxAttempts, Supplier<T> action)을 추가하세요. timed와 retry를 겹쳐서 재시도 전체 시간이 측정되게 합니다.

그 다음 Runnable을 받는 io 오버로딩 static void io(IORunnable action)(IORunnable은 void run() throws IOException)을 추가해 w.write(...)를 return null 없이 쓸 수 있게 하세요.

정답 보기
java
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

public class Q1 {
    static <T> T withRetryAndTiming(String label, int maxAttempts, Supplier<T> action) {
        return BatchUtils.timed(label, () -> BatchUtils.retry(maxAttempts, action));   // timed가 바깥
    }

    @FunctionalInterface
    interface IORunnable { void run() throws IOException; }

    static void io(IORunnable action) {
        try { action.run(); }
        catch (IOException e) { throw new UncheckedIOException(e); }
    }

    public static void main(String[] args) throws IOException {
        AtomicInteger n = new AtomicInteger();
        String r = withRetryAndTiming("connect", 3, () -> {
            if (n.incrementAndGet() < 3) throw new RuntimeException("timeout");
            return "ok";
        });
        // 출력:   재시도 1/3: timeout
        // 출력:   재시도 2/3: timeout
        // 출력:   [connect] 0 ms
        System.out.println(r);                                   // 출력: ok

        Path tmp = Files.createTempFile("q1", ".txt");
        BatchUtils.withWriter(tmp, w -> io(() -> { w.write("no return null"); w.newLine(); }));   // return null 불필요
        String line = BatchUtils.withReader(tmp, br -> BatchUtils.io(br::readLine));
        System.out.println(line);                                // 출력: no return null
        Files.delete(tmp);
    }
}

문제 2

JobParams에 LocalDate endDate(선택, 기본값은 date와 같음)를 추가하고, JobRunner에 runRange(String[] args)를 추가하세요.

date부터 endDate까지 BatchUtils.dateRange로 일자 목록을 만들어 하루씩 잡을 실행하고, 각 리포트의 summary()를 출력한 뒤 오류가 하나라도 있으면 true를 반환합니다. endDate < date면 IllegalArgumentException.

정답 보기
java
import java.time.LocalDate;
import java.util.List;
import java.util.function.Function;

public class Q2 {
    // JobParams 수정본 (필드 추가 + compact 생성자 검증)
    record JobParams2(String job, LocalDate date, LocalDate endDate, int chunkSize) {
        JobParams2 {
            if (job == null || job.isBlank()) throw new IllegalArgumentException("--job 필수");
            if (endDate.isBefore(date)) throw new IllegalArgumentException("--end는 --date 이후여야 함");
        }
        static JobParams2 of(String job, String ymd, String endYmd, int chunk) {
            LocalDate d = BatchUtils.parseYmd(ymd);
            LocalDate e = endYmd == null ? d : BatchUtils.parseYmd(endYmd);    // 기본값 = date
            return new JobParams2(job, d, e, chunk);
        }
    }

    static boolean runRange(JobParams2 p, Function<LocalDate, BatchReport> job) {
        boolean anyError = false;
        for (LocalDate d : BatchUtils.dateRange(p.date(), p.endDate())) {
            BatchReport r = job.apply(d);
            System.out.println(r.summary());
            anyError |= r.hasErrors();
        }
        return anyError;
    }

    public static void main(String[] args) {
        JobParams2 p = JobParams2.of("daily", "20240315", "20240317", 100);
        boolean failed = runRange(p, d ->
                d.getDayOfMonth() == 16 ? BatchReport.of("daily-" + d).error("입력 없음") : BatchReport.of("daily-" + d).read(10).written(10));
        // 출력: [daily-2024-03-15] SUCCESS read=10 written=10 skipped=0 errors=0
        // 출력: [daily-2024-03-16] COMPLETED_WITH_ERRORS read=0 written=0 skipped=0 errors=1
        // 출력: [daily-2024-03-17] SUCCESS read=10 written=10 skipped=0 errors=0
        System.out.println("failed=" + failed);                  // 출력: failed=true

        try {
            JobParams2.of("daily", "20240317", "20240315", 100);
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());                  // 출력: --end는 --date 이후여야 함
        }
    }
}

문제 3

static int copyFiltered(Path in, Path out, int chunkSize, Predicate<String> keep)를 작성하세요. forEachLineChunk로 in을 읽고, 각 청크에서 keep을 통과한 줄만 out에 원자적으로 씁니다(전체가 끝난 뒤 한 번에 writeAtomically).

유지된 줄 수를 반환합니다. 진행률은 progress로 청크마다 출력하되 총 청크 수를 미리 알 수 없으므로 "청크 N 처리" 형식으로 바꾼 Consumer<Integer>를 직접 만드세요.

정답 보기
java
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

public class Q3 {
    static int copyFiltered(Path in, Path out, int chunkSize, Predicate<String> keep) {
        List<String> kept = new ArrayList<>();
        int[] chunkNo = {0};
        Consumer<Integer> progress = n -> System.out.println("  청크 " + n + " 처리");
        BatchUtils.forEachLineChunk(in, chunkSize, lines -> {
            for (String l : lines) if (keep.test(l)) kept.add(l);
            progress.accept(++chunkNo[0]);
        });
        BatchUtils.writeAtomically(out, w -> kept.forEach(l -> BatchUtils.io(() -> { w.write(l); w.newLine(); return null; })));
        return kept.size();
    }

    public static void main(String[] args) throws Exception {
        Path dir = Files.createTempDirectory("q3");
        Path in = dir.resolve("in.txt"), out = dir.resolve("out.txt");
        BatchUtils.withWriter(in, w -> BatchUtils.io(() -> {
            for (int i = 1; i <= 7; i++) { w.write("row" + i + (i % 2 == 0 ? ",ok" : ",skip")); w.newLine(); }
            return null;
        }));
        int n = copyFiltered(in, out, 3, l -> l.endsWith(",ok"));
        // 출력:   청크 1 처리
        // 출력:   청크 2 처리
        // 출력:   청크 3 처리
        System.out.println(n);                                                          // 출력: 3
        List<String> result = BatchUtils.withReader(out, r -> r.lines().toList());
        System.out.println(result);                                                     // 출력: [row2,ok, row4,ok, row6,ok]
        System.out.println(Files.exists(dir.resolve("out.txt.tmp")));                   // 출력: false
        Files.delete(in); Files.delete(out); Files.delete(dir);
    }
}
연습 문제
  • 문제 1
  • 문제 2
  • 문제 3
이전 섹션4 자주 하는 실수 (Tip)5 / 6다음 섹션6 정리