JPA - @Query 어노테이션

컴업·2021년 12월 24일
1

이번 시간에는 @Query 어노테이션을 이용해 JPQL, native SQL query를 사용하는 방법에대해 알아보겠습니다.

1. SELECT

  • 참고
    @Query 어노테이션은 @NamedQuery어노테이션 혹은 orm.xml에 정의해놓은 named queries보다 우선순위가 높습니다.

@Query는 domain model이 아닌 Repository안에있는 메서드에 위치시킬 것을 권장합니다.


JPQL

DB에서 User Entity를 받아오는 아주 기초적인 쿼리 문법입니다.

@Query("SELECT u FROM User u WHERE u.status = 1")
Collection<User> findAllActiveUsers();

Native

native SQL을 사용하려면 SQL을 value attribute에 정의하고, nativeQuery를 true로 설정하여 간단하게 사용할 수 있습니다.

@Query(
  value = "SELECT * FROM USERS u WHERE u.status = 1", 
  nativeQuery = true)
Collection<User> findAllActiveUsersNative();


2. ORDER BY

정렬을 위한 order by절의 사용법을 알아보겠습니다.

Method Signatures

findAll() 처럼 메소드 이름으로 쿼리를 만든경우 정렬을 사용하기 위해 Sort 객체를 사용할 수 밖에 없습니다.

userRepository.findAll(Sort.by(Sort.Direction.ASC, "name"));

이 때문에 만약 우리가 name 칼럼의 길이로 정렬을 하고싶을 때는 다음과 같은 파라미터를 생각할 수 있습니다.

userRepository.findAll(Sort.by("LENGTH(name)"));

그러나. 이 메소드를 실행하면 예외가 터질 것 입니다.

org.springframework.data.mapping.PropertyReferenceException: No property LENGTH(name) found for type User!

name의 길이로 정렬할 수 없다는 뜻!

JPQL

JPQL을 사용해서 쿼리를 정의한다면 문제없이 사용할 수 있습니다.

@Query(value = "SELECT u FROM User u")
List<User> findAllUsers(Sort sort);

위처럼 Sort를 파라미터로 넘겨 메소드를 정의하고 아래 코드로 사용합니다.

userRepository.findAllUsers(Sort.by("name"));

userRepository.findAllUsers(JpaSort.unsafe("LENGTH(name)"));

중요한 것은 JpaSort.unsafe("..") 를 이용해야 name 칼럼의 길이로 정렬할 수 있다는 것입니다.

Sort.by("LENGTH(name)");

단순히 Sort로는 위와 같은 예외가 발생합니다.


Native

native SQL을 사용할 때는 Sort 객체를 받을 수 없습니다. 만약 이를 실행할 경우 예외가 발생합니다.

org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Cannot use native queries with dynamic sorting and/or pagination

예외가 native SQL은 sorting도 pagination도 할 수 없다고 하네요. 하지만 pagination은 가능합니다.



3.Pagination

JPQL

JPQL을 사용하면 간단히 Pageable객체를 파라미터로 넘기면 됩니다.

@Query(value = "SELECT u FROM User u ORDER BY id")
Page<User> findAllUsersWithPagination(Pageable pageable);
  • 참고
    PageRequest 객체를 넘긴다.
PageRequest pr = PageRequest.of(페이지 넘버, 페이지당 data 크기);

userRepository.findAllUsersWithPagination(pr);

Native

native SQL을 이용하여 pagination을 하려면 추가적인 작업이 필요합니다.

@Query(
  value = "SELECT * FROM Users ORDER BY id", 
  countQuery = "SELECT count(*) FROM Users", 
  nativeQuery = true)
Page<User> findAllUsersWithPagination(Pageable pageable);

위처럼 조회하려는 테이블 전체 열 수를 countQuery 에 지정해주어야 합니다.

Spring Data JPA 2.0.4 이전 버젼

Spring Data JPA 2.0.4이전 버젼에서는 위처럼 페이징 메소드를 만들어도 앞에서 말씀드린 예외가 똑같이 터집니다.

@Query(
  value = "SELECT * FROM Users ORDER BY id \n-- #pageable\n",
  countQuery = "SELECT count(*) FROM Users",
  nativeQuery = true)
Page<User> findAllUsersWithPagination(Pageable pageable);

\n-- #pageable\n 이녀석을 넣어 해결할 수 있습니다.



4. Query 메소드에 파라미터 넘기기

쿼리에 두가지 방식으로 파라미터를 넘길 수 있습니다. 첫번째는 파라미터의 index(순서), 두번째는 파라미터 이름 기반입니다.

Indexd Query Parameter

JPQL

@Query("SELECT u FROM User u WHERE u.status = ?1")
User findUserByStatus(Integer status);

@Query("SELECT u FROM User u WHERE u.status = ?1 and u.name = ?2")
User findUserByStatusAndName(Integer status, String name);

위 코드처럼 쿼리문에 ?1, ?2 로 순서를 지정하고, 메소드 파라미터에 그 순서에 맞게 파라미터를 넣어줍니다.

Native

@Query(
  value = "SELECT * FROM Users u WHERE u.status = ?1", 
  nativeQuery = true)
User findUserByStatusNative(Integer status);

Native SQL도 JPQL과 동일합니다.

Named Parameters

위 Indexd Query보다 이녀석을 사용하는 것을 권장합니다. 순서로 하면 좀 헷갈려요!

Named Parameter는 @Param 어노테이션을 사용하여 파라미터를 매칭해줍니다.

JPQL

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")
User findUserByStatusAndNameNamedParams(
  @Param("status") Integer status, 
  @Param("name") String name);

쿼리문 내에 :abc로 위치를 알려주고 @Param("abc") String abc로 파라미터를 넘겨줍니다.

Native

@Query(value = "SELECT * FROM Users u WHERE u.status = :status and u.name = :name", 
  nativeQuery = true)
User findUserByStatusAndNameNamedParamsNative(
  @Param("status") Integer status, @Param("name") String name);

native SQL도 JPQL과 동일합니다.



5. Collection Parameter

파라미터로 Collection 객체를 넘길 수 도 있습니다.

@Query(value = "SELECT u FROM User u WHERE u.name IN :names")
List<User> findUserByNameList(@Param("names") Collection<String> names);


6. @Modifiying

@Query 어노테이션 메소드를 이용해 INSERT, UPDATE, DELETE등 DB에 변화가 발생하는 작업을 하기 위해서는 @Modifyging 어노테이션을 필수로 붙여야합니다.

JPQL

@Modifying
@Query("update User u set u.status = :status where u.name = :name")
int updateUserSetStatusForName(@Param("status") Integer status, 
  @Param("name") String name);

Native

@Modifying
@Query(value = "update Users u set u.status = ? where u.name = ?", 
  nativeQuery = true)
int updateUserSetStatusForNameNative(Integer status, String name);


참고문헌

Spring Data JPA @Query

profile
좋은 사람, 좋은 개발자 (되는중.. :D)

0개의 댓글