JPA를 사용하다 보면 findById() 같은 조회 메서드의 반환 타입이 Optional<T>인 것을 자주 보게 된다.
처음 보면
“그냥 엔티티를 바로 주면 되는 것 아닌가?”
라는 생각이 들 수 있다.
하지만 조회라는 작업은 본질적으로 결과가 있을 수도 있고, 없을 수도 있는 작업이다.
바로 이 점 때문에 Optional이 필요하다.
조회는 항상 성공하는 것이 아니다
데이터베이스 조회는 개발자가 기대한 대로 항상 값을 찾는 것이 아니다.
예를 들어 다음과 같은 경우를 생각할 수 있다.
- 사용자가 존재하지 않는 게시글 번호를 요청한 경우
- 이미 삭제된 데이터를 조회한 경우
- 잘못된 파라미터가 전달된 경우
- 아직 저장되지 않은 식별자로 조회한 경우
이런 상황에서는 조회 결과가 없을 수밖에 없다.
즉, 조회 메서드는 구조적으로 다음 두 가지 가능성을 가진다.
- 값을 찾았다
- 값을 찾지 못했다
문제는 이 “값이 없을 수 있음”을 코드에서 어떻게 표현하느냐이다.
예전 방식: null로 표현
과거에는 조회 결과가 없으면 null을 반환하는 방식이 흔했다.
이 방식은 단순해 보이지만, 실제로는 많은 문제를 만든다.
1. null 체크를 깜빡하기 쉽다
조회한 객체를 바로 사용하는 순간 문제가 생긴다.
Board board = boardRepository.findSomething(...);
System.out.println(board.getTitle());
즉, 문제는 “값이 없다”는 사실이 아니라
그 사실을 개발자가 놓치기 쉽다는 데 있다.
2. 코드만 보고는 위험한 지점을 알기 어렵다
반환 타입이 그냥 Board라면, 이 값이 항상 존재하는 값인지, 아니면 null일 수도 있는 값인지 메서드 시그니처만 보고는 알기 어렵다.
이 말은 곧, 위험한 값인데도 안전한 값처럼 보인다는 뜻이다.
Optional은 “값이 없을 수도 있다”를 타입으로 표현한다
Optional<T>는 단순한 포장 객체가 아니다.
이 타입의 핵심 목적은 다음이다.
이 값은 있을 수도 있고, 없을 수도 있다.
즉, Optional<Board>는
“Board가 반드시 있다”가 아니라
“Board가 존재할 수도 있고, 비어 있을 수도 있다”는 의미를 갖는다.
이렇게 하면 조회 결과의 불확실성이 코드에 명확하게 드러난다.
Optional<Board> result = boardRepository.findById(bno);
이 한 줄은 단순한 문법이 아니라 다음 의미를 가진다.
- 조회를 시도했다
- 결과가 있을 수도 있다
- 없을 수도 있다
- 따라서 바로 쓰지 말고 확인하고 다뤄야 한다
왜 이것이 중요한가
Optional을 사용하는 가장 큰 이유는
조회 결과가 없을 수 있다는 사실을 개발자가 무시하지 못하게 만들기 위해서이다.
즉, 단순히 null을 감추는 것이 아니라
“이 값은 안전하게 처리해야 한다”는 신호를 타입 자체로 주는 것이다.
장점 1. API의 의도가 명확해진다
메서드 시그니처만 봐도 알 수 있다.
Optional<Board> findById(Long id)
이 메서드는 다음을 명확히 전달한다.
- 조회 결과가 비어 있을 수 있다
- 호출하는 쪽에서 반드시 그 가능성을 고려해야 한다
반환 타입이 그냥 Board였다면 이런 의도가 드러나지 않는다.
장점 2. null 실수를 줄일 수 있다
Optional은 개발자에게 결과 처리 방식을 선택하게 한다.
- 값이 있으면 사용한다
- 값이 없으면 예외를 던진다
- 값이 없으면 기본값을 사용한다
- 값이 있을 때만 특정 로직을 수행한다
즉, “없을 때 어떻게 할 것인가”를 코드 작성 시점에 고민하게 만든다.
장점 3. 예외 상황을 더 명확하게 처리할 수 있다
조회 결과가 없을 때의 대응은 상황마다 다르다.
예를 들어,
- 상세 조회에서는 예외를 던질 수 있다
- 수정 화면에서는 존재하지 않는 데이터라고 안내할 수 있다
- 선택적 조회에서는 그냥 빈 값으로 넘길 수 있다
Optional은 이런 흐름을 자연스럽게 표현하기 좋다.
JPA에서 Optional이 특히 잘 맞는 이유
JPA에서 자주 하는 작업 중 하나가 식별자 기반 조회이다.
그런데 식별자로 조회한다고 해서 항상 데이터가 존재하는 것은 아니다.
예를 들어 게시글 번호 100L로 조회를 요청했더라도, 실제 DB에는 그 데이터가 없을 수 있다.
이 상황에서 JPA가 반환 타입을 Optional<T>로 제공하는 것은 매우 자연스럽다.
왜냐하면 이것은 예외적인 상황이 아니라 조회라는 작업 자체가 원래 갖고 있는 속성이기 때문이다.
즉, JPA는 다음처럼 말하고 있는 셈이다.
조회 결과가 없을 수도 있으니, 호출하는 쪽에서 그 가능성을 고려해서 처리하라.
Optional이 유도하는 올바른 처리 방식
Optional을 받으면 개발자는 결과를 바로 꺼내 쓰기보다,
먼저 “없을 수도 있다”는 전제에서 처리하게 된다.
대표적으로 다음과 같은 방식이 있다.
1. 없으면 예외를 던진다
상세 조회, 수정, 삭제 같은 작업에서는 데이터가 반드시 있어야 하는 경우가 많다.
Board board = boardRepository.findById(bno)
.orElseThrow(() -> new RuntimeException("게시글이 존재하지 않습니다."));
이 방식은
“없으면 조용히 넘어가지 말고 명확하게 실패하라”
는 의도를 표현한다.
2. 없으면 기본값을 사용한다
경우에 따라서는 조회 결과가 없어도 기본 객체나 대체 값을 사용할 수 있다.
Board board = boardRepository.findById(bno)
.orElse(new Board());
3. 값이 있을 때만 동작한다
조회 결과가 있으면 처리하고, 없으면 아무 작업도 하지 않는 방식도 가능하다.
boardRepository.findById(bno)
.ifPresent(board -> {
System.out.println(board.getTitle());
});
중요한 점: Optional은 “무조건 쓰는 것”이 아니라 “조회 결과가 비어 있을 수 있을 때 의미가 있다”
여기서 오해하면 안 되는 부분이 있다.
Optional은 모든 곳에 무조건 붙이는 도구가 아니다.
특히 JPA에서 조회 결과가 없을 수 있는 상황을 표현할 때 의미가 크다.
즉, 핵심은 다음이다.
- 조회 결과가 반드시 있다고 보장할 수 없다면 Optional이 적절하다
- 결과가 없을 수 있다는 사실을 API 수준에서 드러내고 싶을 때 Optional이 적절하다
반대로 말하면,
항상 값이 있어야 하는 내부 로직이나 필드까지 무조건 Optional로 감싸는 것은 바람직하지 않다.
Optional을 사용하는 목적은 문법이 아니라 설계에 있다
많은 초보 개발자는 Optional을 그냥
“null 대신 쓰는 문법”
정도로 이해한다.
하지만 더 중요한 것은 문법보다 설계 의도이다.
Optional은 단순한 포장지가 아니라, 다음을 코드에 드러내는 도구이다.
- 이 조회는 실패할 수 있다
- 결과가 없을 수도 있다
- 호출하는 쪽은 그 가능성을 반드시 처리해야 한다
즉, Optional은
조회 결과의 불확실성을 숨기지 않고 드러내는 방식이다.
이 점에서 Optional은 JPA의 조회 메서드와 잘 어울린다.
정리
JPA에서 조회 결과를 Optional로 받는 이유는 단순하다.
조회는 항상 값을 찾는 작업이 아니기 때문이다.
데이터가 없을 수도 있는데, 그 상황을 그냥 null로 넘겨버리면
개발자는 그 위험을 놓치기 쉽고, 결국 NullPointerException 같은 문제로 이어질 수 있다.
반면 Optional은 다음을 명확하게 보여준다.
- 조회 결과가 없을 수 있다
- 그 가능성을 호출하는 쪽에서 처리해야 한다
- null을 무심코 사용하는 실수를 줄일 수 있다
즉, JPA에서 Optional을 쓰는 이유는
편의 때문이 아니라 조회 결과의 불확실성을 더 안전하고 명확하게 다루기 위해서이다.
'Backend > Spring' 카테고리의 다른 글
| 스프링 빈(Bean) 등록 방식 정리 (0) | 2026.03.31 |
|---|