COBI-98
은로그
COBI-98
  • 은로그 (79)
    • Back-End (1)
      • Java (5)
      • Spring (16)
      • DB (1)
      • 알고리즘 (7)
      • ETC (2)
    • 개발 일기 (0)
    • 회고 (4)
    • Project (1)
      • 협업프로젝트 (7)
      • 국비프로젝트 (2)
    • Web (2)
      • Server (2)
    • Git (2)
    • CS (0)
    • 코딩테스트 (24)
      • 백준 (17)
      • 프로그래머스 (7)
    • 우아한 테크코스 (5)

블로그 메뉴

  • ✨깃허브
  • 홈
  • 방명록

공지사항

인기 글

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
COBI-98

은로그

[JPA] QueryDSL을 RepositoryImpl로 관리해보자.
Back-End/Spring

[JPA] QueryDSL을 RepositoryImpl로 관리해보자.

2023. 8. 5. 20:45

서론

JPA와 MyBatis의 자바 ORM을 모두 경험한 결과, 

MyBatis는 SQL 쿼리에 더 직접적인 접근을 가지고 JPA는 객체지향적인 접근을 가지고 있어서

정적 쿼리를 다루는 것은 JPA가 비교적 간단하고 직관적이지만

동적 쿼리를 다루는 것은, JPA가 상대적으로 어려움(엔티티와 속성의 제약)이 있다.

 

JPA의 장점을 더 살리기 위해 쿼리를 분리화 해서 유지 관리하기 쉬운 방법을 모색했고,

RepositoryImpl 클래스 내부에서

QueryDSL을 사용하여 쿼리를 생성하고 실행하는 방식을 찾았다.

 

이번 포스팅은 그 방식을 spring 코드 예시로 설명하며 정리해보려고 한다. 

 

Repository에서 QueryDSL 사용하기

public interface OrderRepository extends JpaRepository<Order, Long> {

    @Query("select o from Order o join fetch o.orderItems")
    List<Order> findAllInnerFetchJoin();

    @Query("select distinct o from Order o join fetch o.orderItems")
    List<Order> findAllInnerFetchJoinWithDistinct();

    //...
}

 

현재 OrderRepository가 사용 중인 정적 쿼리(JPQL)들을 QueryDSL로 교체하려고 한다.

Spring Data JPA는 JpaRepository를 상속한 Repository 클래스에서,
Custom Repository 기능을 사용할 수 있도록 하는 기능을 제공한다.

 

이를 RepositoryImpl에 추가하여 사용하면

Spring Data JPA의 편리함과 장점을 그대로 유지하면서도 쿼리 작성에 더 많은 유연성과 편의성을 줄 것이라고 생각한다.

 

Custom Repository

public interface OrderCustomRepository {

    List<Order> findAllInnerFetchJoin();

    List<Order> findAllInnerFetchJoinWithDistinct();
}

기존의 OrderRepository 인터페이스의

findAllInnerFetchJoin(),

findAllInnerFetchJoinWithDistinct() 메서드와
동일한 메서드를 가지는 새로운 커스텀 인터페이스에 정의한다.

 

RepositoryImpl

import static jpabook.jpashop.domain.QMember.member;
import static jpabook.jpashop.domain.QOrder.order;
import javax.persistence.EntityManager;
import com.querydsl.jpa.impl.JPAQueryFactory;

@Repository
public class OrderRepositoryImpl implements OrderCustomRepository{

    private final EntityManager em;
   
    private final JPAQueryFactory query;

    public OrderRepositoryImpl(EntityManager em) {
        this.em = em;
        this.query = new JPAQueryFactory(em);
    }

    @Override
    public List<Order> findAllInnerFetchJoin() {
        return query.selectFrom(order)
            .innerJoin(Order.orderItems)
            .fetchJoin()
            .fetch();
    }

    @Override
    public List<Order> findAllInnerFetchJoinWithDistinct() {
        return query.selectFrom(order)
            .distinct()
            .innerJoin(order.orderItems)
            .fetchJoin()
            .fetch();    
    }
}

