🔥스파르타 TIL (Spring)

QueryDSL 이란?

승승장규 2025. 2. 25. 16:22

하이버네이트 쿼리 언어의 쿼리를 타입에 안전하게 생성 및 관리해주는 프레임워크로, 자바 백엔드에서 Spring Data JPA를 함께 사용한다. 

하지만 JPA로 복잡한 쿼리, 동적 쿼리를 구현하는데 한계가 있기 때문에 이를 해결할 수 있는 것이 QueryDSL 이다.

Mybatis, JPQL 등 문자열 형태로 쿼리문을 작성하여 컴파일 시에 오류를 발견하는 것이 불가능했지만, QueryDSL은 자바 코드로 SQL문을 작성할 수 있기 때문에 컴파일 시에 발생하는 오류를 확인할 수 있다.

 

예를 들어서 복잡한 검색 조건이 있다고 가정해보자

public interface MemberRepository extends JpaRepository<Member, Long> {
  
    // 이름이 포함되고, 특정 나이 이상이며, 특정 지역에 사는 활성화된 회원 조회
    List<Member> findByNameContainingAndAgeGreaterThanAndAddressContainingAndStatusOrderByCreatedAtDesc(
        String name, 
        int age, 
        String address, 
        MemberStatus status
    );
 }

 

한 눈에 봐도 복잡하고, 좋지 않아보이는 메서드 명이다.

 

이 코드를 QueryDSL로 처리한다면

@Repository
@RequiredArgsConstructor
public class MemberRepositoryCustomImpl implements MemberRepositoryCustom {
    private final JPAQueryFactory queryFactory;
      
    // 이름이 포함되고, 특정 나이 이상이며, 특정 지역에 사는 활성화된 회원 조회
    public List<Member> searchMembers(String name, int age, String address) {
        return queryFactory
            .selectFrom(member)
            .where(
                nameContains(name),
                ageGt(age),
                addressContains(address),
                member.status.eq(MemberStatus.ACTIVE)
            )
            .orderBy(member.createdAt.desc())
            .fetch();
    }
    
    private BooleanExpression nameContains(String name) {
        return StringUtils.hasText(name) ? member.name.contains(name) : null;
    }
    
    private BooleanExpression ageGt(Integer age) {
        return age != null ? member.age.gt(age) : null;
    }
    
    private BooleanExpression addressContains(String address) {
        return StringUtils.hasText(address) ? member.address.contains(address) : null;
    }
 }

 

이렇게 자바 코드로 SQL문을 작성할 수 있기에 메서드 명이 간결해지고, 재사용성이 용이해진다.

 

JPA 방식으로 사용하게 되면 메서드 이름이 너무 길어지고, 조건이 많아질 수록 가독성이 떨어진다. 또한, 재사용이 어렵고, 동적 쿼리 구현이 어렵다는 단점이 있다.

 

하지만 QueryDSL을 사용하기 위해서는 초기 설정이 복잡하고 번거롭다.

 

build.gradle

implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api" // Q클래스를 자동으로 생성
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
@Configuration
public class QuerydslConfiguration {
    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}
// 1. 기본 인터페이스
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
}

// 2. 커스텀 인터페이스
public interface MemberRepositoryCustom {
    List<Member> searchMembers(String name, int age, String address)
}

 

Q 클래스 : 엔티티 클래스에 대응하는 메타데이터 클래스로 QueryDSL은 Q클래스를 사용하여 엔티티의 필드를 안전하게 참조할 수 있는 경로를 제공한다.

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String name;
    private int age;
    
    @ManyToOne(fetch = FetchType.LAZY)
    private Team team;
}

// 자동 생성된 Q클래스로 type-safe 쿼리를 작성할 수 있도록 지원하기 위해
public class QMember extends EntityPathBase<Member> {
    public static final QMember member = new QMember("member");
    
    public final NumberPath<Long> id = createNumber("id", Long.class);
    public final StringPath name = createString("name");
    public final NumberPath<Integer> age = createNumber("age", Integer.class);
    public final QTeam team;
    
    // ... 생성자 및 기타 메서드
}

 

QueryDSL은 메서드 이름을 간결하게 짓고, 조건을 재사용할 수 있다. 또한, 동적 쿼리 생성이 용이하고 컴파일 시점에 오류를 확인할 수 있다는 장점이 있다!

 

 

이후에도 QueryDSL 을 사용해보면서 좀 더 익숙해져 보자🖐️🖐️