안녕하세요. 개발자 Jindory입니다.
오늘은 QueryDsl 동적으로 정렬 조건 적용하기(OrderSpecifier 클래스 구현)에 대해서 작성해보고자 합니다. 본 글은 seungho1216님과 yshjft 글을 참고해서 작성하였습니다.
# 글 작성 이유
pageable의 정렬조건으로 데이터를 정렬하고 싶은데, 어떤 방식으로 전달받은 정렬조건을 QueryDsl에 적용하는지 찾아보다가 아래의 방식으로 동적 정렬조건을 적용할 수 있는 방법을 알게되어 정리하고자 글을 작성하게 되었습니다.
# 정적으로 단일/다중 정렬 조건 적용하기
먼저 제가 사용한 Entity와 QueryDsl에 대해서 코드를 공유하고 정적으로 정렬조건 적용하는 방법에 대해서 설명하도록 하겠습니다.
@Entity
@Getter
@ToString
@Table(name = "JOINING")
@NoArgsConstructor
public class JoinEntity extends BaseEntity {
@Id @GeneratedValue
@Column(name="JOIN_ID")
private Long id;
@Enumerated(EnumType.STRING)
@Column(name="STATUS_TYPE")
private StatusType statusType;
@Enumerated(EnumType.STRING)
@Column(name="REQUESTER_TYPE")
private RequesterType requesterType;
@ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
@JoinColumn(name="PLAYER_ID")
private PlayerEntity player;
@ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
@JoinColumn(name="TEAM_ID")
private TeamEntity team;
@Column(name="ACTIVE_YN")
private char activeYN;
}
private final JPAQueryFactory queryFactory;
QJoinEntity join = QJoinEntity.joinEntity;
QPlayerEntity player = QPlayerEntity.playerEntity;
QTeamEntity team = QTeamEntity.teamEntity;
queryFactory
.select(Projections.fields(JoinDto.class,join.id,join.player.id.as("playerId"),join.player.playerName,join.team.id.as("teamId"),join.team.teamName,join.requesterType,join.statusType,join.activeYN,join.createdDate,join.updatedDate))
.from(join)
.join(join.player,player)
.where(join.requesterType.eq(condition.getRequesterType()),eqStatusType(condition.getStatusType()),eqPlayerId(playerId),join.activeYN.eq('Y'),betweenDate(condition.getFromDate(),condition.getToDate()))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(join.id.asc(),join.updatedDate.desc())
.fetchResults();
정적으로 정렬조건을 셋팅하고 싶을때는 entity의 속성을 가져온 다음에 오름차순으로 정렬할지 내림차순으로 정렬할지 정의해주면 됩니다.
단일 조건일 경우 정렬 parameter를 하나만 넘기면 되고, 다중 조건으로 정렬할 경우 ','로 구분하여 정렬 parameter를 전달하면 됩니다.
ex) orderBy(join.id.asc())[단일] / orderBy(join.id.asc(),join.updatedDate.desc())[다중]
# 동적으로 정렬조건 적용하기
그렇다면 동적으로 정렬조건을 넘기려면 어떻게 할까요?
먼저 pageable의 Sort정보를 orderBy에 파라미터로 넘기면 아래와 같은 결과가 나옵니다.
Error message는 아래와 같이 나옵니다.
Multiple markers at this line - Line breakpoint:JoinRepositoryImpl [line: 49] - findPlayerJoinApplication(JoinSearchCondition, Long, Pageable) - The method orderBy(OrderSpecifier<?>) in the type QueryBase<JPAQuery<JoinDto>> is not applicable for the arguments (Sort)
따라서 orderBy에 맞는 파라미터로 바꿔서 넘겨줘여할것 같습니다.
# OrderBy 메서드 알아보기
orderBy의 매개변수에 대한 정의는 아래와 같이 나와 있습니다.
/**
* Add a single order expression
*
* @param o order
* @return the current object
*/
public Q orderBy(OrderSpecifier<?> o) {
return queryMixin.orderBy(o);
}
/**
* Add order expressions
*
* @param o order
* @return the current object
*/
public Q orderBy(OrderSpecifier<?>... o) {
return queryMixin.orderBy(o);
}
Q-Type 인스턴스의 OrderBy의 parameter을 봤을때 단일조건과 다중조건일때 위와 같이 Parameter를 넘기는것으로 정의되어 있습니다.
(Parameter에 ...은 가변인수(Ellipsis)로 매개변수의 인자들이 개수와 상관없이 같을경우 사용하는 표현입니다. 위의 상황을 설명하면 OrderSpecifier 여러개를 넘긴다고 생각하면 됩니다.)
# OrderSpecifier 알아보기
그렇다면 orderBy에 동적으로 정렬조건을 넘겨주려면 OrderSpecifier에 대해서 이해를 해야할것 같습니다.
구글링을 해보니 OrderSpecifier는 쿼리 인스턴스의 요소별 순서를 나태내는 요소라고 나와 있습니다.
OrderSpecifier represents an order-by-element in a Query instance
위에서 정적 정렬조건으로 넣었던 join.id.asc()와 join.updatedDate.desc()는 OrderSpecifier Type이라서 매개변수로 넘길 수 있었음을 알 수 있습니다.
그렇다면 pageable에서 전달받은 정렬조건을 동적으로 만드려면 어떤식으로 구현해야 할까요?
OrderSpecifier의 생성자를 아래와 같이 알아봤습니다.
OrderSpecifer의 생성자는 Order, Expression<T>로 이루어져 있는데.......
Expression에 대한 설명은 아래와 같이 나와 있으나.... 이해가 가지 않아 다른 블로그에서 어떻게 사용하는지 참고했습니다.
An Expression represents a mathematical expression such as "x+1" or "3" or "sin(x*ln(x)-3*abs(x/4))". An expression has a value, which can depend on the values of variables that occur in the expression. An expression can be differenetiated with respect to a variable. It has a print string representation. This interface is implemented by the classes Constant, Variable, and ExpressionProgram, for example. The Expression interface represents all the properties of expressions that you are likely to need to know about, unless you want to write a new kind of ExpressionCommand.
https://uchupura.tistory.com/7
위 블로그의 Expression parameter에 대한 정보를 파악해 본 결과, Expression target은 정렬 기준이 되는 칼럼의 Path로, 기준이 되는 칼럼과 그 칼럼이 속한 엔티티가 합쳐진것으로 보입니다.
예를들어 id으로 정렬을 하고 싶다면, 해당 칼럼은 Entity(joinEntity)에 속할 것이므로 이 두개가 합쳐진 joinEntity.id가 Path가 되는것입니다.
# 동적으로 정렬조건 적용하기[단일]
그럼 이제 pageable로 넘어온 Sort 조건을 가지고 동적 정렬조건[단일]을 적용하는 방법에 대해서 정리해보겠습니다.
// 동적 정렬을 위한 단일 OrderSpecifier 추출 method
private OrderSpecifier getOrderSpecifiers(Pageable pageable) {
// 순회하며 정렬조건대로 OrderSpecifier 반환
if(!pageable.getSort().isEmpty()) {
for(Sort.Order order : pageable.getSort()) {
Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC;
switch(order.getProperty()) {
case "statusType":
return new OrderSpecifier(direction,join.statusType);
case "requesterType":
return new OrderSpecifier(direction,join.requesterType);
case "playerName":
return new OrderSpecifier(direction,join.player.playerName);
case "teamName":
return new OrderSpecifier(direction,join.team.teamName);
case "updatedDate":
return new OrderSpecifier(direction,join.updatedDate);
default:
return new OrderSpecifier(direction,join.id);
}
}
}
return new OrderSpecifier(Order.ASC,join.id);
}
pageable에 Sort를 가져와서 해당 Property에 따라서 위와 같이 switch 문으로 구현할 수 있습니다.
아래와 같이 전달된 Parameter Path 대로 반환하는 방식으로도 구현이 가능합니다.
private OrderSpecifier getOrderSpecifiers(Pageable pageable) {
// 순회하며 정렬조건대로 OrderSpecifier 반환
if(!pageable.getSort().isEmpty()) {
for(Sort.Order order : pageable.getSort()) {
Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC;
String prop = order.getProperty();
PathBuilder orderPath = new PathBuilder(join.getClass(),"joinEntity");
new OrderSpecifier(direction,orderPath.get(prop));
}
}
return new OrderSpecifier(Order.ASC,join.id);
}
※ 단, Entity안에 Entity의 Property로 정렬하고자 하는경우 pageable parameter를 넘길 때 property값을 잘 전달해줘야 에러가 발생하지 않습니다.
orderBy에는 아래와 같이 pageable을 매개변수로 넘겨 작성하면 됩니다.
QueryResults<JoinDto> results = queryFactory
.select(Projections.fields(JoinDto.class,join.id,join.player.id.as("playerId"),join.player.playerName,join.team.id.as("teamId"),join.team.teamName,join.requesterType,join.statusType,join.activeYN,join.createdDate,join.updatedDate))
.from(join)
.join(join.player,player)
.where(join.requesterType.eq(condition.getRequesterType()),eqStatusType(condition.getStatusType()),eqPlayerId(playerId),join.activeYN.eq('Y'),betweenDate(condition.getFromDate(),condition.getToDate()))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(getOrderSpecifiers(pageable))
.fetchResults();
그렇다면 pageable에는 어떻게 정렬조건을 셋팅해야할까요?
Entity의 property를 기준으로 정렬하는 경우 해당 property 명을 입력하면 되고, 만일 Entity의 Entity property로 정렬한다면 Entity 속에 정의된 또 다른 Entity명+'.'+property Name을 입력해줘야 합니다.
// Entity의 property를 기준으로 정렬하는 경우
PageRequest page = PageRequest.of(0, 10, Sort.by("updatedDate").descending());
// Entity의 Entity의 property 기준으로 정렬하는경우
PageRequest page = PageRequest.of(0, 10, Sort.by("player.playerName").descending());
# 동적으로 정렬조건 적용하기[다중]
이제 동적으로 정렬조건 적용하기[다중]에 대해서 설명해 보도록 하겠습니다.
단일과 크게 다른것은 없고, 전달받은 pageabel의 정렬조건을 List 형식의 OrderSpecifier에 추가하여 반환하면 됩니다.
// 동적 정렬을 위한 다중 OrderSpecifier 추출 method
private List<OrderSpecifier> getAllOrderSpecifiers(Pageable pageable){
List<OrderSpecifier> orders = new ArrayList<>();
if(!pageable.getSort().isEmpty()) {
for(Sort.Order order : pageable.getSort()) {
Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC;
String prop = order.getProperty();
PathBuilder orderPath = new PathBuilder(join.getClass(),"joinEntity");
orders.add(new OrderSpecifier(direction,orderPath.get(prop)));
}
}
return orders;
}
OrderBy는 아래와 같이 Paramter를 적용하면 됩니다.
List를 여러개의 OrderSpecifier로 넘겨야 하기 때문에 stream의 toArray를 사용하여 넘겨줍니다.
List<OrderSpecifier> orders = getAllOrderSpecifiers(pageable);
QueryResults<JoinDto> results = queryFactory
.select(Projections.fields(JoinDto.class,join.id,join.player.id.as("playerId"),join.player.playerName,join.team.id.as("teamId"),join.team.teamName,join.requesterType,join.statusType,join.activeYN,join.createdDate,join.updatedDate))
.from(join)
.join(join.player,player)
.where(join.requesterType.eq(condition.getRequesterType()),eqStatusType(condition.getStatusType()),eqPlayerId(playerId),join.activeYN.eq('Y'),betweenDate(condition.getFromDate(),condition.getToDate()))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(orders.stream().toArray(OrderSpecifier[]::new))
.fetchResults();
pageable에도 다중으로 정렬조건을 넘겨줘야겠죠?
pageable에서 다중으로 넘기는것은 아래와 같이 작성하면 됩니다.
// Entity에 있는 property만 넘길때
PageRequest page = PageRequest.of(0, 10, Sort.by("updatedDate").descending().and(Sort.by("id")));
// Entity의 Entity property를 넘길때
PageRequest page = PageRequest.of(0, 10, Sort.by("player.playerName").descending().and(Sort.by("id")));
이렇게 QueryDsl 동적으로 정렬 조건 적용하기(OrderSpecifier 클래스 구현)에 대해서 알아봤습니다.
혹시라도 정정할 내용이나 추가적으로 필요하신 정보가 있다면 댓글 남겨주시면 감사하겠습니다.
오늘도 Jindory 블로그에 방문해주셔서 감사합니다.
[참조]
https://pathas.tistory.com/129
'개발 > JPA' 카테고리의 다른 글
[JPA] Column에 default Value 설정하기 (0) | 2022.07.02 |
---|---|
[JPA] Eclipse에서 QueryDsl 설정하기 (0) | 2022.05.21 |
[JPA] JPA 설정 방법 (0) | 2022.05.01 |
[JPA] JPA 관련 application.properties 설정 (0) | 2022.04.27 |
[JPA] 객체지향 쿼리 (0) | 2022.02.22 |