EntityManager는 JPA를 사용하여 데이터베이스와 상호작용하는 데 사용되는 객체

  • JPAQueryFactory는 QueryDSL을 사용하여 쿼리를 생성하고 실행하는 데 사용되는 객체

이 클래스들은 Spring의 의존성 주입(DI)을 통해 주입되며, 생성자에서 초기화한다.

커스텀 인터페이스를 구현하는 클래스에 QueryDSL 쿼리를 작성하면 복잡한 쿼리를 더욱 가독성 있게 작성하고,

fetch join을 사용하여 성능을 최적화를 할 수 있다.

 

이때, 해당 구현 클래스 이름을 Impl로 하는 이유

"Impl"은 Spring Data JPA에서 Repository 인터페이스의 구현체를 나타내는 데 사용되며,
사용자 정의 쿼리나 동작을 구현하기 위해 추가되는 클래스를 의미

 

변경된 OrderRepository

public interface OrderRepository extends JpaRepository<Order, Long>, OrderCustomRepository {

    /*
    삭제된 JPQL 정적 쿼리 메서드
    @Query("select o from Order o join fetch o.orderItems")
    List<Order> findAllInnerFetchJoin();

    @Query("select distinct o from Order o join fetch o.orderItems")
    List<Order> findAllInnerFetchJoinWithDistinct();   
     */

    //...
}

JpaRepository를 상속하는 OrderRepository가 OrderCustomRepository 인터페이스를 상속하도록 한다.

OrderCustomRepositoryImpl에 작성된 QueryDSL 코드를 OrderRepository가 자동으로 사용할 수 있게 된다.

 

RepositoryImpl의 장점

  • 코드 분리 및 모듈화
    • 동적인 쿼리 로직을 별도의 클래스에 모듈화함으로써 코드의 가독성을 높이고 유지보수 용이
  • 단일 책임 원칙
    • RepositoryImpl 클래스는 쿼리 로직에 집중하게 되고, Repository 인터페이스는 데이터 액세스 관련 메서드에 집중
  • 재사용성
    • 동적인 쿼리 로직이 별도의 클래스에 캡슐화되면 이를 다른 Repository에서도 재사용 가능
  • 테스트 용이성
    • 인터페이스 구현체로 Mocking을 통한 테스트가 용이
  • 동적 쿼리 관리
    • RepositoryImpl에서 중앙 집중적으로 관리 가능

 

정리

이러한 접근 방식을 선택하기 전에 프로젝트의 구조와 요구 사항을 고려해야 한다고 한다.

일부 프로젝트에서는 QueryDSL을 사용하는 동적인 쿼리 로직을

Repository 내부에 넣는 것을 선호하는 경우도 있지만

JPA를 다루며 만드는 프로젝트이니 Impl로 분리화하여 코드를 작성하고 난 후 어느 것이 적합한지 확인하고

프로젝트 회고를 남기도록 하자.

 

참고자료:

SpringBoot QueryDSL을 사용해 보자

'Back-End > Spring' 카테고리의 다른 글

[Exception] Custom Exception을 언제 써야할까?  (0) 2023.09.18
[Spring] Rest-Assured 알아보기  (0) 2023.08.30
[MVC] DTO 역할 구분, 프로젝트 흐름도 분석  (0) 2023.05.09
[JPA] Entity를 Dto로 변환(리턴)의 중요성  (0) 2023.05.08
[JPA] 도메인 설계, 엔티티 매핑  (0) 2023.04.19
    'Back-End/Spring' 카테고리의 다른 글
    • [Exception] Custom Exception을 언제 써야할까?
    • [Spring] Rest-Assured 알아보기
    • [MVC] DTO 역할 구분, 프로젝트 흐름도 분석
    • [JPA] Entity를 Dto로 변환(리턴)의 중요성
    COBI-98
    COBI-98
    배운 것을 응용하기 위해 기록하는 것을 선호하며 백엔드를 공부하고 있습니다.

    티스토리툴바