JPQL(Jakarta Persistence Query Language)은 테이블이 아니라 엔티티와 필드를 대상으로 쓰는 JPA의 객체 지향 쿼리 언어다. 실행할 때 연결된 DB의 SQL로 번역된다. Spring Data에서는 @Query로 리포지터리 메서드에 직접 붙인다.
public interface ContactRepository extends JpaRepository<Contact, Long> {
@Query("select c from Contact c where c.status = :status and c.createdAt > :after")
List<Contact> findRecent(@Param("status") String status, @Param("after") LocalDateTime after);
@Query("select o from Order o join fetch o.customer where o.id in :ids")
List<Order> findWithCustomer(@Param("ids") List<Long> ids); // N+1 방지
@Query(value = "select * from contact where tsv @@ plainto_tsquery(:q)", nativeQuery = true)
List<Contact> search(@Param("q") String q); // DB 전용 기능
@Modifying(clearAutomatically = true)
@Transactional
@Query("update Contact c set c.status = :status where c.id = :id")
int updateStatus(@Param("id") Long id, @Param("status") String status);
}언제 무엇을
- 조건이 단순하면 메서드 이름 쿼리(Spring Data 리포지터리와 쿼리 메서드)
- 조건이 많거나 조인·fetch join이 필요하면
@Query+ JPQL - 윈도 함수(window function), 전문 검색(full-text search)처럼 DB 고유 기능이 필요하면
nativeQuery = true. 대신 DB를 바꾸면 고쳐야 한다 - 조건이 런타임에 조합되는 검색 화면이라면 Criteria API나 Querydsl 같은 타입 안전한 빌더
변경 쿼리
UPDATE·DELETE에는@Modifying이 필요하고, 트랜잭션 안에서 실행해야 한다(@Transactional)- 벌크 쿼리(bulk query)는 영속성 컨텍스트를 거치지 않고 DB에 바로 간다. 이미 로드된 엔티티는 옛 값을 들고 있으므로
clearAutomatically = true로 비우거나 다시 조회한다. 변경 감지도 동작하지 않는다
Named Query
@NamedQuery(JPQL), @NamedNativeQuery(SQL)는 쿼리를 엔티티 쪽에 이름으로 미리 정의한다. 앱이 시작할 때 문법을 검사한다는 장점이 있지만, 쿼리가 엔티티에 흩어져 요즘은 @Query를 더 많이 쓴다. 여러 개는 @NamedQueries로 묶는다.
한계
JPQL은 SQL 표준의 일부만 지원한다. 쿼리가 복잡해지면 억지로 맞추기보다 네이티브 쿼리나 JDBC와 JdbcTemplate이 낫다. 조인으로 연관을 함께 가져오는 이유는 N+1 문제을 본다.
출처: Spring Data JPA 문서: Using @Query · Modifying Queries · Named Queries · Hibernate User Guide: HQL/JPQL