본문 바로가기

Spring/JPA

JPA심화트랙(5) - 객체지향을 곁들인 JPA, QueryDSL 실전 활용법

Spring Data JPA에서 QueryDSL을 사용하기 위해서는 해당 기능을 지원하는 라이브러리를 추가해야 한다.

 

QueryDSL을 사용할 때는 QueryDSL이 제공하는 Predicate를 사용하여 동적 쿼리를 작성하고, Repository에 인터페이스를 작성하여 구현

 

QueryDSL 기본설정

의존성 추가 후 other-clean 후 compile.Java하여 build/generate에 QEntity 생성된거 확인하기

implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"






clean {
    delete file('src/main/generated')
}

 

Repository에 QueryDSL 추가

 

Entity.java

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Entity {
    @Id
    private Long id;
    private String name;
    
    @Builder
    ...
}

EntityRepository.java

public interface EntityRepository extends JpaRepository<Entity, Long>, EntityQueryDslRepository {
    
}

EntityRepositoryCustom.java

public interface EntityQueryDslRepository {
    List<Entity> findByName(String name);
}

EntityRepositoryCusomImpl.java

@Repository
public class EntityQueryDslRepositoryImpl implements EntityQueryDslRepository {
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List<Entity> findByName(String name) {
        QEntity qEntity = QEntity.Entity; // QueryDSL의 엔티티 클래스

        return new JPAQuery<>(entityManager)
            .select(qEntity)
            .from(qEntity)
            .where(qEntity.name.eq(name))
            .fetch();
    }
}

 

JPA와 QueryDSL은 각각 사용처를 분리해 두면 편하다고 한다.

  1. JPA를 사용하는 경우
    • 기본적인 CRUD(Create, Read, Update, Delete) 기능을 구현할 때 JPA를 사용
    • 단순한 데이터베이스 조작이 필요한 경우에는 JPA를 사용하여 쉽게 처리가 가능하다.
  2. QueryDSL을 사용하는 경우
    • 복잡한 동적 쿼리를 작성해야 할 때 QueryDSL을 사용
      • QueryDSL은 Fluent API를 제공하여 복잡한 쿼리를 Java 코드로 작성할 수 있어  코드 가독성을 높이고 오류를 줄일 수 있다.
    • 복잡한 조인이나 서브쿼리, 다양한 필터링 조건을 가진 쿼리를 작성해야 할 때 QueryDSL을 사용하여 쉽게 처리할 수 있다.
    • 복수의 엔티티 간의 연관 관계를 고려해야 하는 경우에도 QueryDSL을 사용하여 더 직관적이고 유연한 쿼리를 작성할 수 있다.
  3. QueryDSL + JPA를 동시에 사용하는 경우
    • 위와 같이 2개의 EntityQueryDslRepository, EntityQueryDSLRepositoryImpl을 MemberRepository에 상속시켜서 MemberService 입장에서는 MemberRepository만 상속받게 하는 것이 유연한 프로그래밍 구조를 생성해 줄 수 있다.

JPAQueryFactory

import com.querydsl.jpa.impl.JPAQueryFactory;

public class MemberQueryDslRepositoryImpl implements MemberQueryDslRepository {

    private final JPAQueryFactory queryFactory;
    private final QMember member = QMember.member;

    public MemberQueryDslRepositoryImpl(JPAQueryFactory queryFactory) {
        this.queryFactory = queryFactory;
    }

    @Override
    public Member findByEmail(String email) {
        return queryFactory.selectFrom(member)
                .where(member.email.eq(email))
                .fetchOne();
    }
}

