상황
서비스 로직에서 데이터를 조회할 때 findById()를 사용하면
반환 타입이 Optional이기 때문에 보통 다음과 같이 orElseThrow()를 사용해 예외 처리를 하게 된다.
private Menu getMenuEntity(Long menuId) {
return menuRepository.findById(menuId)
.orElseThrow(() -> new MenuException(ErrorCode.MENU_NOT_FOUND));
}
이 방식은 Service 계층에서 엔티티를 조회하고,
존재하지 않을 경우 예외를 발생시키는 구조이다.
현재 프로젝트의 MenuRepository는 다음과 같이 정의되어 있다.
public interface MenuRepository extends JpaRepository<Menu, Long> {
List<Menu> findAllByStoreIdOrderByCreatedAtDesc(Long storeId);
boolean existsByStoreIdAndName(Long storeId, String name);
}
하지만 Repository에서도 default 메서드를 활용하면
Optional 처리와 예외 처리를 함께 할 수 있다는 점을 알게 되었다.
예를 들어 다음과 같은 방식이다.
public interface MenuRepository extends JpaRepository<Menu, Long> {
default Menu findByIdOrThrow(Long menuId) {
return findById(menuId)
.orElseThrow(() -> new MenuException(ErrorCode.MENU_NOT_FOUND));
}
}
이렇게 구현하면 Service에서는 다음과 같이 더 간결하게 사용할 수 있다.
Menu menu = menuRepository.findByIdOrThrow(menuId);
이 두 방식 중 어떤 구조가 더 적절한지 고민하게 되었다.
두 방식의 차이
1️⃣ Service에서 예외 처리
menuRepository.findById(menuId)
.orElseThrow(() -> new MenuException(ErrorCode.MENU_NOT_FOUND));
특징
- Repository는 데이터 조회만 담당
- 예외 처리와 비즈니스 로직은 Service에서 처리
즉, 계층 간 책임이 명확하게 분리되는 구조이다.
일반적으로 Spring 프로젝트에서 많이 사용하는 방식이다.
2️⃣ Repository default 메서드에서 처리
default Menu findByIdOrThrow(Long menuId)
특징
- 조회와 예외 처리를 Repository에서 함께 수행
- Service 코드가 더 간결해질 수 있음
하지만 Repository에 비즈니스 로직이 일부 섞일 수 있다는 단점이 있다.
Repository는 보통 데이터 접근 계층이기 때문에
가능한 한 조회 로직만 담당하도록 두는 것이 일반적인 설계 방식이다.
선택한 방식
이번 프로젝트에서는
Repository는 데이터 접근만 담당하고
예외 처리와 비즈니스 로직은 Service에서 처리하는 구조를 선택했다.
그래서 Service 내부에 다음과 같이 엔티티 조회 메서드를 따로 분리하여 사용했다.
private Menu getMenuEntity(Long menuId) {
return menuRepository.findById(menuId)
.orElseThrow(() -> new MenuException(ErrorCode.MENU_NOT_FOUND));
}
이렇게 구현하면
- 서비스 코드 가독성이 좋아지고
- 예외 처리 로직을 한 곳에서 관리할 수 있으며
- 계층 간 역할도 명확하게 유지할 수 있다.
또한 Service에서 엔티티 조회 메서드를 따로 두면
다른 서비스 로직에서도 재사용하기 쉽다는 장점이 있다.
정리
Spring Data JPA에서 Optional 처리 방식은 크게 두 가지가 있다.
1️⃣ Service에서 orElseThrow() 처리
2️⃣ Repository에서 default 메서드로 처리
하지만 일반적으로는 다음과 같은 구조를 사용하는 것이 더 명확하다.
Repository → 데이터 조회
Service → 예외 처리 및 비즈니스 로직
이번 설계를 통해 계층 간 책임을 분리하는 것이 중요하다는 점을 다시 한 번 느낄 수 있었다 !!
'트러블슈팅' 카테고리의 다른 글
| 조회 성능 개선 1단계 - Pageable 적용 (0) | 2026.03.17 |
|---|---|
| 조회 API 응답 속도가 느린 이유 분석 (Postman 테스트 기반) (0) | 2026.03.17 |
| @Transactional(readOnly = true)를 조회 로직에서 사용하는 이유 (0) | 2026.03.11 |
| JPA 연관관계 설계 트러블슈팅 (0) | 2026.03.06 |
| GitHub Issue Template이 표시되지 않는 문제 해결 (front-matter 설정) (0) | 2026.03.06 |