Spring/Spring 문법

QueryDSL이란 무엇일까??

열심히 해 2024. 11. 14. 13:59

QueryDSL

 

Java에서 타입 세이프한 쿼리 생성을 위해 사용하는 라이브러리 입니다. Query DSL 을 사용하면 JPA Criteria API나 JPQL로 작성하던 쿼리를 코드로 더 작관적으로 표현할 수 있습니다.

 

 

- 장점과 단점

장점

  • 타입 세이프티: QueryDSL은 컴파일 시점에 타입 체크를 통해 잘못된 쿼리를 방지할 수 있습니다.
  • 가독성: 메서드 체인 방식으로서 조건을 여러 줄에 걸쳐 작성할 수 있습니다. 복잡한 조건을 표현하기 좋습니다. 
  • 재사용성: booleanExpression 을 사용하여 and, or 조건으로 조합하여 사용할 수 있습니다.
  • 자동 완성 지원: IDE에서 자동 완성 기능을 제공해 쿼리 작성이 더 편리해집니다.
  • 컴파일 시점에서 에러를 확인할 수 있습니다.

 

단점

  • 빌드 시간 증가: 빌드 시간이 길어질 수 있습니다. Q 클래스 생성을 위해 APT(Annotation Processing Tool)를 사용하는데, 이로 인해 JAR 파일로 만드는 과정에서 빌드 시간이 다소 길어질 수 있습니다.
  • 외부 의존성: QueryDSL은 외부 라이브러리이며 초기 설정이 복잡할 수 있습니다. 
  • 단순한 쿼리가 필요할 때는 JPA나 JPQL 에는 쓰는 게 더 낫습니다.

 

 

- 사용법

1. 의존성 추가

dependencies {
    implementation "com.querydsl:querydsl-jpa:${querydslVersion}:jakarta"
    kapt "com.querydsl:querydsl-apt:${querydslVersion}:jakarta"
    annotationProcessor "com.querydsl:querydsl-apt:${querydslVersion}:jakarta"
}

 

2. Q 클래스 생성

QueryDSL 을 사용하면 각 엔티티에 대한 Q 클래스가 자동 생성됩니다. 예를 들어 User 엔티티가 있을 때, User 엔티티에 대한 쿼리를 작성하려면 QUser 라는 클래스를 사용하게 됩니다. Q 클래스는 build/generated 디렉토리에 생성됩니다. 프로젝트 설정에 따라 IDE에서 해당 파일을 빌드 경로로 추가해줘야 할 수 있습니다.

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    private int age;

    // getters and setters
}

 

이러한 User 엔티티가 있을 때, 이 엔티티에 대해 아래와 같은 QUser 클래스가 생성됩니다. QUser 클래스는 User 엔티티의 모든 필드를 static 필드로 정의하여 쿼리 작성 시 타입 세이프하게 접근할 수 있게 해줍니다.

 

// QUser.java (자동 생성된 클래스)
public class QUser extends EntityPathBase<User> {
    
    public static final QUser user = new QUser("user");

    public final StringPath name = createString("name");
    public final NumberPath<Integer> age = createNumber("age", Integer.class);

    public QUser(String variable) {
        super(User.class, forVariable(variable));
    }
}

 

  • QUser.user는 User 엔티티에 대한 기본 인스턴스로, name과 age 같은 필드에 타입 세이프하게 접근할 수 있습니다.

 

3. QueryDsl Configuration 설정

JPAQueryFactory 빈을 설정하기 위한 Configuration 클래스를 작성합니다.

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QuerydslConfig {

    private final EntityManager entityManager;

    public QuerydslConfig(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}
  • JPAQueryFactory는 EntityManager를 주입 받아 생성됩니다.
  • 이 빈을 통해 다른 클래스에서 QueryDSL을 활용하여 쿼리를 작성할 수 있습니다.
  • 하나의 레포지토리에서만 QueryDSL을 사용하고자 한다면 객체로 생성하는 방법도 있습니다.

 

4. interface RepositoryQuery 작성

import com.example.domain.User;
import java.util.List;

public interface UserRepositoryQuery {
    List<User> findUsersByName(String name);
}

 

 

5. RepositoryQueryImpl : 위 레포지토리 구현체 클래스 작성

import com.example.domain.QUser;
import com.example.domain.User;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.stereotype.Repository;
import java.util.List;

public class UserRepositoryQueryImpl implements UserRepository {

    private final JPAQueryFactory queryFactory;
    private final QUser user = QUser.user;

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

    @Override
    public List<User> findUsersByName(String name) {
        return queryFactory
                .selectFrom(user)
                .where(user.name.eq(name))
                .fetch();
    }
}

 

 

6. 일반적으로 UserService 는 JpaRepository 와 커스컴 Repository를 동시에 상속 받은 UserRepository 를 주입 받습니다.

 

public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryQuery {
//...
}


@Service
public class UserService {
	private final UserRepository userRepository;
    // ...
}

 

구조

 

 

- 동적 쿼리 작성 예시

- name과 age 조건이 선택적으로 추가되는 User 조회 쿼리

 

1. BooleanBuilder

@Override
public List<User> findUsers(String name, Integer age) {
    BooleanBuilder builder = new BooleanBuilder();

    if (name != null) {
        builder.and(user.name.eq(name));
    }

    if (age != null) {
        builder.and(user.age.eq(age));
    }

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

 

 

2. BooleanExpression

@Override
public List<User> findUsers(String name, Integer age) {
    return queryFactory
            .selectFrom(user)
            .where(nameEq(name), ageEq(age))  // 매개변수, 조건들을 조합
            .fetch();
}

// 개별 조건 메서드들 정의
private BooleanExpression nameEq(String name) {
    return name != null ? user.name.eq(name) : null;
}

private BooleanExpression ageEq(Integer age) {
    return age != null ? user.age.eq(age) : null;
}