Programming/Spring Boot

Querydsl 개념 및 Gradle 환경설정 (gradle-7.x.x)

Jan92 2021. 12. 7. 23:00
반응형

Querydsl

- Querydsl 개념 및 Gradle 환경설정

QUser user = QUser.user;
List<User> result = queryFactory
                      .select(user)
                      .from(user)
                      .where(user.name.eq("Jan"))
                      .fetch();
// SELECT * FROM user WHERE user.name = 'Jan'

 

'Querydsl 이란?'

Querydsl은 HQL(Hibernate Query Language) 쿼리를 타입에 맞게 안전하게 생성 및 관리할 수 있게 해주는 프레임워크입니다.

쉽게 자바 코드를 기반으로 SQL 쿼리를 작성해준다고도 이야기할 수 있습니다.

 

Querydsl을 사용하는 이유는 무엇일까요?

SQL은 문자이기 때문에 type-check가 불가능하고, 실행해보기 전까지 작동 여부 확인이 어렵습니다. 만약 SQL이 class처럼 type이 있고, Java 코드로 작성할 수 있다면 좋지 않을까? 하는 데서 시작된 것으로 SQL을 Java로 type-safe 하게 개발할 수 있게 해주는 프레임 워크가 바로 Querydsl입니다.

 

(Querydsl -> JPQL -> SQL)

 

 

'Querydsl의 특징'

- 문자가 아닌 코드로 쿼리를 작성함으로써, 컴파일 시점에서 문법 오류를 쉽게 발견할 수 있습니다.

- 자동완성 등 IDE(Integrated Development Environment)의 도움을 받을 수 있습니다.

- 동적인 쿼리 작성이 편리합니다.

- 쿼리 작성 시 제약 조건 등을 메서드 화하여 재사용할 수 있습니다. (BooleanBuilder)

- 도메인 타입과 프로퍼티를 안전하게 참조할 수 있으며, 도메인 타입의 리팩터링을 더 잘할 수 있습니다.

 

* 단점으로는 사용하기 위해 다소 번거로운 설정 과정과 사용법을 익혀야 한다는 점이 있습니다.

 

 


 

 

'Gradle 환경설정 (gradle-7.x.x)'

 

Gradle을 사용한 프로젝트의 Querydsl 환경설정에서 중요한 것은 spring boot 버전 및 gradle 버전, querydsl 버전입니다.

구글링을 통해 querydsl 환경설정을 찾아보면 비슷한 듯 다른 정말 많은 방법들이 있습니다. 중요한 것은 기존에 작업 중인 spring boot 프로젝트의 버전을 확인하여 버전에 맞는 설정을 하는 것입니다.

(스프링 부트 2.3 부터는 gradle 6.3 이상이 요구됩니다.)

 

springframework.boot-2.6.0

gradle-7.3.1

java-1.8

Intellij-2021.x

(해당 포스팅은 위 작업 환경에서 설정한 내용입니다.)

 

 

plugins {
    id 'org.springframework.boot' version '2.6.0'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'mysql:mysql-connector-java'
    runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    // Querydsl
    implementation 'com.querydsl:querydsl-jpa'

    // Querydsl JPAAnnotationProcessor 사용 지정
    annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
    // java.lang.NoClassDefFoundError(javax.annotation.Entity) 발생 대응
    annotationProcessor "jakarta.persistence:jakarta.persistence-api"
    // java.lang.NoClassDefFoundError(javax.annotation.Generated) 발생 대응
    annotationProcessor "jakarta.annotation:jakarta.annotation-api"
}

// clean task 실행시 QClass 삭제
clean {
    delete file('src/main/generated') // 인텔리제이 Annotation processor 생성물 생성 위치
}

'build.gradle'

 

gradle 프로젝트 querydsl 환경설정 방법에는 크게 두 가지가 있습니다.

하나는 gradle 플러그인 'com.ewerk.gradle.plugins.querydsl'을 통해 설정하는 방법, 다른 하나는 'Annotation Processor'를 통한 설정 방법으로 해당 포스팅은 후자를 사용하여 설정하였습니다.

 

설정 후 프로젝트를 실행하면 엔티티 클래스 앞에 'Q'가 붙은 이름의 쿼리 타입(QClass)들이 생성됩니다.

(IntelliJ에서는 Preferences -> Build, Execution, Deployment -> Build Tools -> Gradle -> Build and run using 설정에 따라 QClass가 생성되는 위치가 변경됩니다.)

 

* 'io.spring.dependency-management' 플러그인을 통해 spring-boot-plugin이 정의한 dependency management 설정을 사용할 수 있습니다.

 

 

동작 원리는 다음과 같습니다.

annotationProcessor는 Java 컴파일러 플러그인으로서, 컴파일 단계에서 프로젝트 내의 @Entity(javax.persistence.Entity) 어노테이션을 선언한 클래스를 탐색하고 JPAAnnotationProcessor를 통해 쿼리 타입(QClass)을 생성합니다.

이러한 쿼리타입(QClass)들은 Querydsl을 사용하여 메서드 기반으로 쿼리를 작성할 때 프로젝트에서 만든 도메인 클래스(Entity)의 구조를 설명해주는 메타데이터 역할을 하며, 쿼리의 조건을 설정할 때 사용됩니다.

 

 


 

import com.querydsl.jpa.impl.JPAQueryFactory;
import javax.persistence.EntityManager;
import javax.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 querydslConfig {
    @PersistenceContext
    private EntityManager entityManager;

    public querydslConfig() {
    }

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(this.entityManager);
    }
}

'querydslConfig'

 

다음으로 querydslConfig.java 입니다.

해당 클래스를 통해 JpaQueryFactory를 bean으로 등록하여 repository에서 필요할 때마다 생성해서 쓰는 게 아니라 바로 가져와서 사용할 수 있도록 합니다.

