견고한 Repository 계층 설계: @Param의 명시적 사용

프로젝트 규모가 커지고 리팩토링이 잦아질수록, 코드의 안전성(Safety) 은 기능 구현만큼이나 중요해집니다. doeatfit의 Repository 계층 구현 시, 우리는 사소해 보일 수 있는 파라미터 바인딩에도 엄격한 규칙을 적용했습니다.

1. 고려사항: 컴파일러 최적화와 리팩토링 리스크

JPQL(@Query)을 사용하여 커스텀 쿼리를 작성할 때, 파라미터 바인딩(:paramName)이 필요합니다.

// 위험한 코드 예시
@Query("select u from User u where u.email = :email")
User findByEmail(String email); // @Param 없음

Java 8 이상 환경이나 특정 컴파일러 옵션(debug info 제외 등)에서는 컴파일 시 메서드 파라미터 이름인 emailarg0, var1 등으로 치환되어 버릴 수 있습니다. 이 경우 JPQL은 :email에 무엇을 넣어야 할지 몰라 런타임 에러를 발생시킵니다.

또한, 개발자가 무심코 파라미터 이름을 String email에서 String userEmail로 리팩토링할 경우, 쿼리문까지 수정하지 않으면 장애로 이어집니다.

2. 구현 표준: @Param의 강제화

우리는 이러한 잠재적 위험을 원천 차단하기 위해, JPQL 사용 시 @Param 어노테이션 사용을 필수 표준으로 정했습니다.

📝 Implementation Code (UserRepository.java)

public interface UserRepository extends JpaRepository<UserEntity, Long> {
    
    // ...
 
    // 동적 쿼리 로직이 포함된 복잡한 JPQL
    @Query(value = "SELECT u FROM UserEntity u WHERE ((:searchString is null or u.name like %:searchString%) ...")
    Page<UserEntity> findAllBySearchStringAndRole(
            @Param("searchString") String searchString, // 명시적 바인딩
            @Param("role") UserEntity.UserRoleEnum role, 
            Pageable pageable
    );
}

3. 동적 쿼리 구현 전략 (QueryDSL 도입 전 과도기)

위 코드의 findAllBySearchStringAndRole 메서드는 QueryDSL을 도입하기 전, JPQL만으로 동적 쿼리(Dynamic Query) 를 흉내 내기 위해 고안된 패턴입니다.

  • Logic: (:searchString is null or ...)

  • 의도: 검색어가 null로 들어오면 앞의 조건이 true가 되어 뒤의 OR 조건을 무시하므로, 전체 데이터를 조회하게 됩니다. 검색어가 있으면 뒤의 조건이 실행됩니다.

🎯 설계 회고

  • @Param: 코드량이 조금 늘어나더라도, 리팩토링 내성과 컴파일 환경 변화에 흔들리지 않는 견고한 애플리케이션을 만들기 위한 필수적인 안전 장치입니다.

  • 동적 쿼리: JPQL로도 간단한 분기 처리는 가능하지만, 조건이 복잡해질 경우 가독성과 유지보수를 위해 QueryDSL(CustomRepositoryImpl)로 전환하는 것이 바람직한 방향임을 확인했습니다. (현재 프로젝트는 QueryDSL 설정을 이미 마친 상태입니다.)