[JAVA] JPQL과 @Query 사용법 – Spring Data JPA에서 복잡한 쿼리 다루기

Spring Data JPA는 메서드 이름만으로도 간단한 쿼리를 자동 생성해주지만, 복잡한 조건이나 조인을 처리하려면 JPQL(Java Persistence Query Language)@Query 어노테이션이 필요합니다. 

JPQL 작성법과 @Query 사용 방법을 정리해보겠습니다.

 

1. JPQL이란?

JPQL은 SQL과 비슷하지만, 엔티티(Entity)를 대상으로 동작한다는 점이 다릅니다. 즉, SELECT u FROM User u처럼 테이블이 아닌 엔티티 클래스 이름을 기준으로 작성합니다.

JPQL vs SQL

JPQL SQL
SELECT u FROM User u WHERE u.age > 20 SELECT * FROM users WHERE age > 20
엔티티 기준 테이블 기준

 

2. @Query 어노테이션 기본 사용법

JPA의 Repository 인터페이스에 @Query를 사용하면 JPQL을 직접 작성할 수 있습니다.

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    @Query("SELECT u FROM User u WHERE u.email = :email")
    Optional<User> findByEmail(@Param("email") String email);
}

주의할 점

  • JPQL은 엔티티명.필드명으로 작성해야 합니다.
  • SQL처럼 *를 사용할 수 없습니다. SELECT u 또는 SELECT u.name처럼 명시해야 합니다.

 

3. @Query로 JOIN 처리하기

복잡한 연관 관계 쿼리는 JPQL의 JOIN 문법을 활용할 수 있습니다.

@Query("SELECT o FROM Order o JOIN o.user u WHERE u.name = :name")
List<Order> findOrdersByUserName(@Param("name") String name);

위 쿼리는 OrderUser가 연관관계(@ManyToOne 등)로 매핑되어 있어야 사용 가능합니다.

 

4. Native SQL 사용하기

JPQL로 표현하기 어려운 쿼리는 nativeQuery = true 옵션으로 Native SQL도 작성할 수 있습니다.

@Query(value = "SELECT * FROM users WHERE email LIKE %:keyword%", nativeQuery = true)
List<User> searchByEmail(@Param("keyword") String keyword);

단, Native SQL을 사용할 경우 반환 타입 매핑에 주의해야 합니다. Projection 또는 Interface 기반 DTO를 함께 활용하는 것이 좋습니다.

 

5. DTO 직접 조회 – new 키워드 활용

JPQL에서는 new 키워드를 사용해 직접 DTO 객체를 생성할 수 있습니다.

@Query("SELECT new com.example.dto.UserDto(u.id, u.name) FROM User u WHERE u.active = true")
List<UserDto> findActiveUsers();

DTO는 반드시 해당 필드들을 받는 생성자가 정의되어 있어야 하며, 패키지 경로까지 명시해야 합니다.

 

6. 정렬, 페이징도 함께

@QueryPageable과 함께 사용할 수 있습니다.

@Query("SELECT u FROM User u WHERE u.status = :status")
Page<User> findByStatus(@Param("status") String status, Pageable pageable);

JPQL 내부에는 ORDER BY를 넣지 않아도 되고, 정렬은 PageRequest.of(...)에서 처리 가능합니다.

 

7. 정리 및 실무 팁

  • 단순 조회는 메서드 이름 기반으로, 복잡한 쿼리는 @Query로 처리
  • JOIN, GROUP BY, 복잡한 조건은 JPQL 또는 Native SQL 활용
  • 결과가 엔티티가 아닐 경우 DTO 매핑 시 new 키워드 또는 Projection 사용
  • 쿼리가 길어질 경우 @Query 대신 Querydsl도 고려