본문 바로가기
데이터베이스 & ORM

QueryDSL 개요

by DoRightting 2024. 7. 25.

1. 멀티 모듈 프로젝트에서의 QueryDSL 설정

멀티 모듈 프로젝트에서 QueryDSL을 사용할 때는 다음과 같은 구조와 설정을 권장합니다:

my-project/
├── core/
│   └── build.gradle
├── api/
│   └── build.gradle
├── domain/
│   └── build.gradle
└── build.gradle

1.1 Root build.gradle 설정

buildscript {
    ext {
        queryDslVersion = "5.0.0"
    }
}

subprojects {
    apply plugin: 'java'

    repositories {
        mavenCentral()
    }
}

1.2 Domain 모듈 build.gradle 설정

plugins {
    id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10'
}

dependencies {
    implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
    implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
}

def querydslDir = "$buildDir/generated/querydsl"

querydsl {
    jpa = true
    querydslSourcesDir = querydslDir
}

sourceSets {
    main.java.srcDir querydslDir
}

configurations {
    querydsl.extendsFrom compileClasspath
}

compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}

1.3 Entity 및 Repository 정의 (Domain 모듈)

ntity
public class User {
    @Id @GeneratedValue
    private Long id;
    private String username;
// getters and setters
}

public interface UserRepository extends JpaRepository<User, Long>, QuerydslPredicateExecutor<User> {
}

1.4 API 모듈 설정

API 모듈의 build.gradle에 domain 모듈 의존성을 추가

dependencies {
    implementation project(':domain')
}

2. 서비스 계층에서의 QueryDSL 사용

2.1 JPAQueryFactory 설정

@Service
public class UserService {
    private final JPAQueryFactory queryFactory;

    public UserService(EntityManager entityManager) {
        this.queryFactory = new JPAQueryFactory(entityManager);
    }
}

2.2 기본 쿼리 작성

public List<User> findUsersByUsernameAndAge(String username, int age) {
    QUser user = QUser.user;
    return queryFactory
        .selectFrom(user)
        .where(user.username.eq(username)
            .and(user.age.gt(age)))
        .fetch();
}

2.3 동적 쿼리 구성

public List<User> searchUsers(String username, Integer age) {
    QUser user = QUser.user;

    BooleanBuilder builder = new BooleanBuilder();
    if (username != null) {
        builder.and(user.username.contains(username));
    }
    if (age != null) {
        builder.and(user.age.gt(age));
    }

    return queryFactory
        .selectFrom(user)
        .where(builder)
        .fetch();
}

2.4 복잡한 쿼리 작성

public List<UserDTO> findUserWithPosts() {
    QUser user = QUser.user;
    QPost post = QPost.post;

    return queryFactory
        .select(Projections.constructor(UserDTO.class,
            user.id,
            user.username,
            post.count()))
        .from(user)
        .leftJoin(user.posts, post)
        .groupBy(user.id)
        .fetch();
}

2.5 페이징 처리

public Page<User> findUsersPage(Pageable pageable) {
    QUser user = QUser.user;

    List<User> users = queryFactory
        .selectFrom(user)
        .offset(pageable.getOffset())
        .limit(pageable.getPageSize())
        .fetch();

    long total = queryFactory
        .selectFrom(user)
        .fetchCount();

    return new PageImpl<>(users, pageable, total);
}

3. 고급 페이징 처리 예시

다음은 복잡한 조건과 DTO 프로젝션을 포함한 페이징 처리 예시

Page<NoticeListResultDtoVo> noticeListResultDtoVo = applyPagination(
    pageRequest,
    contentQuery -> contentQuery
        .select(new QPharmListResultDtoVo(
            noticeRegistTarget.id,
            noticeRegistTarget.title,
            noticeRegistTarget.writer,
            noticeRegistTarget.content,
            noticeRegistTarget.file,
            noticeRegistTarget.image,
            noticeRegistTarget.createdDate,            
        ))
        .from(noticeRegistTarget)
        .where(
            noticeTitleEq(title),
            writerEq(writer),
            contentEq(content),
            fileEq(file),
            imageEq(image),            
        )
        .orderBy(noticeRegistTarget.createdDate.desc()),
    countQuery -> countQuery
        .select(new QNoticeListResultDtoVo(
            noticeRegistTarget.id,
            noticeRegistTarget.title,
            noticeRegistTarget.writer,
            noticeRegistTarget.content,
            noticeRegistTarget.file,
            noticeRegistTarget.image,
            noticeRegistTarget.createdDate
        ))
        .from(noticeRegistTarget)
        .where(
             noticeTitleEq(title),
            writerEq(writer),
            contentEq(content),
            fileEq(file),
            imageEq(image)
        )
);

이 예시에서 contentQuery와 countQuery는 다음과 같은 용도로 사용

  • contentQuery: 실제 데이터를 조회하는 쿼리로 요청된 페이지에 해당하는 데이터를 조회함.
  • countQuery: 전체 결과의 개수를 조회하는 쿼리로 페이징 처리에 필요한 전체 레코드 수를 계산함.

'데이터베이스 & ORM' 카테고리의 다른 글

VACUUM ANALYZE  (1) 2024.11.11
PostgreSQL GIN  (5) 2024.11.10
JPA에서의 엔티티 참조 조회 방식  (1) 2024.07.24
Q DTO  (0) 2024.07.21
Entity, Repository, DTO, domain, service의 연관성과 개념  (0) 2024.07.20