Spring Batch 이용한 API 호출횟수 집계 - 3. txt 파일로 저장하기

Kim Dong Kyun·2023년 6월 14일
2

Spring Batch

목록 보기
3/6
post-thumbnail

지난 시간 요약

  1. AOP + Spring Batch 를 이용해서 "어떤 영화"가 얼만큼 조회되었는지에 대한 통계를 내기 위해서

  2. AOP - pointcut을 어노테이션으로 설정해서, 필요한 매서드에 어노테이션을 통해서 Argument (실제 호출된 영화의 id) 받아옴, static 한 map에 넣어줌

  3. Job

  • step 1 :static한 Map을 이용해서 횟수 저장 후 로깅
  • step 2 : Map 객체를 초기화(clear)

개요

지난시간에 todo로 남겨두었던 로깅 -> 파일로 저장 바꾸기! 오늘 시도해보자.

지난 시간 링크!


현재 상태

...
@Bean
    public Tasklet testTasklet(){
        return ((contribution, chunkContext) -> {
            ArrayList<Long> ids = new ArrayList<>(AnnotationBasedAOP.map.keySet());
            HashMap<String, Long> names = new HashMap<>();

            List<Movie> movieList = movieRepository.findAllById(ids);

            for (Movie movie : movieList) {
                names.put(movie.getMovieName(), AnnotationBasedAOP.map.get(movie.getId()));
            }

            // TODO: 2023/06/12 이곳에 movieNameMap을 파일/디비로 저장하는 로직이 필요하다.
            //  현재는 로깅하도록 하자

            for (String s : names.keySet()) {
                log.info("영화 이름 :" + s + ", 호출 횟수 :" + names.get(s));
            }
            
            return RepeatStatus.FINISHED;
        });
    }

위와 같이

  • map에서 받아온 ids 들로 새 맵을 만든다. {영화 이름 : 호출 횟수} 순서
  • 그 후 for문을 이용해서 로깅한다.

이제, file로 저장 해보자.


1. path 정해주기

우리가 Job과 step을 정의했던 BatchConfig.java에 "파일이 저장될 path"를 정해줘야 한다. 그리고 이걸 위해서 @Value 어노테이션을 사용해보자!

import org.springframework.beans.factory.annotation.Value;
// 주의!!! Lombok 의 @Value 어노테이션 아님.
...

@Value("${output.file.path}")
    private String outputFilePath;

위와 같이 파일 패스를 저장한다. Mac 에서 다운로드에 파일을 넣으려면

/Users/{여러분의 Mac profile 이름}/Downloads/movie_data.txt

위와 같은 형식으로 하면 된다. 그리고 이것을 application.properties, 혹은 yml 파일에 설정해주자

  • application.properties
output.file.path=/Users/{여러분의 Mac profile 이름}/Downloads/movie_data.txt
  • application.yml
output:
  file:
    path: /Users/{여러분의 Mac profile 이름}/Downloads/movie_data.txt

이렇게 되면 일단 저장될 파일 패스는 설정이 되었다.


2. BufferedWriter 이용해서 파일로 저장하기

BufferedWriter 는 백준을 조금 풀어봤다면 아마 다들 사용 해 보셨을 것.

보통

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

이런 식으로 시스템에서 out되게 하는데, BufferedWriter의 아규먼트에 다른 걸 넣으면, 파일로 저장이 가능하다

BufferedWriter writer = new BufferedWriter(new FileWriter(outputFilePath))
  • 더해서 FileWriter의 아규먼트로는 우리가 설정한 파일의 path 가 들어간다.

그래서, 실제 코드는 아래와 같이 된다.

@Bean
    public Tasklet testTasklet(){
        return ((contribution, chunkContext) -> {
            ArrayList<Long> ids = new ArrayList<>(AnnotationBasedAOP.map.keySet());
            HashMap<String, Long> names = new HashMap<>();

            List<Movie> movieList = movieRepository.findAllById(ids);

            for (Movie movie : movieList) {
                names.put(movie.getMovieName(), AnnotationBasedAOP.map.get(movie.getId()));
            }
            
            try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFilePath))) {
                for (String s : names.keySet()) {
                    String line = "영화 이름: " + s + ", 호출 횟수: " + names.get(s);
                    writer.write(line);
                    writer.newLine();
                }
                log.info("데이터를 파일에 저장했습니다: " + outputFilePath);
            } catch (IOException e) {
                log.error("파일 저장 중 오류가 발생했습니다: " + outputFilePath, e);
            }

            return RepeatStatus.FINISHED;
        });
    }
  • try catch 형식으로 혹시 성공/실패에 대한 로깅

잘 저장이 되는 모습! 더불어서 파일은 계속 덮어씌워지기 때문에 저 값은 분기(나의 경우 1분마다) 바뀐다.

그렇다면, 날짜별로 모아 놓을 수는 없을까? 파일 이름에 날짜 형식을 넣어보자.


3. 날짜 형식을 넣어주기

아까 우리가 설정했던 @Value 경로를 수정해야 한다. ( .txt 파일 경로로 사용했으므로, 날짜에 따라 변경이 가능하게 "폴더"에 대한 디렉토리 주소로 변경해주자 )

  • application.properties
output.file.path=/Users/{여러분의 Mac profile 이름}/Downloads
  • application.yml
output:
  file:
    path: /Users/{여러분의 Mac profile 이름}/Downloads

위와 같이 변경하면 된다.

다음으로는 이제 tasklet 을 만져보자.

			...
	LocalDate currentDate = LocalDate.now();
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy_MM_dd");
    String formattedDate = currentDate.format(formatter);

    String fileName = "movie_" + formattedDate + ".txt";
    String filePath = outputFilePath + "/" + fileName;

    try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
        for (String s : names.keySet()) {
            String line = "영화 이름: " + s + ", 호출 횟수: " + names.get(s);
            writer.write(line);
            writer.newLine();
        }
        log.info("데이터를 파일에 저장했습니다: " + outputFilePath);
    } catch (IOException e) {
        log.error("파일 저장 중 오류가 발생했습니다: " + outputFilePath, e);
    }
           ...

라인별로 설명하면

  • LocalDate.now() 로 저장 시점 시간을 가져온다

  • 하루에 한번씩 로그를 저장할 생각이므로 "yyyy_MM_dd" 의 포맷으로 설정해준다 (2023/06/14) 이런 식으로 저장될것이다.

  • LocalDate.now() 에 위와 같은 포맷으로 포맷을 맞춰주고, 이제 fileName에 formattedDate를 넣어주면 된다.

  • FileWriter의 아규먼트에 우리가 직접 설정한 filePath 설정해주면 끝.


4. 마지막으로, 실행 시간 변경하기

테스트를 위해 매 분마다 실행되던 (0 초마다) 녀석을,

@Scheduled(cron = "0 * * * * *") // 매 0초마다 실행
@Scheduled(cron = "0 0 4 * * *") // 매일 4시 0분 0초에 실행

매일 새벽 4시마다 실행되도록 바꿔준다. 아래는 최종본


다음은 뭐해요?

Docker로 버전 파일 저장해서 띄워놓고, Postman으로 계속 요청 쏴봐서 테스트해봐야지.

더해서 CI/CD도 다시 해볼까? 너무 주먹구구식으로 했던 듯 해서 다시 실습 해 봐도 좋을 듯.

0개의 댓글