
Todo 프로젝트를 만들면서 도대체 Optional<>은 왜쓰는거지? JPA에서는 그냥 이런식으로 써야만 하는건가? 라고 의문점이 많았다
List<Todo> findTodoListById(Long id);
Todo findTodoById(Long id);
이렇게 DB를 조회하면 안되는걸까? 작동은 잘되는데 말이다.
근데 이 옵셔널과 디폴트의 조합이 너무나 꿀템이었던 건에 대하여...
나는 그냥 감자였던건에 대하여....
왜인지 함께 알아보자!!
스프링에서 JPA를 사용할 때, repository는 데이터베이스와 상호작용하는 핵심 계층이다.
JpaRepository를 extend하면 다양한 기본 메서드를 제공하지만, 이를 어떻게 활용할지에 따라 코드의 안정성과 유지보수성이 달라진다!!
예제를 통해 기본적으로 구성한 방식과 에러처리를 잘할 수 있는 방법으로 개선한 방법을 알아보자
public interface TodoRepository extends JpaRepository<Todo, Long> {
List<Todo> findTodoListById(Long id);
Todo findTodoById(Long id);
}
기본적으로는 JpaRepository를 확장해 레포지토리를 사용하게 된다.
하지만 위의 코드처럼 사용하게 되면,,
findTodoById(Long id) 메서드는 존재하지 않는 ID를 조회하면 null을 반환한다.
→ null 체크를 따로 해야 하고, NullPointerException이 생길 수도 있다
하지만 이러한 오류를 Optional<>을 통해 개선할 수 있다.
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findMemberByName(String name);
}
위 코드에서처럼 Optional<Member>로 반환하면,
호출하는 쪽에서 isPresent(), orElse(), orElseThrow()등의 메서드를 활용하여 명시적으로 null 처리를 할 수 있다!
Optional<>과 더불어 default를 함께 사용하게 되면 레포지토리 내부에서 공통적인 예외 처리를 할 수 있다
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findMemberByName(String name);
default Member findMemberByNameOrElseThrow(String name) {
return findMemberByName(name)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "존재하지 않는 회원입니다"));
}
}
Optional<Member>를 반환하는 findMemberByName(String name) 메서드를 감싸 orElseThrow()를 활용해 예외를 던진다.
➡️ controller 혹은 service 계층에서 불필요한 null 체크 없이 바로 사용할 수 있게 된다!!
여러가지 예외 던지기 방법이 있지만 orElseThrow가 가장 무난하게 사용 가능하다😉
두 방법을 함께 사용하면 시너지가 엄청나다!
1. 코드의 간결성
default 메서드를 활용하면 예외 처리를 한 곳에서 통합적으로 관리할 수 있다.
이후 사용할 클래스(서비스 계층)에서 불필요한 null 체크 코드가 줄어들어 코드가 간결해지고 가독성도 좋아진다!!
2. null 안전성 확보
Optional<>을 사용하면 null을 직접 다루지 않아도 된다.
orElseThrow()를 통해 명시적으로 예외를 던져버릴 수 있다 크크
3. 유지보수성 향상
예외 처리를 중앙화하여 코드의 중복을 줄이고 가독성을 높일 수 있다.
새롭게 조회하는 메서드를 추가해도, 동일하게 예외처리하면 되니까 아주아주 편하게 확장 가능하다ㅏ~~~!!!!
JPA를 사용하며 가장 편리하다고 느꼈던 부분이 Repository를 다루는 부분인데, 이렇게 예외처리까지 같이 해주니 가독성이 너무너무 좋아졌다!