Querydsl을 사용하여 쿼리를 Build하기 위해서는 JpaQueryFactory가 필요합니다. JpaQueryFactory를 사용하면 EntityManager를 통해 질의가 처리됩니다.

 

 


 

@Table(name = "user")
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "idx", nullable = false)
  private Long idx;

  @Column(name = "name")
  private String name;

  @Column(name = "email")
  private String email;
}

'User' Entity

 

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

'UserRepository' interface

 

public interface UserRepositoryCustom {
  List<User> getUserList();
}

'UserRepositoryCustom' interface

 

@Repository
public class UserRepositoryImpl implements UserRepositoryCustom {

  private final JPAQueryFactory queryFactory;

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

  QUser user = QUser.user;

  @Override
  public List<User> getUserList() {
    return queryFactory
        .selectFrom(user)
        .fetch();
  }
}

'UserRepositoryImpl' class

 

 

repository

 

UserEntity, UserRepository, UserRepositoryCustom, UserRepositoyImpl입니다.

 

UserRepository에서는 JpaRepository를 사용하여 메서드의 이름으로 간단한 메서드들을 구현하는 용도이며, UserRepositoryCustom은 querydsl을 통해 사용하기 위한 쿼리를 정의하는 interface입니다. 마지막 UserRepositoryImpl은 Custom interface에서 정의된 메서드를 오버라이드 하여 실제로 구현하는 클래스로, 이렇게 Repository, RepositoryCustom, RepositoryImpl 구조를 통해서 실제로 사용하는 곳에서는 Repository 하나만을 가지고 모든 메서드를 사용할 수 있게 됩니다.

 

이러한 구조는 JpaRepository와 Querydsl을 사용하는데 거의 공식처럼 많이 사용되는 구조로, 직접 만들어서 사용하다 보면 저절로 익숙해지지만 처음에는 원리를 생각하면서 인터페이스와 클래스를 구현하면 이해하기가 더 쉽습니다.

 

 


 

@RequiredArgsConstructor
@Repository
public class BookQueryRepository {

    private final JPAQueryFactory queryFactory;
    
    QBook book = QBook.book;
    
    @Transactional(readOnly = true)
    public Boolean exist(Long idx) {
        Integer fetchOne = queryFactory
                .selectFrom(book)
                .where(book.idx.eq(idx))
                .fetchFirst();
                
        return fetchOne != null;
    }
}

(JpaRepository 없이 이렇게 Querydsl만 사용하는 경우도 있습니다.)

 

처음부터의 과정을 통해 Entity를 통해 Querydsl에서 사용하기 위한 쿼리 타입(QClass)을 만들었습니다. 

Querydsl 쿼리를 만들기 위해서 Book 타입을 위한 정적 타입 변수로 QBook을 사용합니다. QBook은 기본 인스턴스 변수를 갖고 있으며, 아래와 같이 정적 필드로 접근할 수 있습니다.

QBook book = QBook.book;
QBook fictionBook = new QBook("fictionBook");

 

 

 

 

 

* 환경설정이랑은 다른 부분이지만 활용적인 부분에서 재미있는 이야기 (번외)

 

대량의 데이터를 조회(SELECT)에 SQL의 exist를 사용하게 되면 count(1)을 사용하는 것보다 속도가 빠릅니다. 전자인 exist는 조건을 만족하는 row를 찾으면 바로 동작을 종료해버리지만 후자인 count는 찾더라도 총개수를 카운팅 하기 위해 모든 row를 탐색하기 때문입니다. 이 경우 스캔하는 대상이 앞에 있을수록 성능 차이가 심해질 수 있습니다.

 

여기서 중요한 점은 Querydsl의 select exist()는 SQL의 exist를 사용하지 않고, 성능상 비효율적일 수 있는 count를 사용하여 수행된다는 것인데요. 그렇기 때문에 효율성을 위해서는 위 코드의 querydsl처럼 fetchFirst()을 사용하는 방법이 있습니다.

(fetchFirst()는 내부적으로 limit(1).fetchOne() 으로 되어있습니다.)

 

< 참고 자료 >

 

JPA exists 쿼리 성능 개선

Spring Data Jpa를 사용하다보면 해당 조건의 데이터가 존재하는지 확인 하기 위해 exists 쿼리가 필요할때가 많습니다. 간단한 쿼리의 경우엔 아래와 같이 메소드로 쿼리를 만들어서 사용하는데요. b

jojoldu.tistory.com

 

 

 

 

< Querydsl 참고 자료 >

 

[gradle] 그레이들 Annotation processor 와 Querydsl - I'm honeymon(JiHeon Kim).

이 글에서 다룰 예정인 ‘Querydsl’과 ‘Annotation processor’ 에 관한 내용도, 스프링 부트를 버전업하는 과정에서 겪게 된다. 사내 개발기기 교체주기가 되어 새로운 맥북을 받고 스프링 부트 버전

honeymon.io

 

 

Spring Boot Data Jpa 프로젝트에 Querydsl 적용하기

안녕하세요? 이번 시간에는 Spring Boot Data Jpa 프로젝트에 Querydsl을 적용하는 방법을 소개 드리겠습니다. 모든 코드는 Github에 있습니다. Spring Data Jpa를 써보신 분들은 아시겠지만, 기본으로 제공해

jojoldu.tistory.com

 

 

11. [JPA] Querydsl

Querydsl은 오픈 소스 프로젝트이고 type-safe한 쿼리를 위한 Domain Specific Language이다. 왜 필요한가? SQL query는 문자이다. 이는 type-check가 불가능하고 실행해 보기 전까지 작동여부 확인이 어렵다. 만..

lng1982.tistory.com

 

반응형