[SpringBoot]- 쿼리메소드(Query Methods)기능과 @Query

ACAI BERRY DEVELOVER·2023년 6월 17일
0
post-thumbnail

  • 특정 범위의 객체를 검색하거나, like 처리가 필요한 경우, 여러 검색 조건이 필요할 때를 위해 Spring Data JPA는 다음의 방법을 제공한다.

  • 쿼리 메서드 : 메서드의 이름 자체가 쿼리의 구문으로 처리되는 기능

  • @Query : SQL과 유사하게 엔티티 클래스의 정보를 이용해서 쿼리를 작성하는 기능

  • Querydsl 등의 동적 쿼리 가능 (다음에 다룰 예정)

쿼리 메서드 (Query Methods)

Reference https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation

  • 쿼리 메서드는 말 그대로 메서드의 이름 자체가 질의(query)문이 되는 기능이다.
  • 주로 'findBy..'나 'getBy...'로 시작하고 이후에 필요한 필드조건이나 And, Or와 같은 키워드를 이용해서 메서드이름 그 자체로 질의 조건을 만들어낸다.
  • 쿼리 리턴 타입
    - select작업 시 List 타입이나 배열 이용 가능
    - 파라미터에 Pageable 타입을 넣는 경우에는 무조건 Page<엔티티 타입>
      List<Memo> findByMnoBetweenOrderByMnoDesc(Long from, Long to);
     

작성된 메서드 이름을 보면 mno를 기준으로 해서 between 구문을 사용하고 order by가 적용될 것임을 알 수 있다.

테스트 코드

