QueryDSL @QueryDelegate 어노테이션 사용해 보기
'QueryDSL'은 정접 타입을 이용해서 SQL 등의 쿼리를 생성해 주는 오픈 소스 프로젝트이며, 이름 그대로 query(데이터 조회)에 특화되어 있습니다.
* dsl(Domain Specific Language) 특정 도메인을 적용하는데 특화된 프로그래밍 언어
문자가 아닌 코드로 쿼리를 작성하기 때문에 컴파일 시점에서 문법 오류를 발생할 수 있다는 장점이 있으며, 동적인 쿼리 작성이 편리하고, 쿼리의 제약 조건 등을 메서드 추출을 통해 재사용할 수 있다는 장점이 있는데요.
해당 포스팅은 '@QueryDelegate Annotation을 통해 QueryDSL의 where 조건절을 간결하게 사용하는 방법'에 대해 정리한 내용입니다.
예시 상황
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Clothes {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String code;
private Category category;
private Long price;
private Boolean isExposed;
@Getter
public enum Category {
TOP,
PANTS,
OUTER,
DRESS;
}
}
(Clothes Entity class 및 Category)
public enum RequestState {
A, B, C;
}
(RequestState Enum)
@Override
public List<Clothes> findAllByRequestState(RequestState requestState) {
BooleanBuilder builder = new BooleanBuilder();
if (requestState.equals(RequestState.A)) {
builder.and(clothes.category.eq(Clothes.Category.TOP)).and(clothes.price.between(10000L, 30000L));
}
else if (requestState.equals(RequestState.B)) {
builder.and(clothes.category.eq(Clothes.Category.TOP)).and(clothes.price.between(40000L, 50000L));
}
else if (requestState.equals(RequestState.C)) {
builder.and(clothes.category.eq(Clothes.Category.PANTS)).and(clothes.price.between(30000L, 50000L));
}
JPAQuery<Clothes> query = new JPAQuery<>(em);
return query.select(clothes)
.from(clothes)
.where(builder)
.fetch();
}
(RepositoryImpl class에 구현된 findAllByRequestState method)
* 해당 코드는 상황적 이해를 돕기 위한 코드라는 점 참고 부탁드립니다.
다음과 같은 코드를 예로 들어 RequestState 타입의 파라미터 값에 따라 공통적으로 적용되는 where 조건절이 있을 수 있는데요.
만약 requestState에 따라 다음과 같은 조건이 적용되는 부분이 예시의 findAllByRequestState() 메서드 하나가 아니라 여러 개가 존재한다면? 그리고 계속 추가된다면?
각 메서드마다 위 코드와 같이 requestState에 따른 분기 처리로 where 조건을 설정해줘야 하는 상황이 발생할 것입니다.
때문에 같은 코드를 계속 적용해 주는 과정에서 개발자의 실수로 인해 빠지는 부분이 생겨 쿼리의 결과가 원하는 대로 나오지 않는 등의 문제도 발생할 수도 있는데요.
이 같은 상황에서 해당 request parameter에 따른 where 조건을 설정하는 메서드를 만들어서 공통적으로 사용할 수 있으며, 거기서 한 단계 더 유용하게 사용할 수 있는 것이 바로 '@QueryDelegate(메서드 위임 기능)' 어노테이션입니다.
@QueryDelegate
public class ClothesExpression {
@QueryDelegate(Clothes.class)
public static BooleanExpression byRequestState(QClothes clothes, RequestState requestState) {
if (requestState.equals(RequestState.A)) {
return clothes.category.eq(Clothes.Category.TOP).and(clothes.price.between(10000L, 30000L));
}
else if (requestState.equals(RequestState.B)) {
return clothes.category.eq(Clothes.Category.TOP).and(clothes.price.between(40000L, 50000L));
}
else if (requestState.equals(RequestState.C)) {
return clothes.category.eq(Clothes.Category.PANTS).and(clothes.price.between(30000L, 50000L));
} else {
return null;
}
}
}
메서드 위임 기능을 사용하기 위해서는 정적(static) 메서드를 만들고, 해당 메서드에 @QueryDelegate 어노테이션을 적용, 그리고 속성으로 해당 메서드를 적용할 엔티티(Entity)를 지정하면 됩니다.
이렇게 @QueryDelegate 어노테이션을 적용한 정적 메서드를 만들고 다시 QClass를 생성하게 되면, 아래와 같이 해당 QClass에 위 ClothesExpression에서 생성한 정적 메서드를 사용하는 메서드가 생긴 것을 확인할 수 있는데요.
@Override
public List<Clothes> findAllByRequestState(RequestState requestState) {
JPAQuery<Clothes> query = new JPAQuery<>(em);
return query.select(clothes)
.from(clothes)
.where(clothes.byRequestState(requestState))
.fetch();
}
사용 방법은 다음과 같이 QClass에서 해당 메서드를 바로 호출하여 사용할 수 있습니다.
이처럼 @QueryDelegate를 사용하여 메서드를 위임하게 되면 해당 QClass에서 메서드화 된 조건절을 간편하게 적용할 수 있는데요.
또한 메서드의 네이밍을 직접 할 수 있기 때문에 where 절에 직접 조건을 거는 것보다 byRequestState처럼 해당 기능의 의도를 명확하게 나타낼 수 있다는 장점도 있습니다.
+++
@QueryDelegate(String.class)
public static BooleanExpression isCodeStartWithA(StringPath stringPath) {
return stringPath.startsWith("A");
}
추가로 String, Date, Timestamp와 같은 Java 기본 내장 타입에 대해서도 @QueryDelegate 어노테이션을 통한 메서드 위임 기능을 적용할 수 있습니다.
(기본 타입에 대해서도 Q 클래스가 생성됩니다.)
JPAQuery<Clothes> query = new JPAQuery<>(em);
return query.select(clothes)
.from(clothes)
.where(clothes.code.isCodeStartWithA()
.fetch();
(사용 방법)
< 참고 자료 >
http://querydsl.com/static/querydsl/3.4.2/reference/html/ch03s03.html
'Programming > Spring Boot' 카테고리의 다른 글
Spring Boot 자동 구성 AutoConfiguration 동작 원리 파헤치기 (0) | 2023.07.21 |
---|---|
@Conditional 어노테이션의 기능과 확장(@ConditionalOnXxx) (0) | 2023.07.18 |
JPA 연관 관계 매핑 - 조인 테이블(@JoinTable) 개념과 적용 방법 (0) | 2023.06.21 |
Redisson 분산락을 사용하는 이유와 기본적인 사용 방법 (1) | 2023.06.17 |
Spring Boot + PayPal 결제 구현해 보기 (sandbox 테스트 환경) (0) | 2023.06.06 |