Spring + Boot

JPA 쿼리 메서드 기능 @Query

devRachel 2021. 4. 8. 23:01

특정한 범위의 객체를 검색하거나 like 처리, 여러가지 검색 조건이 필요하다면?

 

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

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

Querydsl 등의 동적 쿼리 처리 기능

 

쿼리 메서드는 메서드의 ㅇ이름 자체가 Query문이 되는 기능입니다. findBy나 getBy로 시작하며 필요한 필드 조건이나 And, Or 키워드를 이용해서 조건을 만들어냅니다. 

쿼리 메서드는 사용하는 키워드에 따라서 파라미터의 개수가 결정됩니다. 리턴 타입도 상당히 자유롭습니다. Select를 하는 작업이라면 List 타입이나 배열을 이용할 수 있고 파라미터에 Pageable 타입을 넣는 경우 무조건 Page<E> 타입입니다. 

 

import org.springframework.data.jpa.repository.JpaRepository;
import org.zerock.ex2.entity.Memo;

import java.util.List;

public interface MemoRepository extends JpaRepository<Memo, Long> {
    List<Memo> findByMnoBetweenOrderByMnoDesc(Long from, Long to);
}

 

 

@Test
    public void testQueryMethod(){
        List<Memo> list = memoRepository.findByMnoBetweenOrderByMnoDesc(70L, 80L);

        for(Memo memo : list){
            System.out.println(memo);
        }
    }

 

실행 결과

 

 

Hibernate: 
    select
        memo0_.mno as mno1_0_,
        memo0_.memo_text as memo_tex2_0_ 
    from
        tbl_memo memo0_ 
    where
        memo0_.mno between ? and ?        메서드의 이름이 쿼리 구문으로 처리된 부분
    order by
        memo0_.mno desc
Memo(mno=80, memoText=테스트_____80)
Memo(mno=79, memoText=테스트_____79)
Memo(mno=78, memoText=테스트_____78)
Memo(mno=77, memoText=테스트_____77)
Memo(mno=76, memoText=테스트_____76)
Memo(mno=75, memoText=테스트_____75)
Memo(mno=74, memoText=테스트_____74)
Memo(mno=73, memoText=테스트_____73)
Memo(mno=72, memoText=테스트_____72)
Memo(mno=71, memoText=테스트_____71)
Memo(mno=70, memoText=테스트_____70)

 

 

만약 목록을 원하는 쿼리를 실행해야 된다면 OrderBy키워드 등을 사용해야 되기 때문에 메서드의 이름이 길어지고 혼동될 위험이 있겠죠. 하지만 다행스럽게도 쿼리 메서드는 Pageable 파라미터를 같이 결합해서 사용할 수 있기 때문에 Pagealbe로 간략한 메서드를 생성할 수 있습니다.

 

public interface MemoRepository extends JpaRepository<Memo, Long> {
    List<Memo> findByMnoBetweenOrderByMnoDesc(Long from, Long to);

    Page<Memo> findByMnoBetween(Long from, Long to, Pageable pageable);
}

 

 

  @Test
    public void testQueryMethodWithPageable(){
        Pageable pageable = PageRequest.of(0, 10, Sort.by("mno").descending());
        Page<Memo> result = memoRepository.findByMnoBetween(10L, 50L, pageable);

        result.get().forEach(memo -> System.out.println(memo));
    }
Hibernate: 
    select
        memo0_.mno as mno1_0_,
        memo0_.memo_text as memo_tex2_0_ 
    from
        tbl_memo memo0_ 
    where
        memo0_.mno between ? and ? 
    order by
        memo0_.mno desc limit ?
Hibernate: 
    select
        count(memo0_.mno) as col_0_0_ 
    from
        tbl_memo memo0_ 
    where
        memo0_.mno between ? and ?
