이번 글에서는 스프링 데이터 JPA의 @Query를 이용해 리포지토리 메소드에 쿼리를 직접 정의하는 방법을 살펴본다.
“원래 JPA로 코드를 짠다면 어떻게 될까?”라는 비교에서 시작해 보자.
1. 순수 JPA로 작성했을 때
public List<Member> findUser(String username, int age) {
return em.createQuery(
"select m from Member m where m.username = :username and m.age = :age",
Member.class)
.setParameter("username", username)
.setParameter("age", age)
.getResultList();
}
위는 이름과 나이로 한 멤버를 찾는 메소드이다.
순수 JPA 방식은 다음과 같은 단점이 있다.
- 문자열 JPQL을 직접 관리해야 한다.
- 오타나 잘못된 필드명을 적어도 컴파일 시점에는 전혀 알 수 없다.
- 결국 애플리케이션을 실행한 뒤에야 오류를 발견하게 되고, 이는 버그를 양산하는 원인이 된다.
2. 스프링 데이터 JPA의 @Query 방식
스프링 데이터 JPA에서는 훨씬 단순하다.
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = :username and m.age = :age")
List<Member> findUser(@Param("username") String username, @Param("age") int age);
}
- 리포지토리 인터페이스의 메소드에 @Query를 붙이고 JPQL을 직접 작성한다.
- 메소드와 파라미터 이름이 JPQL과 연결되어 관리된다.
- 무엇보다 애플리케이션 실행 시점에 쿼리 검증이 이루어지기 때문에 오타나 문법 오류를 미리 잡아낼 수 있다.
- Named 쿼리와 같은 수준의 안전성을 제공한다는 점이 큰 장점이다.
3. 왜 @Query를 자주 쓰는가
- 메소드 이름으로 표현할 수 있는 쿼리는 한계가 있다.
List<Member> findByUsernameAndAgeGreaterThanAndTeamNameAndStatusAnd…
- 이렇게 되면 메소드 이름이 지나치게 길고 읽기 어려워진다.
- @Query를 쓰면 쿼리를 명확하게 메소드에 붙여놓을 수 있어 가독성과 유지보수성이 훨씬 좋아진다.
- 또한 페치 조인 같은 복잡한 로직도 쉽게 표현 가능하다.
@Query("select m from Member m join fetch m.team where m.username = :username")
List<Member> findMemberWithTeam(@Param("username") String username);
이처럼 한 번의 SQL로 회원과 팀을 함께 가져올 수 있다. (N+1 문제 해결)
4. DTO로 직접 조회하기
실무에서는 엔티티 자체가 아니라 DTO(Data Transfer Object) 로 필요한 데이터만 조회해야 할 때가 많다.
이때도 @Query를 이용할 수 있다.
@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) " +
"from Member m join m.team t")
List<MemberDto> findMemberDto();
여기서 중요한 점은 JPA의 new 명령어를 사용한다는 것이다.
그리고 반드시 생성자가 일치하는 DTO 클래스가 있어야 한다.
package study.datajpa.dto;
import lombok.Data;
@Data
public class MemberDto {
private Long id;
private String username;
private String teamName;
public MemberDto(Long id, String username, String teamName) {
this.id = id;
this.username = username;
this.teamName = teamName;
}
}
- select new … 구문으로 바로 DTO 인스턴스를 생성한다.
- 필요 없는 필드까지 전부 가져오지 않고, 정확히 필요한 데이터만 조회할 수 있어 성능과 응용 면에서 유리하다.
마무리하며
정리하자면,
순수 JPA는 쿼리를 문자열로 관리하기 때문에 오타나 잘못된 필드명이 있어도 컴파일 시점에 잡히지 않는다. 그래서 런타임에야 오류를 발견하는 경우가 많아지고, 이는 곧 버그로 이어진다.
반면 스프링 데이터 JPA의 @Query는 애플리케이션 실행 시점에 쿼리를 검증한다. 문법 오류를 미리 잡을 수 있어 안정적이고, 복잡한 쿼리도 메소드에 깔끔하게 정의할 수 있다는 장점이 있다. 또한 페치 조인처럼 메소드 이름만으로는 표현하기 힘든 쿼리도 쉽게 해결할 수 있다.
여기에 메소드 이름으로 쿼리 생성 기능은 단순 조건을 표현할 때는 매우 편리하다. 예를 들어 findByUsernameAndAgeGreaterThan 같은 형태는 짧고 직관적이다. 하지만 조건이 많아질수록 메소드 이름이 길어지고 가독성이 떨어진다. 그래서 실무에서는 간단한 조회는 메소드 이름 기반으로, 복잡한 조건이나 페치 조인이 필요할 때는 @Query를 함께 사용하는 방식이 가장 많이 쓰인다한다.
결국 스프링 데이터 JPA는 메소드 이름 기반 쿼리와 @Query를 상황에 맞게 적절히 섞어서 쓰는 것이 핵심이다.
감사합니다.
'스프링 데이터 JPA' 카테고리의 다른 글
| [스프링] 스프링 Data JPA(6) @EntityGraph (0) | 2025.08.21 |
|---|---|
| [스프링] 스프링 Data JPA(5) 벌크성 수정쿼리 (0) | 2025.08.21 |
| [스프링] 스프링 Data JPA(4) JPA 페이징과 정렬 (0) | 2025.08.21 |
| [스프링] 스프링 Data JPA(2) 메소드 이름으로 쿼리생성 (1) | 2025.08.21 |
| [스프링] 스프링 Data JPA에 대하여 (4) | 2025.08.16 |
