본문으로 건너뛰기
개발 머꼬
개발 노트Java
hohyeon.dev14

Optional.orElse에 넣은 기본값 조회가 값이 있을 때도 항상 실행된 이유

  • #Engineering Note
  • #Java

문제 발생

캐시에 값이 있으면 그대로 쓰고 없으면 DB에서 읽도록 짰는데, 캐시가 적중해도 DB 쿼리가 계속 나갔습니다.

User user = cache.find(id).orElse(repository.findById(id));   // 항상 DB를 친다

원인 분석

이건 Optional의 버그가 아니라 자바의 평가 순서 그대로입니다. orElse(...)메서드이고, 메서드를 호출하려면 인자를 먼저 평가해야 합니다. 그래서 repository.findById(id)는 캐시에 값이 있든 없든 항상 실행되고, 그 결과가 orElse에 전달된 다음에야 "값이 있으니 이건 버린다"가 결정됩니다.

자바독의 표현 차이가 이 지점을 드러냅니다.

  • orElse(T other) — "값이 있으면 그 값을, 없으면 other를 반환한다" (이미 만들어진 값)
  • orElseGet(Supplier) — "값이 있으면 그 값을, 없으면 공급 함수가 만든 결과를 반환한다" (필요할 때 호출)

orElseGet은 람다를 받으므로 실제로 필요할 때만 실행됩니다.

해결 방안

  1. 비용이 드는 기본값은 orElseGet으로 바꿉니다.
User user = cache.find(id).orElseGet(() -> repository.findById(id));
  1. 이미 있는 값이면 orElse가 맞습니다. 상수나 이미 계산된 변수라면 람다를 만드는 쪽이 오히려 번잡합니다.
String name = optionalName.orElse("이름 없음");
  1. 예외를 던져야 하면 orElseThrow를 씁니다. 인자 없는 형태는 NoSuchElementException을 던지고, 공급 함수를 주면 원하는 예외를 던집니다. 자바독은 get()보다 orElseThrow()를 선호할 것을 권합니다.
User user = repository.findById(id)
    .orElseThrow(() -> new UserNotFoundException(id));
  1. 부수 효과가 있는 표현식을 orElse에 넣지 않습니다. 로그를 남기거나 카운터를 올리는 코드가 들어 있으면, 값이 있을 때도 실행되어 통계가 틀어집니다. 같은 이유로 ifPresentOrElse가 필요한 자리도 구분해야 합니다.
  2. Optional을 필드나 파라미터로 쓰지 않습니다. 자바독의 API 노트가 명시합니다 — Optional"결과 없음"을 표현해야 하고 null이 오류를 부를 만한 메서드 반환 타입을 위한 것입니다. 그리고 Optional 타입 변수 자체가 null이어서는 안 됩니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.