JPAQueryFactory는 Querydsl을 사용하여 JPA 쿼리를 생성하기 위한 중요한 클래스다. EntityManager를 이용하여 JPA 쿼리를 생성하고 실행하는 데 사용되며, 주요 메서드는 다음과 같다.

 

  • selectFrom():
    • 쿼리의 시작점을 설정하고, 조회 대상 엔티티의 시작 테이블을 지정
    • Querydsl의 QEntity 클래스를 인자로 받아 해당 엔티티를 기반으로 쿼리를 시작
  • select():
    • 쿼리 결과로 얻고자 하는 엔티티, 열 또는 표현식을 지정
    • selectFrom() 이후에 사용되며, 특정 열 또는 표현식을 선택할 때 사용됩니다.
  • where():
    • 쿼리에 조건을 추가
    • 엔티티의 필드 값을 비교하거나, 조건을 결합하는 등 다양한 조건을 지정할 수 있다.
    • 주로 eq(), ne(), lt(), gt() 등의 메서드를 사용하여 비교
  • groupBy():
    • 그룹화를 위한 조건을 지정
    • 일반적으로 집계 함수와 함께 사용되며, 결과를 그룹화하는 데 사용됨
  • orderBy():
    • 쿼리 결과의 정렬 순서를 지정
    • 오름차순 또는 내림차순으로 정렬할 필드를 지정할 수 있다.
  • fetch() 및 fetchOne():
    • 쿼리 결과를 가져오는 메서드
    • fetch()는 여러 결과를 가져오며, fetchOne()은 최대 하나의 결과만 가져온다.
    • 보통 fetchOne() 메서드는 단일 결과를 반환하는 쿼리에서 사용된다.
  • join():
    • 쿼리에 조인을 추가
    • 내부 조인, 외부 조인, 왼쪽 조인, 오른쪽 조인 등 다양한 유형의 조인을 지원
  • limit() 및 offset():
    • 결과를 제한하고 오프셋을 지정
    • 페이지네이션을 구현할 때 사용된다.

QueryDslRepositorySupport

Spring Data JPA에서 제공하는 클래스로, Querydsl을 사용하여 JPA 쿼리를 작성하고 실행하는 데 도움을 준다.

QueryDslRepositorySupport를 상속하면 기본적으로 querydsl 필드가 제공되어 Querydsl의 JPAQueryFactory를 사용할 수 있다.

*쿼리를 쉽게 생성할 수 있도록 지원해 주는 부분도 있고, 페이징 처리에 유용한 기능들도 제공해준다.다음 코드를 참고하면 좋을 것 같다.

import com.querydsl.jpa.impl.JPAQuery;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;

@Repository
public class MemberQueryDslRepository extends QuerydslRepositorySupport {

    public MemberQueryDslRepository() {
        super(Member.class);
    }

    public Page<Member> findMembersByEmail(String email, Pageable pageable) {
        // EntityManager를 사용하여 JPAQuery 생성
        JPAQuery<Member> query = new JPAQuery<>(getEntityManager());

        // Querydsl을 사용하여 동적 쿼리 작성
        QMember member = QMember.member;
        query.from(member)
             .where(member.email.eq(email));

        // applyPagination 메서드를 사용하여 Pageable 객체의 정보를 적용하여 페이징된 결과 반환
        return getQuerydsl().applyPagination(pageable, query).fetch();
    }
}

 

다음과 같은 추가 기능이 있다고 하니, 학습해보는것도 좋을 것 같다.

  1. Querydsl 컨텍스트 액세스: QueryDslRepositorySupport를 통해 Querydsl 컨텍스트에 접근할 수 있다. 이를 통해 Querydsl의 다양한 기능을 활용할 수 있다.
  2. 동적 쿼리 생성: QueryDslRepositorySupport를 사용하면 동적으로 쿼리를 생성할 수 있다. 예를 들어, 여러 조건에 따라 쿼리를 동적으로 생성할 수 있다.
  3. 페이징 및 정렬: QueryDslRepositorySupport를 사용하여 페이징 및 정렬된 쿼리를 작성할 수 있다. 이를 통해 특정 범위의 결과를 가져오고 정렬할 수 있다.
  4. 복잡한 조인 지원: QueryDslRepositorySupport를 사용하면 복잡한 조인을 수행하는 쿼리를 쉽게 작성할 수 있다. 예를 들어, 여러 테이블 간의 조인이 필요한 경우에 유용하다.
  5. 서브쿼리 작성: QueryDslRepositorySupport를 사용하여 서브쿼리를 작성할 수 있다. 이를 통해 복잡한 데이터 검색 및 필터링이 가능하다.
  6. 정적 타입 안전한 쿼리 작성: Querydsl을 사용하면 정적 타입 안전한 쿼리를 작성할 수 있다. 이는 오타나 오류를 미리 방지하여 안전한 쿼리 작성을 지원한다.