이번 시간에는 @Query 어노테이션을 이용해 JPQL, native SQL query를 사용하는 방법에대해 알아보겠습니다.
@Query는 domain model이 아닌 Repository안에있는 메서드에 위치시킬 것을 권장합니다.
DB에서 User Entity를 받아오는 아주 기초적인 쿼리 문법입니다.
@Query("SELECT u FROM User u WHERE u.status = 1")
Collection<User> findAllActiveUsers();
native SQL을 사용하려면 SQL을 value attribute에 정의하고, nativeQuery를 true로 설정하여 간단하게 사용할 수 있습니다.
@Query(
value = "SELECT * FROM USERS u WHERE u.status = 1",
nativeQuery = true)
Collection<User> findAllActiveUsersNative();
정렬을 위한 order by절의 사용법을 알아보겠습니다.
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을 사용해서 쿼리를 정의한다면 문제없이 사용할 수 있습니다.
@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 SQL을 사용할 때는 Sort 객체를 받을 수 없습니다. 만약 이를 실행할 경우 예외가 발생합니다.
org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Cannot use native queries with dynamic sorting and/or pagination
예외가 native SQL은 sorting도 pagination도 할 수 없다고 하네요. 하지만 pagination은 가능합니다.
JPQL을 사용하면 간단히 Pageable객체를 파라미터로 넘기면 됩니다.
@Query(value = "SELECT u FROM User u ORDER BY id")
Page<User> findAllUsersWithPagination(Pageable pageable);
PageRequest pr = PageRequest.of(페이지 넘버, 페이지당 data 크기);
userRepository.findAllUsersWithPagination(pr);
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이전 버젼에서는 위처럼 페이징 메소드를 만들어도 앞에서 말씀드린 예외가 똑같이 터집니다.
@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 이녀석을 넣어 해결할 수 있습니다.
쿼리에 두가지 방식으로 파라미터를 넘길 수 있습니다. 첫번째는 파라미터의 index(순서), 두번째는 파라미터 이름 기반입니다.
@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 로 순서를 지정하고, 메소드 파라미터에 그 순서에 맞게 파라미터를 넣어줍니다.
@Query(
value = "SELECT * FROM Users u WHERE u.status = ?1",
nativeQuery = true)
User findUserByStatusNative(Integer status);
Native SQL도 JPQL과 동일합니다.
위 Indexd Query보다 이녀석을 사용하는 것을 권장합니다. 순서로 하면 좀 헷갈려요!
Named Parameter는 @Param 어노테이션을 사용하여 파라미터를 매칭해줍니다.
@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로 파라미터를 넘겨줍니다.
@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과 동일합니다.
파라미터로 Collection 객체를 넘길 수 도 있습니다.
@Query(value = "SELECT u FROM User u WHERE u.name IN :names")
List<User> findUserByNameList(@Param("names") Collection<String> names);
@Query 어노테이션 메소드를 이용해 INSERT, UPDATE, DELETE등 DB에 변화가 발생하는 작업을 하기 위해서는 @Modifyging 어노테이션을 필수로 붙여야합니다.
@Modifying
@Query("update User u set u.status = :status where u.name = :name")
int updateUserSetStatusForName(@Param("status") Integer status,
@Param("name") String name);
@Modifying
@Query(value = "update Users u set u.status = ? where u.name = ?",
nativeQuery = true)
int updateUserSetStatusForNameNative(Integer status, String name);