견고한 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 제외 등)에서는 컴파일 시 메서드 파라미터 이름인 email이 arg0, 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 설정을 이미 마친 상태입니다.)