쿼리 메서드 (Query Methods) 기능과 @Query

작성일

쿼리 메서드와 JPQL(Java Persistence Query Language) 은 객체 지향 쿼리

Spring Data JPA 는 다양한 검색 조건을 위해 다음과 같은 방법 제공

  • 쿼리 메서드: 메서드의 이름 자체가 쿼리의 구문으로 처리되는 기능
  • @Query: SQL 과 유사하게 엔티티 클래스의 정보를 이용해서 쿼리를 작성하는 기능
  • Querydsl 등의 동적 쿼리 처리 기능

쿼리 메서드(Query Methods)

쿼리 메서드란? 메서드의 이름 자체가 질의(query)문

쿼리 메서드 레퍼런스 문서

Spring Data JPA - Reference Documentation

쿼리 메서드의 리턴 타입

  • select 를 하는 작업 → List 타입이나 배열
  • 파라미터에 Pageable 타입을 넣는 경우 → Page

MemoRepository.java

package org.zerock.ex2.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
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);
		// 쿼리 메서드 + Pageable
    Page<Memo> findByMnoBetween(Long from, Long to, Pageable pageable);
}

쿼리 메서드의 경우 이름이 길어지고 혼동하기 쉬워 이를 보완하기 위해 Pageable 파라미터를 같이 사용. 정렬에 관한 부분은 좀 더 간략한 메서드를 생성할 수 있음

MemoRepositoryTests.java

package org.zerock.ex2.repository;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.test.annotation.Commit;
import org.springframework.transaction.annotation.Transactional;
import org.zerock.ex2.entity.Memo;

import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;

@SpringBootTest
class MemoRepositoryTests {

    @Autowired
    MemoRepository memoRepository;

		...

    @Test
    @DisplayName("쿼리 메서드")
    public void testQueryMethods() {
        List<Memo> list = memoRepository.findByMnoBetweenOrderByMnoDesc(70L, 80L);

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

    @Test
    @DisplayName("쿼리 메서드와 Pageable")
    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));
    }
}

deleteBy 로 시작하는 삭제 처리

쿼리 메서드를 이용해 deleteBy 로 메서드의 이름이 시작하면 특정 조건에 맞는 데이터를 삭제하는 것도 가능

MemoRepository.java

package org.zerock.ex2.repository;

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

import java.util.List;

public interface MemoRepository extends JpaRepository<Memo, Long> {

		...

    void deleteMemoByMnoLessThan(Long num);
}

MemoRepositoryTests.java

package org.zerock.ex2.repository;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.test.annotation.Commit;
import org.springframework.transaction.annotation.Transactional;
import org.zerock.ex2.entity.Memo;

import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;

@SpringBootTest
class MemoRepositoryTests {

    @Autowired
    MemoRepository memoRepository;

		...

    @Commit
    @Transactional
    @Test
    @DisplayName("deleteBy")
    public void testDeleteQueryMethods(){
        memoRepository.deleteMemoByMnoLessThan(10L);
    }
}

@Transactional

→ select 문으로 해당 엔티티 객체들을 가져오는 작업과 각 엔티티를 삭제하는 작업이 같이 이루어지기 때문에 Transactional 어노테이션 사용해야 함

@Commit

→ 최종 결과를 커밋하기 위해 사용. 이를 적용하지 않으면 기본적으로 Rollback 처리 되어서 결과가 반영 안됨

deleteBy 는 실제 개발에 많이 사용 안함

→ why? 한 번에 삭제가 이루어지는 것이 아닌 각 엔티티 객체를 하나씩 삭제하기 때문

→ 따라서 개발 시에는 deleteBy 보다 @Query 를 이용하는 것이 효율적

@Query 어노테이션

간단한 처리는 쿼리 메서드를 이용하고 보통은 @Query 를 사용함

@Query 의 경우 메서드의 이름과 상관없이 메서드에 추가한 어노테이션을 통해 처리가 가능

@Query 의 value 는 JPQL 로 작성하는데 객체지향 쿼리라고 부름

  • 필요한 데이터만 선별적으로 추축하는 기능이 가능
  • 데이터베이스에 맞는 순수한 SQL 을 사용하는 기능
  • DML 등을 처리하는 기능 (@Modifying 과 함께 사용)

객체지향 쿼리는 테이블 대신 엔티티 클래스, 테이블의 칼럼 대신에 클래스에 선언된 필드를 이용

// mno의 역순으로 정렬
@Query("select m from Memo m order by m.mno desc")
List<Memo> getListDesc();

@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 와 페이징 처리

@Query 를 이용하는 경우에도 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(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);

Native SQL 처리

SQL 구문을 그대로 활용. 이는 데이터베이스에 독립적으로 구현이 가능하다는 장점은 사라짐. 하지만 경우에 따라 복잡한 구문을 처리하기 위해 사용

@Query(value = "select * from memo where mno > 0", nativeQuery = true)
List<Object[]> getNativeResult();

카테고리: