트러블슈팅

Spring JPA에서 findById() 예외 처리는 Service에서 해야 할까? Repository에서 해야 할까?

sudaruuu 2026. 3. 11. 19:23

상황

서비스 로직에서 데이터를 조회할 때 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 → 예외 처리 및 비즈니스 로직

 

이번 설계를 통해 계층 간 책임을 분리하는 것이 중요하다는 점을 다시 한 번 느낄 수 있었다 !!