20221222 TIL JPA에서 @Query를 이용한 사용자 정의 쿼리 날리기!
JpaRepository는 기본적인 메서드만을 제공해준다.
하지만 개발을 하다보면 JpaRepository가 기본적으로 제공하는 메서드보다 복잡한 쿼리를 날리고 싶은 경우가 생긴다.
예를 들면 특정 컬럼 값을 기준으로 조회하거나 정렬을 하는 등의 경우가 있을 수 있다.
이럴 때는 사용자 정의 쿼리를 사용해야 하는데,
사용자 정의 쿼리를 사용하는 방식에는 크게 쿼리 메서드 방식과 @Query 어노테이션을 사용하는 방법이 있다.
쿼리 메서드 방식은 메서드 이름을 정해진 컨벤션에 따라 작성하면
메서드 이름을 기반으로 적절한 쿼리가 수행되는 방식이다.
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repository-query-keywords
위 링크에서 수많은 키워드들을 확인할 수 있다.
사용자 이름으로 사용자를 찾는 등의 간단한 쿼리는 위 방식을 이용해서 작업을 하면 매우 편리하다.
실제로 강의에서도 쿼리 메서드 방식을 이용해서 아샬님께서 알려주셨고,
메서드 이름만 잘 정의해주면 잘 동작하는 것을 보고 신기해하면서 편하게 사용하고 있었다.
그런데 포트폴리오를 진행하면서 도저히 쿼리메서드로 어떻게 메서드 이름을 정하면 좋을지 감이 잡히지 않는 경우가 발생했다.
나의 포트폴리오 웹페이지의 첫 화면에서는 특정 기간동안의 인기 질문이 표시된다.
인기 질문은 질문 좋아요 순으로 뽑힌다.
질문 엔티티는 좋아요를 한 사용자 id를 배열로 들고 있고,
그 질문들 중 좋아요를 한 사용자 id 배열의 크기가 큰 순으로 정렬을 하는 작업이 필요했다.
이 작업은 오히려 쿼리를 직접짜는 것이 더 효율적일 것 같았다.
그래서 내가 직접 쿼리를 짤 수 있는 방식을 찾아보았고,
@Query 어노테이션을 활용하면 된다는 것을 알게 되었다.
엔티티를 대상으로 쿼리를 작성하는 JPQL을 이용해서 쿼리를 작성해주고,
메서드 위에 어노테이션을 붙여주면 된다.
@Query("SELECT u FROM User u WHERE u.status = 1")
Collection<User> findAllActiveUsers();
혹은 native SQL을 사용할 수도 있다.
@Query(
value = "SELECT * FROM USERS u WHERE u.status = 1",
nativeQuery = true)
Collection<User> findAllActiveUsersNative();
나는 굳이 nativeSQL을 활용할 이유를 느끼지 못하여서 JPQL을 이용해서 작성해보았다.
쿼리가 잘 작동하는지 확인하기 위해 테스트를 작성해주자.
@SpringBootTest
@Transactional
@ActiveProfiles("test")
class QuestionRepositoryTest {
@Autowired
QuestionRepository questionRepository;
@Test
void findAllByLikesCount() {
List<LikeUserId> likeUserIds1 = List.of(new LikeUserId(1L), new LikeUserId(2L));
List<LikeUserId> likeUserIds2 = List.of(new LikeUserId(1L), new LikeUserId(2L), new LikeUserId(3L));
List<LikeUserId> likeUserIds3 = List.of(new LikeUserId(1L));
Question question1 = new Question(likeUserIds1);
Question question2 = new Question(likeUserIds2);
Question question3 = new Question(likeUserIds3);
questionRepository.save(question1);
questionRepository.save(question2);
questionRepository.save(question3);
List<Question> questions = questionRepository.findAllOrderByLikesCount();
assertThat(questions.get(0).getLikeUserIds()).hasSize(3);
assertThat(questions.get(1).getLikeUserIds()).hasSize(2);
assertThat(questions.get(2).getLikeUserIds()).hasSize(1);
}
}
public interface QuestionRepository extends JpaRepository<Question, Long> {
@Query("SELECT q FROM Question q ORDER BY size(q.likeUserIds) DESC")
List<Question> findAllOrderByLikesCount();
}
위 코드를 통해 좋아요 순으로 정렬된 질문 목록을 얻을 수 있게 되었다!
생각보다 직접 쿼리를 짜는 것도 많이 복잡하지 않았다.
간단한 쿼리일 때는 쿼리 메서드를 이용하고,
앞으로 데이터를 처리하면서 복잡한 경우에는 직접 쿼리를 짜는 방식을 이용하면 좋을 것 같다!
참고