[Spring JPA] @Query

SeongWon Oh·2021년 10월 10일
3

Spring Framework

목록 보기
21/33
post-thumbnail

1. @Query는 무엇인가?

  • @Query는 더 구체적인 쿼리 메서드를 작성하기 위해 사용하는 쿼리 메서드의 custom버전이다.
  • 쿼리는 기본적으로 JPA에서 사용하는 쿼리 문법인 JPQL을 사용한다.
    ※ JPQL은 JPA의 entity를 기반으로 하는 쿼리를 생성해주기 위한 문법이다.

1.1. Query Method의 단점

쿼리메서드는 아래의 코드와 같이 조건이 많아지게 된다면 method의 이름이 어마어마하게 길어지게 된다.

public interface BookRepository extends JpaRepository<Book, Long> {
    List<Book> findByCategoryIsNullAndDeletedFalse();
}

이러한 문제는 @Qeury를 사용하면 메서드 명을 줄일 수 있고 더욱 더 가독성 있는 메서드를 만들 수 있다.


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

2.1. 사용 예시

    @Query(value = "select b from Book b " +
            "where name =?1 and createdAt >=?2 and updatedAt >= ?3 and category is null")
    List<Book> findByNameRecently(String name, LocalDateTime createdAt, LocalDateTime updatedAt);
  • @Query 어노테이션은 위와 같이 Repository 인터페이스 안에서 메서드 위에 @Query를 붙이며 만들 수 있다.
  • value안에는 SQL쿼리문이 아닌 JPQL 쿼리문을 사용하여 데이터를 넣어주어야한다.
  • JPA의 entity를 기반으로 하는 쿼리를 생성해주기에 table이름인 book이 아닌 entity이름인 Book이 들어가게된다. 또한 where에 있는 name, createdAt 등은 메서드의 파라미터가 아니고 엔티티 내에 있는 field의 이름을 가르킨다.
  • where조건에 parameter를 넣는 방법은 아래와 같이 크게 2가지 방법이 있다.

2.2. where조건에 parameter를 넣는 방법

2.2.1 첫번째 방법

    @Query(value = "select b from Book b " +
            "where name =?1 and createdAt >=?2 and updatedAt >= ?3 and category is null")
    List<Book> findByNameRecently(String name, LocalDateTime createdAt, LocalDateTime updatedAt);
  • 위의 코드와 같이 값이 들어가야 하는 부분에 ?1, ?2, ?3과 같이 넣어준다.
  • ?1, ?2 등이 의미하는 것은 메서드의 첫번째 파라미터, 두번째 파라미터를 가르킨다.
  • 이때 index는 1부터 시작한다.
  • ?1, ?3, ?2처럼 꼭 파라미터의 순서대로 사용하지 않아도 되는데 자바에서는 순서에 의존성을 가지는 프로그래밍을 지양하여 가능하면 순서대로 하는게 좋다.
  • 🚨 하지만 만약 코딩 중간에 파라미터를 기존 파라미터들 사이에 추가를 하게 된다면 뒤의 순서는 다 꼬여서 오류가 발생할 수도 있는 문제점이 있다.

2.2.2 두번째 방법

    @Query(value = "select b from Book b " +
            "where name = :name and createdAt >= :createdAt and updatedAt >= :updatedAt and category is null")
    List<Book> findByNameRecently2(
            @Param("name") String name,
            @Param("createdAt") LocalDateTime createdAt,
            @Param("updatedAt") LocalDateTime updatedAt);
  • 위와 같이 메서드의 파라미터들을 @Param을 통해 이름을 정해주고 where문에서는 :name, ?createdAt과 같이 넣어주는 방법이다.

  • 해당 방법은 parameter순서에 영향을 받지 않기 때문에 logic의 변경에 더 자유롭고 부작용에서 더 자유로워질 수 있다.


2.3. 특정 컬럼만 추출하기

  • 기존 쿼리 메서드는 리턴 타입이 entity라 테이블의 모든 컬럼을 조회하였지만 @Query는 리턴 타입이 entity가 아닌 필요한 특명 몇개의 컬럼만 추출할 수 있다.

