JeongJin's Blog

08. Spring Data JPA 활용 (3) 본문

Book Study/스프링 부트 핵심 가이드

08. Spring Data JPA 활용 (3)

정진킴 2023. 11. 8. 15:58

8.5 @Query 어노테이션 사용하기

  • JPQL을 사용하면 JPA 구현체에서 자동으로 쿼리 문장을 해석하고 실행하게 된다.
  • 만약 데이터베이스를 다른 데이터베이스로 변경할 일이 없다면 직접 해당 데이터베이스에 특화된 SQL을 작성할 수 있으며, 주로 튜닝된 쿼리를 사용하고자 할 때 직접 SQL을 작성한다.
@Query("SELECT p FROM Product AS p WHERE p.name = ?1"
  • 조건문에서 '?1' 은 파라미터를 전달받기 위한 인자에 해당한다.
  • 주의할 점은 파라미터의 순서가 바뀌면 오류가 발생할 가능성이 있어 @Param 어노테이션을 사용하는 것이 좋다.
@Query("SELECT p FROM Product AS p WHERE p.number = :name")
List<Product> findByNameParam(@Param("name") String name);
  • 파라미터를 바인딩하는 방식으로 메서드를 구현하면 코드의 가독성이 높아지고 유지보수가 수월해진다.
@Query("SELECT p.name, p.price, p.stock FROM Product AS p WHERE p.name = :name")
List<Object[]) findByNumberParam2(@Param("name") String name);
  • SELECT에 가져오고자 하는 컬럼을 지정한다.
  • 리턴 타입으로는 Object 배열의 리스트 형태로 정의 한다.

8.6 QueryDSL 적용하기

  • @Query 어노테이션의 단점으로 직접 문자열을 입력하기 때문에 컴파일 시점에 에러를 잡지 못하고 Runtime 에러가 발생할 수 있다.
  • 운영 환경에 배포하고 나서 오류가 발견되는 리스크를 유발한다.

8.6.1 QueryDSL 이란?

8.6.2 QueryDSL의 장점

  • IDE가 제공하는 코드 자동 완성 기능을 사용할 수 있다.
  • 문법적으로 잘못된 쿼리를 허용하지 않는다. 따라서 정상적으로 활용된 QueryDSL은 문법 오류를 발생시키지 않는다.
  • 고정된 SQL 쿼리를 작성하지 않기 때문에 동적으로 쿼리를 생성할 수 있다.
  • 코드로 작성하므로 가독성 및 생산성이 향샹된다.
  • 도메인 타입과 프로퍼티를 안전하게 참조할 수 있다.

8.6.3 QueryDSL을 사용하기 위한 프로젝트 설정

  • gradle 방식으로 설정
//querydsl 추가
buildscript {
    dependencies {
        classpath("gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:1.0.10")
    }
}

// querySQL
apply plugin: "com.ewerk.gradle.plugins.querydsl"

dependencies {
    ...

    //querydsl 추가
    testImplementation("org.junit.vintage:junit-vintage-engine") {
        exclude group: "org.hamcrest", module: "hamcrest-core"
    }

    //querydsl 추가
    implementation 'com.querydsl:querydsl-jpa'
    //querydsl 추가
    implementation 'com.querydsl:querydsl-apt'
}

//querydsl 추가
def querydslDir = "$projectDir/build/generated"

querydsl {
    library = "com.querydsl:querydsl-apt"
    jpa = true
    querydslSourcesDir = querydslDir
}

sourceSets {
    main {
        java {
            srcDirs = ['src/main/java', querydslDir]
        }
    }
}

compileQuerydsl{
    options.annotationProcessorPath = configurations.querydsl
}

configurations {
    querydsl.extendsFrom compileClasspath
}

querydslDir 정의된 하위에 Q도메인 클래스 생성

8.6.4 기본적인 QueryDSL 사용하기

Test 클래스 생성 후 상단에 @SpringBootTest 를 넣어주어야 EntityManager nullPointException 에러가 발생하지 않는다.!!

 

@PersistenceContext
EntityManager entityManager;

