AOP + Spring Batch 를 이용해서 "어떤 영화"가 얼만큼 조회되었는지에 대한 통계를 내기 위해서
AOP - pointcut을 어노테이션으로 설정해서, 필요한 매서드에 어노테이션을 통해서 Argument (실제 호출된 영화의 id) 받아옴, static 한 map에 넣어줌
Job
지난시간에 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;
});
}
위와 같이
이제, file로 저장 해보자.
우리가 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 파일에 설정해주자
output.file.path=/Users/{여러분의 Mac profile 이름}/Downloads/movie_data.txt
output:
file:
path: /Users/{여러분의 Mac profile 이름}/Downloads/movie_data.txt
이렇게 되면 일단 저장될 파일 패스는 설정이 되었다.
BufferedWriter 는 백준을 조금 풀어봤다면 아마 다들 사용 해 보셨을 것.
보통
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
이런 식으로 시스템에서 out되게 하는데, BufferedWriter의 아규먼트에 다른 걸 넣으면, 파일로 저장이 가능하다
BufferedWriter writer = new BufferedWriter(new FileWriter(outputFilePath))
그래서, 실제 코드는 아래와 같이 된다.
@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;
});
}
잘 저장이 되는 모습! 더불어서 파일은 계속 덮어씌워지기 때문에 저 값은 분기(나의 경우 1분마다) 바뀐다.
그렇다면, 날짜별로 모아 놓을 수는 없을까? 파일 이름에 날짜 형식을 넣어보자.
아까 우리가 설정했던 @Value 경로를 수정해야 한다. ( .txt 파일 경로로 사용했으므로, 날짜에 따라 변경이 가능하게 "폴더"에 대한 디렉토리 주소로 변경해주자 )
output.file.path=/Users/{여러분의 Mac profile 이름}/Downloads
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 설정해주면 끝.
테스트를 위해 매 분마다 실행되던 (0 초마다) 녀석을,
@Scheduled(cron = "0 * * * * *") // 매 0초마다 실행
@Scheduled(cron = "0 0 4 * * *") // 매일 4시 0분 0초에 실행
매일 새벽 4시마다 실행되도록 바꿔준다. 아래는 최종본
Docker로 버전 파일 저장해서 띄워놓고, Postman으로 계속 요청 쏴봐서 테스트해봐야지.
더해서 CI/CD도 다시 해볼까? 너무 주먹구구식으로 했던 듯 해서 다시 실습 해 봐도 좋을 듯.