2.3.1 첫번째 방법 - 인터페이스로 특정 컬럼 추출하기

public interface BookRepository extends JpaRepository<Book, Long> {
    @Query(value = "select b.name as name, b.category as category from Book b")
    List<BookNameAndCategory> findBookNameAndCategory();
}
// BookNameAndCategory.java
public interface BookNameAndCategory {
    String getName();
    String getCategory();
}

interface를 선언해주고 위와 같이 사용을 하면 getName, getCategory와 같은 메서드를 통해 해당 column의 값만 가져올 수 있다.

2.3.2 두번째 방법 - 클래스로 특정 컬럼 추출하기

public interface BookRepository extends JpaRepository<Book, Long> {
    @Query(value = "select new com.example.jpa_study.repository.dto.BookNameAndCategory(b.name, b.category) from Book b")
    List<BookNameAndCategory> findBookNameAndCategory();
}
// BookNameAndCategory.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BookNameAndCategory {
    private String name;
    private String category;
}

Class를 통해 받는 방법도 interface와 유사하며 똑같이 getName, getCategory와 같은 메서드를 통해 해당 column의 값만 가져올 수 있다.

하지만 class를 통해 필요한 값만 가져오려면 해당 class의 위치를 정확하게 대입해줘야한다는 점을 주의해줘야한다❗❗


Native Query사용하기

native query는 DB에서 사용하는 SQL문을 그대로 사용하게 해준다.

Native query를 사용하는 이유

  1. jpa의 쿼리메서드는 여러 데이터를 업데이트할때 여러번의 update query가 실행되지만 native query를 사용하면 한번의 query로 업데이트가 가능하다.
  2. JPA에서 기본적으로 지원하지 않는 기능을 사용할 때 사용한다. (ex. show tables, show databases)
    nativeQuery옵션을 true를 사용하면 entity가 아닌 table을 사용하게 된다.
    field의 이름도 table의 column명을 써야한다. (createdAt이 아닌 created_At)

Native query사용 예시

  • Native Query를 사용하여 값을 조회하는 경우
    @Query(value = "select * from book", nativeQuery = true)
    List<Book> findAllCustom();
    
    @Query(value = "show tables", nativeQuery = true)
    List<String> showTables();
  • Native Query를 사용하여 값을 변경하는 경우
    • jpa의 쿼리메서드의 save같은 경우는 save자체에 @Transactional이 붙어있지만 native query는 데이터 조작을 할 때는 직접 붙여줘야한다.
    • update, delete와 같은 DML작업에서는 @Modyfying이라는 것을 붙여 update가 되었다는 것을 확인시켜줘야 return값을 받을 수 있다.
    • 이와 같이 native query를 사용하면 한번의 query로 업데이트가 가능하다.
    @Transactional
    @Modifying
    @Query(value = "update book set category = 'IT Book'", nativeQuery = true)
    int updateCategories(); // return 타입을 int, long과 같은 타입으로 하면 업데이트 된 row의 수를 return해준다.

🚨 주의할 점
Native Query 특정 DB에 의존성을 가진 쿼리를 만들게 된다.
-> DB종류가 바껴도 DB에 맞게 자동으로 쿼리를 바꿔준다는 JPA의 장점에서 빗겨나가게 된다.


📝 정리

  • @Query는 더 구체적인 쿼리 메서드를 작성하기위해 사용한다.
  • @Query는 JPQL 쿼리문을 사용한다.
  • @Query를 이용해도 페이징 처리를 하는 Pageable인터페이스는 활용 가능하다.
  • Native QUery를 사용가능하다.
  • 쿼리메서드의 장점
    • @Qeury를 사용하면 메서드 명을 줄일 수 있고 더욱 더 가독성 있는 메서드를 만들 수 있다.
    • @Query는 리턴 타입이 entity가 아닌 필요한 특명 몇개의 컬럼만 추출할 수 있다.

Reference

profile
블로그 이전했습니다. -> https://seongwon.dev/

0개의 댓글