Spring Data JPA 에서 제공하는 @Query 어노테이션을 이용하면 SQL과 유사한 JPQL(JAVA)이라는 객체지향 쿼리 언어를 통해 복잡한 쿼리도 처리가 가능합니다. SQL과 문법 자체가 유사하기 때문에 기존에 SQL을 사용하셨던 분들은 쉽게 배울 수 있습니다.
SQL의 경우 데이터베이싀 테이블을 대상으로 쿼리를 수행하고 JPQL은 엔티티 객체를 대상으로 쿼리를 수행합니다.
테이블이 아닌 객체를 대상으로 검색하는 객체지향 쿼리입니다.
JPQL은 SQL을 추상화 해서 사용하기 때문에 특정 데이터베이스 SQL에 의존하지 않습니다. 즉 JPQL로 작성을 했다면 데이터베이스가 변경되어도 에플리케이션이 영향을 받지 않습니다.
@Query 는 Entity의 JpaRepository를 상속받는 인터페이스에 정의하게 됩니다.
기본적인 작성 방법은 from 구문에 Entity의 객체를 선언하여 해당 객체의 속성명을 통해서
조건과 파라미터를 작성하게 됩니다.
주의할점은 각 문장에 끝에 띄어쓰기를 추가하도록 해야합니다. 각문장이 문자열로 이어져있기때문에
문자의 처음 또는 끝에 띄어쓰기가 없는 경우 한 문장으로 인식되어 정상적인 문장이 아니게 됩니다.
public interface UserRepository extends JpaRepository<User, String> {
@Query(value = "select user " +
"from User user " +
"where user.name = :name")
List<User> findByName(@Param("name") String name);
//@Query 어노테이션을 이용해서 상품 데이터 조회
//상품 항세설명 을 파라미터로 받아 해당 내용을 상품 상세 설명에 포함하고 있는 데이터를 조회하며 정렬 순서는 가격이 높은 순 조회
// 파라미터에 @Param 어노테이션을 이용하여 파라미터로 넘어온 값을 JPQL에 들어갈 변수로 지정할수 있음
//현재 itemDetail 변수를 "like % %"사이에 :"itemDetail"로 값이 들어가도록 작성
@Query("select i from It8em i where i.itemDetail like " +
"%:itemDetail% order by i.price desc")//JPQL로 작성한 쿼리문
List<Item> findByItemDetail(@Param("itemDetail") String itemDetail);
@Param 어노테이션을 이용하여 변수 JPQL에 전달하는 대신
파라미터의 순서를 이용해 전달해줄 수도 있습니다. 그럴경우 :itemDetail' 대신 첫 번째 파라미터를 전달하겠다는 '?1' 이라는 표현을 사용하면 됩니다. 하지만 파라미터의 순서가 달라지면 해당 쿼리문이 제대로 동작하지 않을 수 있기 때문에
좀더 명시적인 방법인 @Param 어노테이션을 이용하는 방법을 추천합니다.
nativeQuery
만약 기존의 데이터베이스에서 사용하던 쿼리를 그대로 사용해야 할 때는 @Query의 nativeQuery
속성을 사용하면 기존 쿼리를 그대로 활용할 수 있습니다. 하지만 특정 데이터베이스에 종속되는 쿼리문을 사용하기 때문에 데이터베이스에 대해 독립적이라는 장점을 잃어버립니다. 기존에 작성한 통계용 쿼리처럼 복잡한 쿼리를 그대로 사용해야 하는 경우 활용할 수 있습니다.
//기존의 데이터베이스에서 사용하던 쿼리를 그대로 사용해야 할때@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);
value 안에 네이티브 쿼리문을 작성하고 "nativeQuery=true"를 지정합니다.
위 예제와 같은 결과 값을 출력할 수 있습니다.
@Test
@DisplayName("nativeQuery 속성을 이용한 상품 조회 테스트")
public void findByItemDetailByNative(){
this.createItemList();
List<Item> itemList= itemRepository.findByItemDetailByNative("테스트 상품 상세 설명");
for(Item item : itemList){
System.out.println(item.toString());
}
}
단점
@Query 어노테이션을 이용한 방법에는 단점이 있습니다. @Query 어노테이션 안에 JPQL문법으로 문자열을 입력하기
때문에 잘못 입력하면 컴파일 시점에 에러를 발견할 수 없습니다. 에러는 가능한 빨리 발견하는 것이 가장 좋습니다.
이를 보안할 수 있는 방법으로 Querydsl이 있습니다.
Querydsl
작성한 Query 중에 오타가 있는경우 어플리케이션을 실행하기 전에는 오류가 있다는 것을 알 수 없습니다.
이때 도와준ㄴ 것이 Querydsl입니다. JPQL을 코드로 작성할 수 있도록 도와주는 빌더 API입니다.
Querydsl은 소스코드로 SQL문을 문자열이 아닌 코드로 작성하기 대문에 컴파일러의 도움을 받을 수 있습니다.
소스 작성 시 오타가 발생하면 개발자에게 오타가 있음을 바로 알려줍니다.
또한 동적 으로 쿼리를 생성해주는것이 아주 큰 장점 입니다. JPQL은 문자를 계속 더해야 하기 때문에
작성이 힘듭니다 Querydsl 의 장점은 아래와 같습니다
- 고정된 SQL문이 아닌 조건에 맞게 동적으로 쿼리를 생성할 수 있습니다
- 비슷한 쿼리를 재사용할 수 있으며 제약 조건 조립 및 가독성을 향상시킬 수 있습니다.
- 문자열이 아닌 자바 소스코드로 작성하기 때문에 컴파일 시점에 오류를 발견할 수 있습니다.
- IDE의 도움을 받아서 자동 완성 기능을 이용할 수 있기 때문에 생산성을 향상시킬 수 있습니다.
Qdomain 생성하는 플러그인 입니다. 엔티티를 기반으로 접두사(prefix)로 'Q'가 붙는 클래스들을 자동으로 생성해주는 플러그인입니다. Querydsl을 통해서 쿼리를 생성할 때Qdomain 객체를 사용합니다.
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>5.0.0</version>
</dependency>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
ItemRepositoryTest
@SpringBootTest
@TestPropertySource(locations="classpath:application-test.properties")
class ItemRepositoryTest {
@PersistenceContext
EntityManager em;
@Test
@DisplayName("Querydsl 조회 테스트1")
public void queryDslTest(){
this.createItemList();
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
QItem qItem = QItem.item;
JPAQuery<Item> query = queryFactory.selectFrom(qItem)
.where(qItem.itemSellStatus.eq(ItemSellStatus.SELL))
.where(qItem.itemDetail.like("%"+ "테스트 상품 상세 설명"+"%"))
.orderBy(qItem.price.desc());
List<Item> itemList = query.fetch();
for(Item item : itemList){
System.out.println(item.toString());
}
}
1.영속성 컨텍스트를 사용하기 위해 @PersistenceContext 어노테이션을 이용해 EntityManager 빈을 주입합니다.
2.JPAQueryFacotry 를 이용하여 쿼리를 동적으로 생성합니다. 생성자의 파라미터로는 EntityManager 객체를 넣어줍니다
3.Querydsl을 통해 쿼리를 생성하기 위해 플러그인을 통해 자동으로 생성된 QItem 객체를 이용합니다.
4.자바 소스 코드지만 SQL문과 비슷하게 소스를 작성할 수 있습니다.
5.JPAQuery 메소드중 하나인 fetch를 이용해서 쿼리 결과를 리스트로 반환합니다. fetch() 메소드 실행 시점에 쿼리문이 실행됩니다. JPAQuery에서 결과를 반환하는 메소드는 아래 표를 참조하시면 됩니다.
메소드 | 기능 |
List<T> fetch | 조회 결과 리스트 반환 |
T fetch0ne | 조회 대상이 1건인 경우 제네릭으로 지정한 타입 반환 |
Long fetchCOunt() | 조회 대상 개수 반환 |
QueryResult<T> fetchResults() | 조회한 리스트와 전체 개수를 포함한 QueryResults 반환 |
실행된 쿼리문을 확인해 보시면 JPAQuery에 추가한 판매상태 코드와 상품 상세 설명이 where 조건에 추가돼 있고, 상품의 가격이 내림차순으로 정렬돼 데이터를 조회합니다. 이렇게 자바 코드를 이용해서 고정된 쿼리문이 아닌 비즈니스 로직에 따라서 동적으로 쿼리문을 생성해줄 수 있습니다.
public interface ItemRepository extends JpaRepository<Item,Long>,
QuerydslPredicateExecutor<Item> {
QuerydslPredicateExecutor<Item> 인터페이스는 다음과 같이 메소드들이 선언돼 있습니다.
메소드 | 기능 |
long count(Predicate) | 조건에 맞는 데이터의 총 개수 반환 |
boolean exists(Predicate) | 조건에 맞는 데이터 존재 여부 반환 |
Iterable findAll(Predicate) | 조건에 맞는 모든 데이터 반환 |
Page<T> findAll(Predicate, Pageable) | 조건에 맞는 페이지 데이터 반환 |
Iterable findAll(Predicate,Sort) | 조건에 맞는 정렬된 데이터 반환 |
T find0ne | 조건에 맞는 데이터 1개 반환 |
'Spring > Spring JPA' 카테고리의 다른 글
Auditing을 이용한 엔티티 공통 속성 공통화 (0) | 2023.03.06 |
---|---|
영속성 전이 (0) | 2023.03.06 |
연관 관계 매핑 종류 (1) | 2023.03.05 |
엔티티 매핑 관련 어노테이션 (0) | 2023.02.28 |
JPA (0) | 2023.02.28 |