하루치 프로젝트에서 오늘 기준으로 이번달 말일까지 조회하는 API가 있는데 잘되던 API가 갑자기 오늘 기준이 아닌 특정 날을 기준으로 나오는 문제가 발생했다.
LocalDate now = LocalDate.now(); // 현재 날짜 가져오기
int year = now.getYear();
int month = now.getMonthValue(); // 현재 월 가져오기
int day = now.getDayOfMonth(); // 현재 일 가져오기
이 코드를 여러 클래스에서 여러 메소드에 공통적으로 사용하고 있었는데 인스턴스 변수로 선언하고있었다.
우리는 단순히 해당 service 클래스의 메소드를 호출할 때, 인스턴스 변수도 같이 호출되어 now변수에 해당 API를 사용하는 시간을 넣어주고 있다고 생각했다.
그러나 인스턴스 변수는 클래스의 인스턴스가 생성될 때 한 번만 초기화되기 때문에, 이후 메소드를 호출하더라도 이미 저장된 값이 사용되었다. 즉, 현재 날짜가 아닌 인스턴스가 생성된 날짜가 계속 사용되고 있던 것이다.
해당 클래스의 인스턴스가 생성될 때 한번만 변수가 초기화된다. 따라서, 이후 메소드를 호출하더라도, 이미 저장된 값이 사용되므로, 현재 날짜가 아닌 인스턴스가 생성된 날짜가 계속 들어가있던것이다.
그렇다면, 인스턴스는 언제 생성되는데?
애플리케이션 시작: Spring 애플리케이션이 시작될 때, 모든 @RestController 및 기타 컴포넌트가 스프링 컨테이너에 의해 스캔된다.
빈 등록: @RestController가 붙은 클래스는 Spring이 빈으로 등록하여 인스턴스를 생성한다. 이때 생성자 주입 방식인 @RequiredArgsConstructor에 의해 service의 인스턴스도 주입된다.
HTTP 요청 처리: 클라이언트가 해당 컨트롤러의 API를 호출할 때, 이미 생성된 service의 인스턴스가 사용된다.
@Transactional
public PushPlusClosing push(BudgetRedistributionRequestDTO.createPushDTO request, Long memberId) {
LocalDate now = LocalDate.now(); // 현재 날짜 가져오기
int localNowYear = now.getYear();
int localNowMonth = now.getMonthValue(); // 현재 월 가져오기
int localNowDay = now.getDayOfMonth(); // 현재 일 가져오기
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new MemberHandler(NO_MEMBER_EXIST)); //영속화
MonthBudget monthBudget = monthBudgetRepository.findByMemberIdAndYearAndMonth(member.getId(), localNowYear, localNowMonth)
.orElseThrow(() -> new MonthBudgetHandler(MONTH_BUDGET_NOT_FOUND));
DayBudget sourceBudget = dayBudgetRepository.findByMonthBudgetAndDay(monthBudget, request.getSourceDay())
.
.
.
}
이런식으로 인스턴스 변수가 아닌 메서드 안에서 선언하는 지역변수를 사용하게 되면 이 메서드를 사용하는 API가 호출될 떄, 그 떄의 시간이 now 변수에 들어가서 우리가 원하는 시간을 가져올 수 있다.