🔍 findAll()에서 Optional<List<T>>를 쓰지 않아도 되는 이유
Spring 또는 MyBatis 기반 프로젝트를 개발하다 보면 Repository에서 데이터를 조회할 때 어떤 반환 타입을 써야 할지 고민할 수 있습니다. 특히, Optional<List<T>>를 사용할까? List<T>로 충분할까? 하는 문제는 많은 개발자들이 한 번쯤 마주하는 주제입니다.
이번 글에서는 왜 findAll() 같은 목록 조회 메서드에서는 Optional<List<T>>를 굳이 쓰지 않아도 되는지를 설명해 보겠습니다.
✅ 단일 조회와 다중 조회의 차이
목적 | 메서드 예시 | 변환타입 |
단일 조회 | findById(Long id) | Optional<User> |
다중 조회 | findAll() | List<User> |
단일 조회에서는 해당 ID에 해당하는 데이터가 존재하지 않을 수 있기 때문에, Optional<T>를 사용하여 null-safe하게 처리하는 것이 좋습니다.
하지만 findAll()과 같은 다중 조회는 성격이 다릅니다.
❓ findAll()은 Optional<List<T>>를 써야 할까?
정답은 "아니요"입니다.
그 이유는 다음과 같습니다:
1. List 자체가 "없음"을 표현할 수 있다
Java의 List는 결과가 없을 경우 null이 아닌 빈 리스트([]) 를 반환하는 것이 일반적이며 자연스럽습니다.
List<User> users = userRepository.findAll();
if (users.isEmpty()) {
System.out.println("사용자가 없습니다.");
}
이처럼 List 자체에 .isEmpty() 메서드가 존재해 결과 유무를 쉽게 판별할 수 있습니다.
굳이 Optional<List<User>>처럼 한 번 더 감싸는 건 중복 표현일 뿐입니다.
2. MyBatis나 Spring Data JPA는 List<T>로 빈 리스트를 잘 처리한다
- MyBatis에서 @Select 어노테이션으로 List<T>를 반환하면, 쿼리 결과가 없을 경우 자동으로 빈 리스트가 반환됩니다.
- Spring Data JPA에서도 List<T>를 반환할 경우 결과가 없으면 빈 리스트를 반환합니다.
즉, 널 체크도 필요 없고, Optional도 필요 없습니다.
3. Optional은 단일 값 표현에 적합하다
Optional<T>의 본래 목적은 단일 객체의 존재 여부를 표현하는 것입니다.
이를 리스트에 사용하게 되면 다음과 같이 불필요하게 코드가 복잡해집니다.
Optional<List<User>> usersOpt = userRepository.findAll();
if (usersOpt.isPresent()) {
List<User> users = usersOpt.get();
...
}
이럴 바에는 차라리 그냥 List<User>로 반환하고 .isEmpty()를 체크하는 게 훨씬 간결하고 가독성도 좋습니다.
🚫 잘못된 예시
Optional<List<User>> findAllUsers();
이 방식은 null을 이중으로 감싸는 셈이기 때문에 오히려 코드의 명확성을 떨어뜨립니다.
게다가 Optional은 컬렉션 타입에 권장되지 않습니다. → [Effective Java, Item 55]
✨ 결론
- ✅ Optional<List<T>>는 거의 모든 경우에 과도한 포장이다.
- ✅ List<T>만으로도 결과의 유무를 표현할 수 있다.
- ✅ 빈 리스트는 의미 있는 결과이며, 널과는 다르다.
- ✅ 단일 값 조회에서만 Optional<T>를 사용하자.
💡 Tip: Optional<T>는 "값이 없을 수도 있다"는 걸 나타내기 위해 존재하는 타입이지만, 그 대상은 단일 값일 때만입니다.
리스트는 그 자체로 "값이 없을 수도 있다"는 상태를 표현할 수 있으므로, 불필요하게 Optional로 감싸지 마세요!
📚 참고자료
- Effective Java - Item 55: Return optionals judiciously
- Spring Data JPA 공식 문서
- MyBatis 공식 문서
'웹 개발 > 🍃 SpringBoot' 카테고리의 다른 글
배포시 spring-boot-devtools 비활성화 (1) | 2025.06.13 |
---|---|
Spring Boot | 패키지 구조(계층형 vs 도메인형) (1) | 2025.05.29 |
SpringSecurity + JWT 에서 nginx health-check 요구시 문제 (0) | 2025.04.01 |
Springboot | 생성자관련 어노테이션 (0) | 2025.03.13 |
SpringBoot + MyBatis + Postgres 완전정복 (0) | 2025.03.11 |