@Test
void queryDslTest() {
    JPAQuery<Product> query = enw JPAQuery(entityManager);
    QProduct qProduct = QProduct.product;

    List<Product> productList = query
        .form(qProduct)
        .where(qProduct.name.eq("펜"))
        .orderBy(qProduct.price.asc())
        .fetch();

}
  • JPAQuery 객체를 생성한다
  • List 타입으로 받기 위해서 fetch() 를 사용
    • List fetch(): 조회 결과를 리스트로 반환
    • T fetchOne(): 단 건의 조회 결과를 반환
    • T fetchFirst(): 여러 건의 조회 결과 중 1건을 반환, 내부 로직에서 'limit(1).fetchOne()' 으로 구현되어 있음
    • Long fetchCount(): 조회 결과의 개수를 반환
    • QueryResult fetchResults(): 조회 결과 리스트와 개수를 포함한 QueryResults를 반환
@PersistenceContext
EntityManager entityManager;

@Test
void QueryDslTest2() {
    JPAQueryFactory jpaQueryFactory = enw JPAQueryFactory(entityManager);
    QProduct qProduct = QProduct.product();

    List<Product> productList = jpaQueryFactory.selectFrom(qProduct)
        .where(qProduct.name.eq("펜"))
        .orderBy(qProduct.price.asc())
        .fetch();
}
  • JPAQueryFactory를 활용한 쿼리 작성
  • JPAQuery 와 다르게 select 절부터 작성 가능
  • 전체 컬럼을 조회하지 않고 일부만 조회하고 싶다면
    • selectFrom(gProduct) -> select(qProduct.name, qProduct.stock).from(qProduct) 로 수정한다.
  • 비지니스 로직에서 활용하려면 config 클래스에 bean을 등록한다.
@Configuration
public class QueryDSLConfiguration {
    @PersistenceContext
    EntityManager entitiManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}
  • JPAQueryFactory를 초기화 하지 않고 @Autowired로 빈을 주입받아서 바로 사용 가능하다
@Autowired
JPAQueryFactory jpaQueryFactory;

@Test  
void queryDslTest4() {  
QProduct qProduct = QProduct.product;

List<String> productList = jpaQueryFactory
    .select(qProduct.name)
    .from(qProduct)
    .where(qProduct.name.eq("펜"))
    .orderBy(qProduct.price.asc())
    .fetch();
}

8.6.5 QuerydslPredicateExecutor, QuerydslRepositorySupport 활용

  • Spring Data JPA 에서는 QueryDSL을 더욱 편하게 사용할 수 있게 QuerydslPredicateExecutor 인터페이스와 QuerydslRepositorySupport 클래스를 제공한다.

QuerydslPredicateExecutor 인터페이스

  • JpaRepository와 함께 레포지토리에서 QueryDSL을 사용할 수 있게 인터페이스를 제공한다.

public interface QProductRepository extends JpaRepository<Product, Long>, QuerydslPredicateExecutor {
}


-   QuerydslPredicateExcutor 인터페이스의 메서드 대부분은 Predicate 타입을 받는다.

@SpringBootTest
public class QProductRepositoryTest {
@Autowired
QProductRepository qProductRepository;

@Test
public void queryDSLTest1() {
    Predicate predicate = QProduct.product.name.containsIgnoreCase("펜")
        .and(QProduct.product.price.between(1000, 2500));

    Optional<Product> foundProduct = qProductRepository.findOne(predicate);

}

}


-   findOne() 는 QuerydslPredicateExecutor 내부에 메서드로 구현되어 있다.

**QuerydslRepositorySupport 추상 클래스 사용하기**

-   가장 보편적으로 사용하는 방식은 CustomRepository를 활용해 레포지토리를 구현하는 방법

public interface ProductRepositoryCustom {
List findByName(String name);
}

@Component
public class ProductRepositoryCustomImpl extends QuerydslRepositorySuppory implements
ProductRepositoryCustom {
public ProductRepositoryCustomImpl() {
super(Product.class);
}

@Override
public List<Product> findByName(String name) {
    QProduct qProduct = QProduct.product;

    List<Product> productList = from(product)
        .wehre(product.name.eq(name))
        .select(product)
        .fetch();

    return productList;
}

}
```

'Book Study > 스프링 부트 핵심 가이드' 카테고리의 다른 글

09. 연관관계 매핑  (0) 2023.11.15
08. Spring Data JPA 활용 (4)  (2) 2023.11.08
08. Spring Data JPA 활용 (2)  (0) 2023.11.07
08. Spring Data JPA 활용 (1)  (0) 2023.11.06
06. 데이터베이스 연동(7)  (2) 2023.11.03