개발하는 햄팡이

[Spring][일정 관리 앱 만들기] CRUD 생성 - QueryDsl 시작하기 본문

Back-End/Spring

[Spring][일정 관리 앱 만들기] CRUD 생성 - QueryDsl 시작하기

hampangee 2025. 5. 24. 17:54

저저번주부터  Spring 수업을 시작했는데 과제하랴 정처기 공부하랴 너무 바빠서 블로그 글을 쓰지 못했다...

진짜 블로그 꾸준히 쓰는 사람들은 성실성 인정해줘야 함...

 

 

뭐 그래서 어찌저찌 Spring 두번째 과제를 시작했는데 JPA와 쿠키/세션을 사용한 간단한 일정 관리 앱 만들기이다.

무난하게 User를 만들고 일정을 저장할 Schedule CRUD를 구현중이었는데

Schedule List를 가져오는 부분에서 API를 아래처럼 작성했었다.

 

요청  param에서 선택으로 수정일과 작성자ID를 받을 수 있게 했는데 

 

선택으로 하다보니 null체크도 해줘야하고 Jpa는 말그대로 Simple이라서 동적 쿼리를 제공하지 않는다.

그래서 그냥 if-els문으로 다 처리해버릴까..생각하다가 뭐 다음 과제에서 또 써먹을 수도 있고 그래서 QueryDsl을 적용하는 방법으로 구현하기로 했다.


1. QueryDsl이란?

QueryDsl은 Query Domain Specific Language로

JPQL, SQL 등의 쿼리를 Java코드로 작성할 수 있게 해주는 라이브러리이다.

 

JPA가 처음 나왔을땐 JPQL(Java Persistence Query Language)이라는 쿼리문을 사용했는데

이는 JPA에서 사용하는 객체 지향 쿼리 언어이다.

SQL과 비슷한데 데이터베이스의 테이블 이름과 컬럼명을 사용하는게 아니라 Java코드에서 사용하는 클래스와 필드명을 기준으로 쿼리를 작성한다는 특징이 있다.

String jpql = "SELECT u FROM User u WHERE u.username = :name AND u.age > :age";

이런식으로 작성한다.

 

또 다른 특징으로는 정적 쿼리만 지원한다는 점이 있다. 이 점을 보완하기 위해 QueryDsl 라이브러리가 만들어지게 되었다.

JPQL은 쿼리를 문자열로 작성하기 때문에 동적 쿼리를 작성할 수 없다.

 

나의 케이스를 보면 지금 수정일이 있냐 없냐, 작성자 ID가 있냐 없냐만 판별하더라도 if-else문이 4개가 추가된다. 이를 QueryDsl로 작성하면 좀 더 깔끔하게 작성할 수 있다.

 

그리고 문자열로 작성하기 때문에 잘못된 문법이나 필드명이 있어도 컴파일 과정에서 잡아주지 못한다.

 

이러한 점들을 보완하기 위해 QueryDsl이 생겨나게 된 것이다.

 

 

2. Q 클래스

QueryDsl을 적용하면 build>generated아래에 Qclass가 자동으로 생성된다.

Q클래스는 각 엔티티의 필드를 QueryDsl용 표현객체로 감싸 쿼리를 작성하는데 사용한다.

 

예시:

User Entity

@Entity
public class User {
    private String username;
    private int age;
}

 

생성된 QUser 클래스

public class QUser extends EntityPathBase<User> {
    public final StringPath username = createString("username");
    public final NumberPath<Integer> age = createNumber("age", Integer.class);
}

 메소드를 뜯어보면 입력값을 QueryDsl이 받아들일 수 있는 String이나 Number객체로 만들어서 Query작성에 필요한 기능들을 사용할 수 있게 해준다.

 

 

 

3. Setting

build.gradle 파일에 아래의 의존성을 추가한다.

    // QueryDsl
    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"

 

 

그러고 빌드를 해주면 아래처럼 Q클래스가 생긴다.

 

 

 

그리고 그 내부를 살펴보면

 

이런식으로 구성되어있다.

맨 처음 QueryDsl이 뭔지 몰라서 팀플할때 누군가가 추가해놓은 이상한 객체 때문에 모른다고 혼날까봐 물어보지도 못하고 이것저것 찾아보다가 내가 임의로 편집했는데 실행이 안되고 난리가 났던 적이 있다.

지금은 인텔리제이에 친절하게 이런 경고문이 떠 있지만 그땐 없었다..ㅜㅡㅜ

 

그리고 QueryDsl을 사용하기 위해서는 entityManager와 JPAQueryFactory가 필요해서 이를 설정해준다.

package com.sparta.schedule.common.config;

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing
public class JPAConfig {

    @PersistenceContext
    private EntityManager em;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(em);
    }
}

 

 

실행했을때 아래와 같은 문구가 뜬다면 

***************************
APPLICATION FAILED TO START
***************************

Description:

The bean 'jpaAuditingHandler' could not be registered. A bean with that name has already been defined and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

 

@EnableJpaAuditing

해당 어노테이션이 다른 곳에 위치하고 있는지 확인해봐야한다.

나같은 경우는 JPAAuditing을 사용해서 작성일 수정일 필드를 자동으로 등록하기 위해서 Application최상단에 붙여놨었는데 QueryDsl을 사용하기 위해 Config로 바꾸었다.

 

 

변경 전

package com.sparta.schedule;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class ScheduleApplication {

    public static void main(String[] args) {
        SpringApplication.run(ScheduleApplication.class, args);
    }

}

 

변경 후

package com.sparta.schedule;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ScheduleApplication {

    public static void main(String[] args) {
        SpringApplication.run(ScheduleApplication.class, args);
    }

}

 

 

4. Repository

일단 ScheduleRepository를 호출했을 때 QueryDsl을 적용한 레포와 기존에 사용하던 JpaRepo를 둘 다 사용할 수 있어야하기 때문에 이를 확장해줘야한다.

 

그러기 위해서

package com.sparta.schedule.schedule.repository;

public interface ScheduleRepositoryCustom {

}

Custom 인터페이스를 만들고

 

그 다음 이를 상속한 구현체 class를 만든다.

package com.sparta.schedule.schedule.repository;

import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class ScheduleRepositoryCustomImpl implements ScheduleRepositoryCustom {

    private final JPAQueryFactory queryFactory;
    
}

 

 

 

그 다음 아래와 같이 기존에 JpaRepository만 상속중이던 ScheduleRepository 인터페이스에 Cutom인터페이스를 상속해준다.

package com.sparta.schedule.schedule.repository;

import com.sparta.schedule.schedule.entity.Schedule;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;

public interface ScheduleRepository extends JpaRepository<Schedule, Long>, ScheduleRepositoryCustom {

    default Schedule findByIdOrElseThrow(long id) {
        return findById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "게시글을 찾을 수 없습니다."));
    }
}

 

 

그리고 구현체에 필요한 쿼리문을 작성하면 된다.

쿼리문 작성하는 것은 다음 포스팅에서 계속..