쿼리 메소드를 사용하다보면 한 가지 의문이 생긴다. 한두 개 정도의 조건을 이용해서 상품 데이터를 조회할 때는 괜찮지만, 조건이 많아질 때 쿼리 메소드를 선언하면 이름이 정말 길어지기도 하기 때문이다. 그럴 경우 오히려 이름을 보고 어떻게 동작하는지 해석하는 게 더 힘들 수도 있다. 간단한 쿼리를 처리할 때는 유용하지만 복잡한 쿼리를 다루기에는 적합하지 않다. 이를 보완하기 위한 방법이 있다.
Spring Data JPA에서 제공하는 @Query 어노테이션을 이용하면 SQL과 유사한 JPQL(Java Persistence Query Language)이라는 객체지향 쿼리 언어를 통해 복잡한 쿼리도 처리가 가능하다. SQL과 문법 자체가 매우 유사하기 때문에 기존에 SQL을 사용했던 사람이라면 쉽게 배울 수 있을 것이다. SQL의 경우 데이터베이스의 테이블을 대상으로 쿼리를 수행하고, SPQL은 엔티티 객체를 대상으로 쿼리를 수행한다. 테이블이 아닌 객체를 대상으로 검색하는 객체지향 퀄이ㅣ다. JPQL은 SQL을 추상화해서 사용하기 때문에 특정 데이터베이스 SQL에 의존하지 않는다. 즉, JPQL로 작성을 했다면 데이터베이스가 변경되어도 애플리케이션이 영향을 받지 않는다.
@Query 어노테이션을 이용하여 상품 데이터를 조회하는 예제를 진해
public interface ItemRepository extends JpaRepository<Item, Long> {
@Query("select i from Item i where i.itemDetail like" +
"%:itemDetail% order by i.price desc")
List<Item> findByItemDetail(@Param("itemDetail") String itemDetail);
}
@Query 어노테이션 안에 JPQL로 작성한 쿼리문을 넣어줬다. from 뒤에는 엔티티 클래스로 작성한 Item을 지정해주었고, Item으로부터 데이터를 select하겠다는 것을 의미한다.
파라미터 @Param 어노테이션을 이용하여 파라미터로 넘어온 값을 JPQL에 들어갈 변수로 지정해줄 수 있다. 현재는 itemDetail 변수를 "like % %"사이에 ":itemDetail"로 값이 들어가도록 작성했다.
@Param 어노테이션을 이용해 변수를 JPQL에 전달하는 대신 파라미터의 순서를 이용해 전달해줄 수도 있다. 그럴 경우 ":itemDetail" 대신 첫 번째 파라미터를 전달하겠다는 "?1"이라는 표현을 사용하면 된다. 하지만 파라미터의 순서가 달라지면 해당 쿼리문이 제대로 동작하지 않을 수 있기 때문에 좀 더 명시적인 방법인 @Param 어노테이션을 이용하는 방법을 추천한다.
@Test
@DisplayName("@Query를 이용한 상품 조회 테스트")
public void findByItemDetailTest() {
this.createItemList();
List<Item> itemList = itemRepository.findByItemDetail("테스트 상품 상세 설명");
for (Item item : itemList) {
System.out.println(item.toString());
}
}
**실행결과**
Item(id=10, itemNm=테스트 상품10, price=10010, stockNumber=100, itemDetail=테스트 상품 상세 설명10, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.896615, updateTime=2021-12-02T11:52:16.896615)
Item(id=9, itemNm=테스트 상품9, price=10009, stockNumber=100, itemDetail=테스트 상품 상세 설명9, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.894616, updateTime=2021-12-02T11:52:16.894616)
Item(id=8, itemNm=테스트 상품8, price=10008, stockNumber=100, itemDetail=테스트 상품 상세 설명8, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.892615, updateTime=2021-12-02T11:52:16.892615)
Item(id=7, itemNm=테스트 상품7, price=10007, stockNumber=100, itemDetail=테스트 상품 상세 설명7, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.890614, updateTime=2021-12-02T11:52:16.890614)
Item(id=6, itemNm=테스트 상품6, price=10006, stockNumber=100, itemDetail=테스트 상품 상세 설명6, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.888614, updateTime=2021-12-02T11:52:16.888614)
Item(id=5, itemNm=테스트 상품5, price=10005, stockNumber=100, itemDetail=테스트 상품 상세 설명5, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.886614, updateTime=2021-12-02T11:52:16.886614)
Item(id=4, itemNm=테스트 상품4, price=10004, stockNumber=100, itemDetail=테스트 상품 상세 설명4, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.884612, updateTime=2021-12-02T11:52:16.884612)
Item(id=3, itemNm=테스트 상품3, price=10003, stockNumber=100, itemDetail=테스트 상품 상세 설명3, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.883611, updateTime=2021-12-02T11:52:16.883611)
Item(id=2, itemNm=테스트 상품2, price=10002, stockNumber=100, itemDetail=테스트 상품 상세 설명2, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.881611, updateTime=2021-12-02T11:52:16.881611)
Item(id=1, itemNm=테스트 상품1, price=10001, stockNumber=100, itemDetail=테스트 상품 상세 설명1, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.831600, updateTime=2021-12-02T11:52:16.831600)
테스트 코드 실행 결과 상품 상세 설명에 "테스트 상품 상세 설명"을 포함하고 있는 상품 데이터 10개가 가격이 높은 순부터 조회되는 것을 확인할 수 있다. 복잡한 쿼리의 경우 @Query 어노테이션을 사용해서 조회하면 된다.
만약 기존의 데이터베이스에서 사용하던 쿼리를 그대로 사용해야 할 때는 @Query의 nativeQuery 속성을 사용하면 기존 쿼리를 그대로 활용할 수 있다. 하지만 특정 데이터베이스에 종속되는 쿼리문을 사용하기 때문에 데이터베이스에 대해 독립적이라는 장점을 잃어버린다. 기존에 작성한 통계용 쿼리처럼 복잡한 쿼리를 그대로 사용해야 하는 경우 활용할 수 있다.
@Query(value = "select * from item i where i.item_detail like %:itemDetail% order by i.price desc", nativeQuery = true)
List<Item> findByItemDetailByNative(@Param("itemDetail") String itemDetail);
@Test
@DisplayName("nativeQuery 속성을 이용한 상품 조회 테스트")
public void findByItemDetailByNative() {
this.createItemList();
List<Item> itemList = itemRepository.findByItemDetailByNative("테스트 상품 상세 설명");
for (Item item : itemList) {
System.out.println(item.toString());
}
}
**실행결과**
Item(id=10, itemNm=테스트 상품10, price=10010, stockNumber=100, itemDetail=테스트 상품 상세 설명10, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.896615, updateTime=2021-12-02T11:52:16.896615)
Item(id=9, itemNm=테스트 상품9, price=10009, stockNumber=100, itemDetail=테스트 상품 상세 설명9, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.894616, updateTime=2021-12-02T11:52:16.894616)
Item(id=8, itemNm=테스트 상품8, price=10008, stockNumber=100, itemDetail=테스트 상품 상세 설명8, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.892615, updateTime=2021-12-02T11:52:16.892615)
Item(id=7, itemNm=테스트 상품7, price=10007, stockNumber=100, itemDetail=테스트 상품 상세 설명7, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.890614, updateTime=2021-12-02T11:52:16.890614)
Item(id=6, itemNm=테스트 상품6, price=10006, stockNumber=100, itemDetail=테스트 상품 상세 설명6, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.888614, updateTime=2021-12-02T11:52:16.888614)
Item(id=5, itemNm=테스트 상품5, price=10005, stockNumber=100, itemDetail=테스트 상품 상세 설명5, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.886614, updateTime=2021-12-02T11:52:16.886614)
Item(id=4, itemNm=테스트 상품4, price=10004, stockNumber=100, itemDetail=테스트 상품 상세 설명4, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.884612, updateTime=2021-12-02T11:52:16.884612)
Item(id=3, itemNm=테스트 상품3, price=10003, stockNumber=100, itemDetail=테스트 상품 상세 설명3, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.883611, updateTime=2021-12-02T11:52:16.883611)
Item(id=2, itemNm=테스트 상품2, price=10002, stockNumber=100, itemDetail=테스트 상품 상세 설명2, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.881611, updateTime=2021-12-02T11:52:16.881611)
Item(id=1, itemNm=테스트 상품1, price=10001, stockNumber=100, itemDetail=테스트 상품 상세 설명1, itemSellStatus=SELL, regTime=2021-12-02T11:52:16.831600, updateTime=2021-12-02T11:52:16.831600)
해당 게시글은 변구훈, 『스프링 부트 쇼핑몰 프로젝트 with JPA』, 로드북, 2021를 참고하여 작성하였습니다.