Chap.8 Spring Data JPA 활용
1. JPQL
JPQL은 JPA Query Language의 줄임말이고 JPA에서 사용할 수 있는 쿼리를 의미한다.
JPQL은 SQL문법과 상당히 유사해서 DB와 SQL에 익숙하다면 빠르게 익혀서 사용할 수 있다는 점이 장점이라고 생각한다.
일반적으로 진짜 SQL문과 거의 똑같다고 볼 수 있다.
우선 FROM절에 어떤 데이터를 조회할 것인지에 따라 해당 엔티티 타입이 들어가고 SELECT절에는 해당 테이블의 어떤 정보를 가져올 것인지를 뜻한다. 만약 p.name과 같은 식으로 정의한다면, p 엔티티의 이름만 쭉 나오게 될 것이다.
WHERE절을 통해서 우리가 추출하고자 하는 부분들만 따로 추출해서 사용할 수 있다.
Query Method 살펴보기
하지만, JPQL의 결과는 단건 조회뿐만 아니라 다량의 데이터를 조회할 수도 있기 때문에, List<Product>와 같은 식으로 반환하게 된다.
보통 조회할 때는 findBy, getBy, queryBy, searchBy, streamBy를 통해서 조회하게 된다. JPA를 쓰는 문법을 많이 알고 있다면, JPQL을 도입해서 복잡한 JPA를 쿼리로 풀어내는 것이 효과적이라는 생각이 든다.
OrderBy와 DESC 또는 ASC를 통해서 특정 컬럼의 값을 기준으로 오름차순 또는 내림차순을 수행할 수 있으며, StartingWith, EndingWith를 통해서 해당 문자열의 앞 또는 뒤의 값을 검사하여 조건에 맞는 데이터를 추출한다. 또한, 조건절에는 Like, Top, First, Contain과 같은 키워드를 통해서 특정 조건을 만족시키는 데이터를 추출할 수 있다.
삭제하기 위해서는 deleteBy, removeBy와 같은 키워드를 사용하는데, 둘 다 모두 동일한 기능을 수행하며, deleteBy는 void형식으로 리턴 값이 존재하지 않으며, removeBy는 삭제한 횟수를 반환하게 된다.
여러 조건을 묶어서 동시에 만족하는 경우나, 여러 경우 중 하나만 만족해도 동작하게 하기 위해서 And 또는 Or 키워드를 사용해서 조건절을 조합할 수 있다. 정렬의 경우에도 여러 개의 정렬 조건을 추가할 수 있다.
페이징 처리
페이징이란 데이터베이스의 레코드를 개수로 나눠 페이지를 구분하는 것을 의미합니다. 만약 25개의 레코드가 있다면 레코드를 7개씩, 총 4개의 페이지로 구분하고 그중에서 특정 페이지를 가져오는 것입니다. 흔히 웹 페이지나 웹툰 사이트 같은 곳에 적용돼서 페이지에 맞는 데이터를 요청하는 것이라고 생각하면 됩니다.
JPA에서는 이러한 기능을 제공하기 위해서 Page라는 타입과 Pageable이라는 타입을 지원합니다.
Pageable이라는 타입을 넘겨주기 위해서 Pageable이라는 타입을 구현한 PageRequest를 사용해야 합니다.
위의 표에서 알 수 있다시피, PageRequest.of() 메서드를 통해서 Pageable 타입을 정의합니다.
만약 0페이지에 2번째까지 보고 싶다면, PageRequest.of(0,2)와 같은 식으로 지정하면 됩니다.
@Query 어노테이션 사용하기
이전에는 JPA에서 키워드의 조합을 통해서 데이터를 추출하거나, 정렬, 페이징 처리에 대해서 다뤘습니다.
이번에는 @Query를 통한 쿼리를 작성하고 해당 쿼리에 맞게 데이터를 처리하는 방법에 대해서 다룹니다.
위에서 말했던 대로, JPQL은 SQL과 매우 유사한 형태를 띠고 있다는 것을 알 수 있습니다.
사실 JPA를 통해서 데이터베이스를 사용하거나, 데이터를 처리하기 위해서는 JpaRepository를 상속하는 자식 레포지토리에서 JPQL을 사용해서 데이터를 추출할 수 있었습니다.
하지만 이렇게 JPA를 사용하는 경우, 만약 엔티티의 컬럼이 많다면, 한 건만 조회해도 많은 데이터가 추출되는 것을 알 수 있습니다.
이런 경우 해당 데이터를 조회할 때, 많은 시간이 소모되게 됩니다. 추가적으로 1대다의 관계, FK를 통해 데이터를 조회하는 경우 더욱 많은 시간을 소모하게 될 수도 있습니다.
이러한 경우에 @Query라는 어노테이션을 사용한다면, 개발자는 자신에게 필요한 컬럼만 조회하는 방식으로 개발을 진행할 수 있습니다.
2. Query DSL
이전에서는 @Query 어노테이션을 사용해 직접 JPQL의 쿼리를 작성하는 방법에 대해서 알아봤습니다.
메서드의 이름을 기반으로 생성하는 JPQL의 한계는 @Query 어노테이션을 통해 대부분 해소할 수 있지만, 직접 문자열을 입력하기 때문에 컴파일 시점에서는 에러를 잡지 못하고, 런타임 에러가 발생할 수도 있습니다. 쿼리의 문자열이 잘못된 경우, 애플리케이션이 실행된 후 로직이 실행되고 나서야 오류를 발견할 수 있습니다. 이러한 이유로 개발 환경에서는 문제가 없는 것처럼 보이다가 실제 운영 환경에서 애플리케이션을 배포하고 나서 오류가 발견되는 리스크를 유발합니다.
이러한 문제점을 해결하기 위해서 Query DSL을 사용합니다.
Query DSL이란?
Query DSL은 정적 타입을 이용해서 SQL과 같은 쿼리를 생성할 수 있도록 지원하는 프레임워크입니다.
문자열이나 XML 파일을 통해 쿼리를 작성하는 대신 QueryDSL이 제공하는 플루언트(Fluent API)를 사용해서 쿼리를 생성할 수 있습니다.
의존성 추가
또한, Plugin Tag에 QueryDSL을 사용하기 위한 APT 플러그인을 추가해야 합니다.
이후 Maven의 complie 단계를 통해서 빌드작업을 수행하면, 우리가 정의한 엔티티 클래스 앞에 Q가 붙은 Q도메인 클래스가 생성된 것을 확인할 수 있습니다.
QueryDSL은 지금까지 작성했던 엔티티 클래스와 Q도메인이라는 쿼리 타입의 클래스를 자체적으로 생성해서 메타데이터로 사용하는데, 이를 통해서 SQL과 같은 쿼리를 생성해서 제공합니다.
Example Code
일반적으로 위의 사진과 같은 형식으로 Query DSL을 사용합니다.
마지막의 fetch 메서드를 사용하면 Product가 List 형식으로 반환되게 됩니다.
만약, 단건만 조회하고 싶다면, fetchFirst, fetchOne과 같은 형식으로 변경해야 합니다.
해당 코드에서는 사용하기 위해서 JPAQuery(entityManager)라는 객체를 통해서 JPAQuery <Product> 타입의 객체를 선언했습니다.
하지만 QueryDSL을 사용하기 위해서 매번 똑같은 코드를 사용하게 된다면, 번거로울 수 있습니다.
따라서 QueryDSLConfig를 사용해서 @Bean으로 등록시켜 준 뒤 사용한다면 매번 번거로운 선언단계를 건너뛸 수 있습니다.
QuerydslPredicateExecutor Interface
QuerydslPredicateExecutor는 JpaRepository에서 함께 리포지토리에서 QueryDSL을 사용할 수 있게 인터페이스를 제공합니다.
위처럼 interface를 만들게 되면, 각 메서드별로 매개변수를 Predicate타입으로 넣어주어야 합니다.
Predicate는 표현식을 작성할 수 있게 QueryDSL에서 제공하는 인터페이스입니다.
하지만, 위의 방식을 사용할 경우, 더욱 편하게 QueryDSL을 사용할 수 있지만, JOIN이나 FETCH기능을 사용할 수 없다는 단점이 존재합니다.
QuerydslRepositorySupport
QuerydslRepositorySupport 클래스 역시 QueryDSL 라이브러리를 사용하는 데 많은 기능을 제공합니다.
가장 보편적으로 사용하는 방식은 customREpository를 활용해 리포지토리를 구현하는 방식입니다.
위와 같이 인터페이스를 생성하고 쿼리로 구현하고자 하는 메서드를 정의하는 작업을 수행합니다.
다음으로는 위의 인터페이스를 구현하는 CumstomImpl 클래스를 만듭니다.
해당 클래스에서는 QueryDSL을 사용하기 위해서 QuerydslRepositorySupport를 상속받고 이전에 생성한 ProductRepositoryCustom을 구현합니다. 상속받으면 5~7번 라인같이 생성자를 통해서 도메인 클래스를 부모 클래스에 전달해야 합니다.
다음으로, 인터페이스에서 정의한 메서드를 구현합니다. 이 과정에서 Q도메인 클래스인 QProduct를 사용해 QuerydslRepositorySupport가 제공하는 기능을 사용합니다.
한 걸음 더.. JPA Auditing 적용
JPA에서 Audit이란 "감시하다"라는 뜻으로, 각 데이터마다 누가, 언제 데이터를 생성했고 변경했는지 감시한다라는 의미로 사용됩니다.
흔히 엔티티의 생성일자, 변경일자와 같은 값을 자동으로 주입하기 위해서 사용하게 됩니다.
생성일자, 변경일자뿐만 아니라, 생성주체, 변경주체에 대한 정보도 수집합니다.
해당 기능을 활성화하기 위해서는 가장 루트에 있는 실행클래스 위에 @EnableJpaAuditing 어노테이션을 적용해야 합니다.
하지만 이렇게 선언할 경우, Controller를 테스트할 때 문제가 발생할 수도 있기 때문에 별도의 Config파일을 작성해서 해당 Configuration에 EnableJpaAuditing 어노테이션을 추가합니다.
이렇게 선언하면 JpaAuditing을 하기 위한 설정이 끝났습니다.
이전에 말했던 것처럼 각 엔티티별로 중복되는 컬럼을 BaseEntity로 선언해서 상속받아 엔티티를 구현하면 중복되는 코드 조각들을 최소화할 수 있습니다.
Getter와 Setter, ToString 어노테이션은 너무 익숙하니까 넘어가고, MappedSuperClass와 EntityListeners만 설명하겠습니다.
MappedSuperClass는 JPA의 엔티티 클래스가 상속받을 경우, 자식 클래스에게 매핑 정보를 전달하는 어노테이션입니다.
EntityListeners 어노테이션은 엔티티의 Auditing 정보를 주입하는 JPA 엔티티 리스너 클래스입니다.
이와 같이 BaseEntity를 상속받는 경우, 코드가 줄어드는 것을 알 수 있습니다. 테스트 코드를 통해서 어떻게 데이터가 들어가는지 확인해 보면,
위와 같이 createdAt이 잘 나오는 것을 확인할 수 있습니다.
'책' 카테고리의 다른 글
[Book] 스프링 부트 핵심 가이드 Chap.10 - 유효성 검사와 예외 처리 (0) | 2024.07.28 |
---|---|
[Book] 스프링 부트 핵심 가이드 Chap.9 (1) | 2024.07.21 |
[Book] 스프링 부트 핵심 가이드 Chap.6 (0) | 2024.07.02 |
[Book] 스프링 부트 핵심 가이드 Chap.5 (1) | 2024.07.02 |
[Book] 스프링 부트 핵심 가이드 Chap.4 (0) | 2024.06.25 |