@Test
    public void testQueryMethods(){
        //번호역순으로 50번과 60번사이대 리스트
//        List<Memo> list = memoRepository.findByMnoBetweenOrderByMnoDesc(50L, 60L);
//        for(Memo memo : list){
//            log.info(memo);
//        }

쿼리 메소드와 Pageable의 결합

  • 목록을 원하는 쿼리를 실행해야 한다면 앞의 예제와 같이 'OrderBy' 키워드 등을 사용해야 하기 때문에 메소드의 이름이 길어지고 혼동하기도 쉽다.
  • 쿼리 메소드는 Pageable 파라미터를 같이 결합해서 사용이 가능하다.
  • 정렬에 관한 부분은 Pageable로 처리해서 좀 더 간략한 메소드가 생성가능하다.
List<Memo> findByMnoBetweenOrderByMnoDesc(Long from, Long to);

Page<Memo> findByMnoBetween(Long from, Long to, Pageable pageable);
  • 위의 메소드를 아래로 간략하게 수정이 가능하다.
  • 정렬 조건은 Pageable을 통해 조절 할 수 있다 .
  • 이로 인해 더 간단한 형태의 메소드 선언이 가능해진진다.
  • Pageable 파라미터는 모든 쿼리 메서드에 적용할 수 있다.
  • 일반적으로 쿼리 메소드에 정렬 조건은 생략하고 만드는 경우가 많다.

테스트 코드

Sort sort  = Sort.by("mno").descending();
    Pageable pageable = PageRequest.of(1, 10, Sort.by("mno").descending());
    Page<Memo> result = memoRepository.findByMnoBetween(50L, 70L, pageable);
    for (Memo memo : result){

        log.info(memo);
    }

deleteBy로 시작하는 삭제 처리

  • 쿼리 메소드를 이용해서 'deleteBy'로 메소드 이름을 시작하면 특정한 조건에 맞는 데이터를 삭제하는 것이 가능하다.
void deleteMemoByMnoLessThan(Long num);

테스트 코드

@Test
  @Transactional
  @Commit
  public void testDeleteQueryMethods(){

      memoRepository.deleteMemoByMnoLessThan(10L);

  }
  • deleteBy..인 경우 우선 'select'문으로 해당 엔티티 객체를 가져오는 작업과 각 엔티티를 삭제하는 작업이 같이 이루어지기 때문에 @Transactional과 @Commit이라는 어노테이션을 사용한다.
  • 공부해야겠다.. 모르겠다...
  • Commit은 최종 결과를 커밋하기 위해 사용한다.
  • deleteBy는 실제 개발에 많이 사용되지 않는다.
  • 그 이유는 SQL을 이용하듯이 한 번에 삭제가 이루어지는 것이 아니라 각 엔티티 객체를 하나씩 삭제하기 때문이다(번거롭다).
  • deleteBy 보다는 @Query를 이용해서 효율적으로 개선한다.

@Query 어노테이션

  • 쿼리 메소드는 검색과 같은 기능을 작성할 때 편리하다.

  • 하지만 조인이나 복잡한 조건을 처리할 때는 'And, Or'등이 사용되면서 복잡해진다.

  • 일반적인 경우에는 간단한 처리만 쿼리 메소드를 이용하고, @Query를 이용하는 경우가 더 많다.

  • @Query는 메서드 이름과 상관없이 메소드에 추가한 어노테이션을 통해 원하는 처리가 가능하다.

  • @Query의 value는 JPQL(Java Persistence Query Language)로 작성한다.

  • JPQL은 객체 지향 쿼리라고 불리는 구문이다.

  • @Query의 기능

    • @Query로 필요한 데이터만 선별적으로 추출할 수 있다.
    • 데이터베이스에 맞는 순수한 SQL(Native SQL)을 사용할 수 있다.
    • insert, update, delete와 같은 select가 아닌 DML 등을 처리하는 기능이 있다.(@Modifying과 함께 사용)
  • 객체 지향 쿼리는 테이블 대신 엔티티 클래스를 이용한다.

  • 테이블의 칼럼 대신에 클래스에 선언된 필드를 이용해서 작성한다.

  • JPQL은 SQL과 상당히 유사하다.

예를 들어 'mno의 역순으로 정렬하라'는 다음과 같은 형태가 된다.

@Query("select m from Memo m order by m.mno desc")
List<Memo> getListDesc();
  • JPQL은 데이터베이스의 테이블 대신에 엔티티 클래스와 멤버 변수를 이용해서 SQL과 비슷한 JPQL을 작성한다.
  • JPQL은 SQL에서 사용되는 avg(),count(),group by, order by등과 같은 함수들을 사용할 수 있다.

@Query의 파라미터 바인딩

  • 'where' 구문과 그에 맞는 파라미터를 처리할 때 다음과 같은 방식을 사용한다.
    • '?1. ?2'와 1부터 시작하는 파라미터의 순서를 이용하는 방식
    • ':xxx'와 같이 ':파라미터 이름'을 활용하는 방식
    • '#{ }'와 같이 자바 빈 스타일을 이용하는 방식

예를 들어 ':파라미터'를 이용하는 방식은 다음과 같다.

@Transactional
@Modifying
@Query("update Memo m set m.memoText = :memoText where m.mno = :mno")
int updateMemoText(@Param("mno") Long mno, @Param("memoText") String memoText);

만일 ':'를 이용하는 경우는 여러 개의 파라미터를 전달할 때 복잡해 질 경우가 있다고 생각된다면 ':#'를 이용해 객체를 사용할 수 있다.

 @Transactional
 @Modifying
 @Query("update Memo m set m.memoText = :#{#param.memoText} where m.mno = :#{#param.mno}")
 int updateMemoText(@Param("param") Memo memo);

@Query와 페이징 처리

  • 쿼리메소드와 마찬가지로 @Query를 이용하는 경우에도 Pageable 타입의 파라미터를 지정할 수 있다.
  • Pageable 타입을 파라미터로 지정 시 페이징 처리와 정렬에 대한 부분을 작성하지 않을 수 있다.
  • 리턴 타입을 Page<엔티티 타입>으로 지정하는 경우에는 count를 처리하는 쿼리를 적용할 수 있다.
  • 따라서 @Query를 이용할 때는 별도의 countQuery라는 속성을 지정해주고 Pageable 타입의 파라미터를 전달하면 된다.
    @Query(value= " select m from Memo m where m.mno > :mno",
           countQuery = "select count(m) from Memo m where m.mno > :mno"
       )
       Page<Memo> getListWithQuery(Long mno, Pageable pageable);

  

Object[] 리턴

  • 쿼리 메소드의 경우에는 엔티티 타입의 데이터만을 추출한다.
  • @Query를 이용하는 경우에는 현재 필요한 데이터만을 Object[]의 형태로 선별적으로 추출할 수 있다.(강점)
  • JPQL을 사용할 때 경우에 따라서 JOIN이나 GROUP BY등을 이용하는 경우가 있는데, 이럴때 적절한 엔티티 타입이 존재하지 않은 경우가 많기 때문에 Object[] 타입을 리턴타입으로 지정할 수 있다.
  • 예를 들어 mno, memoText 그리고 현재 시간을 같이 얻어오고 싶을 때 Memo 엔티티 클래스에는 시간 관련된 부분의 선언이 없기 때문에 추가적 구문이 필요하다.
  @Query(value = "select m.mno, m.memoText, current_date  from Memo m where m.mno > :mno",
        countQuery = "select count(m) from Memo m where m.mno > :mno"
    )
    Page<Object[]> getListWithQueryObject(Long mno, Pageable pageable);
  

테스트 코드

  
    Page<Object[]> listWithQueryObject = memoRepository.getListWithQueryObject(34L, pageable);

        listWithQueryObject.forEach(m -> {
            Long mno = (Long) m[0];
            String memoText =(String) m[1];
            Date current_date = (Date) m[2];
            log.info("번호 : " + mno + " 메모내용 : " +  memoText +" 메모 시간 : " +  current_date);

        });

Native SQL 처리

  • @Query의 강력한 기능은 DB 고유의 SQL 구문을 그대로 활용하는 것이다.
  • JPA 자체가 DB에 독립적으로 구현이 가능하다는 장점을 잃어버리기는 하나
    경우에 따라서 복잡한 JOIN 구문을 처리하기 위해서 사용한다.
  //NativeQuery
    @Query(value = "select * from tbl_memo where mno > 0", nativeQuery = true)
    List<Object[]> getNativeResult();
  • @Query의 nativeQuery 속성값을 true로 지정하고 일반 SQL을 그대로 사용할 수 있다.
profile
쓸때 대충 쓰지 말고! 공부하면서 써!

0개의 댓글