Querydsl
QueryDSL과 JPA 연동: 커스텀 리포지토리 구현 심층 분석¶
1. 기본 개념 및 용어 정의¶
- QueryDSL: 타입 안전한 SQL 쿼리를 생성하는 프레임워크. 컴파일 시점에 오류 검출 가능.
- JPA (Java Persistence API): 자바 객체와 관계형 데이터베이스 매핑을 위한 표준 인터페이스.
- Spring Data JPA: JPA를 추상화하여 CRUD 작업을 간소화하는 프레임워크.
- 커스텀 리포지토리: Spring Data JPA의 기본 메서드로 처리할 수 없는 복잡한 쿼리를 구현하기 위한 확장 패턴.
2. 잘못된 코드 예시 및 문제점¶
// ❌ 문제점 1: QuerydslRepositorySupport의 과도한 의존
public class UserRepositoryImpl extends QuerydslRepositorySupport implements UserRepositoryCustom {
public UserRepositoryImpl() {
super(User.class); // EntityManager 주입 누락
}
@Override
public List findUsersWithComplexCriteria(String firstName, Integer minAge) {
QUser user = QUser.user;
JPQLQuery query = from(user); // ❌ 비효율적 쿼리 생성
if (firstName != null) {
query.where(user.firstName.eq(firstName)); // ❌ 동적 조건 처리 미흡
}
if (minAge != null) {
query.where(user.age.gt(minAge));
}
return query.fetch();
}
}
주요 문제점¶
- EntityManager 주입 누락:
QuerydslRepositorySupport는 내부적으로EntityManager를 사용하지만, 명시적 주입이 없어 NPE 발생 가능. - 동적 쿼리 처리 미흡:
if문을 통한 조건 추가는 가독성을 해치고 유지보수 어려움. - JPQLQuery 직접 사용:
JPAQueryFactory를 사용하지 않아 타입 안전성과 유연성 저하.
3. 올바른 구현 방식 및 최적화 전략¶
3.1 JPAQueryFactory 사용¶
@Repository
public class UserRepositoryImpl implements UserRepositoryCustom {
private final JPAQueryFactory queryFactory;
public UserRepositoryImpl(EntityManager em) {
this.queryFactory = new JPAQueryFactory(em); // ✅ EntityManager 주입
}
@Override
public List findUsersWithComplexCriteria(String firstName, Integer minAge) {
QUser user = QUser.user;
// ✅ BooleanBuilder로 동적 쿼리 구성
BooleanBuilder builder = new BooleanBuilder();
if (firstName != null) {
builder.and(user.firstName.eq(firstName));
}
if (minAge != null) {
builder.and(user.age.gt(minAge));
}
return queryFactory
.selectFrom(user)
.where(builder)
.fetch();
}
}
개선된 점¶
- 의존성 주입:
EntityManager를 통해JPAQueryFactory생성. - BooleanBuilder 활용: 동적 쿼리 구성이 명확해지고 확장성 증가.
- 타입 안전성 강화:
selectFrom()을 사용한 컴파일 시점 검증.
3. Spring Data JPA 통합¶
// ✅ 기본 리포지토리 확장
public interface UserRepository
extends JpaRepository, UserRepositoryCustom {
}
// ✅ 설정 클래스에 JPAQueryFactory 빈 등록
@Configuration
public class QuerydslConfig {
@Bean
public JPAQueryFactory jpaQueryFactory(EntityManager em) {
return new JPAQueryFactory(em);
}
}
중요 포인트¶
- 빈 등록 필수:
JPAQueryFactory는 스프링 빈으로 등록해야 의존성 주입 가능. - 커스텀 인터페이스 분리: 비즈니스 로직과 기본 CRUD 작업을 명확히 분리.
4. 성능 최적화 팁¶
- 컴파일된 쿼리 사용:
QClass가static final로 선언되었는지 확인. - 페이징 처리:
offset(),limit()을 활용한 페이지네이션. - 벌크 연산:
update(),delete()절에서execute()호출 시 영속성 컨텍스트 초기화 필수.
5. 자주 묻는 질문 (FAQ)¶
Q. QuerydslRepositorySupport vs JPAQueryFactory 어떤 것을 사용해야 하나요?
A. JPAQueryFactory가 더 현대적인 접근 방식이며, 코드 가독성과 유지보수성이 우수합니다.
Q. 동적 쿼리를 구현할 때 BooleanBuilder 외 다른 방법은?
A. WhereClause와 람다를 결합한 메서드 체이닝 방식도 가능합니다.
return queryFactory
.selectFrom(user)
.where(
firstName == null ? null : user.firstName.eq(firstName),
minAge == null ? null : user.age.gt(minAge)
)
.fetch();
Q. fetch() vs fetchOne() vs fetchFirst() 차이는?
A.
- fetch(): 전체 결과 리스트 반환
- fetchOne(): 단일 결과 반환 (결과 없거나 둘 이상이면 예외)
- fetchFirst(): 첫 번째 결과 반환 (결과 없으면 null)
📌 심화 학습 제안¶
"QueryDSL에서 서브쿼리와 윈도우 함수를 효율적으로 사용하는 방법은 무엇인가요?"
복잡한 분석 쿼리 작성 시 서브쿼리와 윈도우 함수(ROW_NUMBER(), RANK())를 활용하면 성능을 크게 향상시킬 수 있습니다. PostgreSQL의 FILTER (WHERE ...) 절이나 MySQL의 OVER() 구문과의 통합 사례를 연구해 보세요.