Memo(mno=50, memoText=테스트_____50)
Memo(mno=49, memoText=테스트_____49)
Memo(mno=48, memoText=테스트_____48)
Memo(mno=47, memoText=테스트_____47)
Memo(mno=46, memoText=테스트_____46)
Memo(mno=45, memoText=테스트_____45)
Memo(mno=44, memoText=테스트_____44)
Memo(mno=43, memoText=테스트_____43)
Memo(mno=42, memoText=테스트_____42)
Memo(mno=41, memoText=테스트_____41)

 

쿼리 메서드를 이용해서 deleteBy로 메서드의 이름을 시작하면 조건에 맞는 데이터를 삭제하는 것도 가능합니다. mno가 10보다 작은 데이터를 삭제하는 테스트를 만들어보겠습니다.

 

 

public interface MemoRepository extends JpaRepository<Memo, Long> {
    List<Memo> findByMnoBetweenOrderByMnoDesc(Long from, Long to);

    Page<Memo> findByMnoBetween(Long from, Long to, Pageable pageable);

    void deleteMemoByMnoLessThan(Long num);
}

 

 

    @Commit
    @Transactional
    @Test
    public void testDeleteQueryMethod(){
        memoRepository.deleteMemoByMnoLessThan(10L);
    }
Hibernate: 
    select
        memo0_.mno as mno1_0_,
        memo0_.memo_text as memo_tex2_0_ 
    from
        tbl_memo memo0_ 
    where
        memo0_.mno<?
Hibernate: 
    delete 
    from
        tbl_memo 
    where
        mno=?
Hibernate: 
    delete 
    from
        tbl_memo 
    where
        mno=?
Hibernate: 
    delete 
    from
        tbl_memo 
    where
        mno=?
       
       
       
       

deleteBy는 10회가 하나씩 이루어집니다. 그래서 @Query를 사용해서 개선합니다.

 

 

Spring Data JPA가 제공하는 쿼리 메서드는 검색을 구현할 경우 편리하지만 조인이나 복잡한 조건을 처리해야할 경우 불편해질 수 있습니다. @Query의 value는 JPQL로 작성하는데 흔히 객체지향 쿼리라고 불립니다. 

 

@Query를 이용하면

필요한 데이터만 선별적으로 추출하는 기능이 가능하다.

데이터베이스에 맞는 순수한 SQL을 사용하는 기능

insert update date와 같은 select가 아닌 DML을 처리하는 기능 (@Modifying과 함께 사용)

 

객체지향 쿼리는 테이블 대신 엔티티 클래스를 이용하고, 테이블의 컬럼 대신 클래스에 선언된 필드를 이용해서 작성합니다. JPQL은 SQL과 상당히 유사해서 간단한 기능을 제작하는 경우 추가적인 학습 없이도 적용 가능합니다. 

mno 의 역순으로 정렬하는 기능을 만들려면

@Query("select m from Memo m odrer by m.mno desc")

List<Memo> getListDesc();

 

JPQL은 데이터베이스의 테이블 대신에 엔티티 클래스의 멤버 변수를 이용하여 SQL과 비슷한 JPQL을 작성합니다. JPQL이 SQL과 유사하듯 실제 SQL에서 사용되는 함수들도 JPQL에서 동일하게 사용됩니다. avg(), count(),group by, order by

 

@Query의 파라미터 바인딩

'?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를 사용하는 경우에도 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);

 

 

@Query의 장점 중 하나는 쿼리 메서드의 경우 엔티티 타입의 데이터만 추출하지만 @Query는 현재 필요한 데이터만 Object[]의 형태로 선별적으로 추출할 수 있다는 점입니다. JPQL을 이용할 때 경우에 따라 JOIN이나 GROUP BY를 이용하는 경우가 종종 있는데, 이럴 때는 적당한 엔티티 타입이 존재하지 않는 경우가 많기 때문에 이런 상황에서 Object[]타입을 리턴 타입으로 지정할 수 있습니다. 

 

 

@Query의 강력한 기능인 데이터베이스 고유의 SQL을 그대로 활용하는 것입니다.

 

@Query(value="select * from memo where mno > 0", nativeQuery = true)

List<Object[]> getNativeResult();