심화 프로젝트를 진행하면서 동적쿼리에 대한 정리와 동적쿼리 중 정렬(orderBy)에 사용되는 OrderSpecifier을 사용하여 공통 코드를 작성해보려고 한다.
동적쿼리란 사용자의 입력에 따라 조건이나 정렬 방식을 유현하게 변경하는 기능이다.
리뷰를 별점이 있다고 가정했을때 별점 많은순 별점 적은순 등 사용자가 원하는 입력에 따라 결과를 변경할 수 있게 한다.
그중 OrderSpecifier는 QueryDSL에서 쿼리 결과를 정렬하기 위한 클래스 이다.
아래의 코드는 Pageable을 사용한 간단하게 작성한 공통 처리 코드이다.
Entity, Controller, Dto, Serive, Repository를 간단하게 구성후 테스트를 진행
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Entity
@Table(
name = "p_store"
)
public class Store extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "id")
private UUID id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Column(name = "name", nullable = false)
private String name;
@OneToMany(mappedBy = "store",fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<StoreCategory> storeCategory = new ArrayList<>();
@Column(name = "address", nullable = false)
private String address;
@Column(name = "call_number", nullable = false)
private String callNumber;
@Column(name = "store_grade", nullable = false)
private double storeGrade;
@Column(name = "store_grade_reviews", nullable = false)
private int storeGradeReviews;
}
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseTimeEntity {
@CreatedDate
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@Setter
@Column(name = "deleted_at")
private LocalDateTime deletedAt;
@Setter
@CreatedBy
@Column(name = "created_by", updatable = false)
private UUID createdBy;
@LastModifiedBy
@Column(name = "updated_by")
private UUID updatedBy;
@Setter
@Column(name = "deleted_by")
private UUID deletedBy;
}
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1")
@Tag(name = "매장 관련 API")
public class StoreController {
private final StoreService storeService;
@GetMapping("/stores/test")
public ResponseEntity<CommonResponse<Slice<TestResponse>>> testStoreGradeQuery(
Pageable pageable
){
return CommonResponse.success(SuccessCode.SUCCESS,
storeService.testStoreGradeQuery(pageable));
}
}
@Getter
@AllArgsConstructor
public class TestResponse {
private final UUID storeId;
private final String storeName;
private final double storeGrade;
}
@Service
@RequiredArgsConstructor
public class StoreService {
private final StoreJpaRepository storeJpaRepository;
private final StoreQueryRepository storeQueryRepository;
@Transactional(readOnly = true)
public Slice<TestResponse> testStoreGradeQuery(Pageable pageable){
return storeQueryRepository.testStoreQuery(pageable);
}
}
@Repository
@RequiredArgsConstructor
public class StoreQueryRepository {
private final JPAQueryFactory jpaQueryFactory;
QStore qStore = QStore.store;
public Slice<TestResponse> testStoreQuery(Pageable pageable){
List<TestResponse> testResponseList = jpaQueryFactory.query()
.select(
Projections.constructor(
TestResponse.class,
qStore.id,
qStore.name,
qStore.storeGrade
)
).from(qStore)
.orderBy(getOrderSpecifierStore(pageable))
.limit(pageable.getPageSize() + 1)
.fetch();
boolean hasNext = testResponseList.size() > pageable.getPageSize();
if (hasNext) {
testResponseList.remove(testResponseList.size() - 1);
}
return new SliceImpl<>(testResponseList, pageable, hasNext);
}
private OrderSpecifier<?>[] getOrderSpecifierStore(Pageable pageable) {
return pageable
.getSort()
.stream()
.map(order -> {
PathBuilder pathBuilder = new PathBuilder<>(QStore.store.getType(), QStore.store.getMetadata());
return new OrderSpecifier(
order.isAscending() ? Order.ASC : Order.DESC,
pathBuilder.get(order.getProperty()));
}).toArray(OrderSpecifier[]::new);
}
}
매장 평점이 1, 2, 3 인 매장이 있다고 가정하고 평점으로 정렬 테스트를 진행
오름차순 정렬
내림차순 정렬
아래의 코드로 Pageable에 Entity의 필드값에 대한 정렬옵션을 받아서 공통으로 처리하는 메서드를 사용할 수 있지만
private OrderSpecifier<?>[] getOrderSpecifierStore(Pageable pageable) {
return pageable
.getSort()
.stream()
.map(order -> {
PathBuilder pathBuilder = new PathBuilder<>(QStore.store.getType(), QStore.store.getMetadata());
return new OrderSpecifier(
order.isAscending() ? Order.ASC : Order.DESC,
pathBuilder.get(order.getProperty()));
}).toArray(OrderSpecifier[]::new);
}
커스텀으로 OrderSpecifier 메서드를 만들어서 해당 객체에대해서 정렬 옵션을 받을수 있다. 아래의 코드는 Enum을 사용한 간단하게 구현한 예시이다.
만약 추가하고 싶다면 옵션을 추가해서 추가로 작성해서 사용하면 된다!
public enum SortOption {
CREATED_AT_ASC,
CREATED_AT_DESC,
UPDATED_AT_ASC,
UPDATED_AT_DESC,
STORE_STAR_RATING, // 스토어 별점순
}
private OrderSpecifier<?> getOrderSpecifier(SortOption sortOption) {
return switch (sortOption) {
case CREATED_AT_DESC -> qStore.createdAt.desc();
case UPDATED_AT_ASC -> qStore.updatedAt.asc();
case UPDATED_AT_DESC -> qStore.updatedAt.desc();
case STORE_STAR_RATING -> qStore.storeGrade.desc();
default -> qStore.createdAt.asc();
};
}
'심화 캠프 정리' 카테고리의 다른 글
ModelAttribute (0) | 2024.11.20 |
---|---|
PageableArgumentResolver (0) | 2024.11.20 |
동적쿼리 BooleanExpression (0) | 2024.11.20 |
PreAuthorize (2) | 2024.11.19 |
생성자 수정자 자동으로 생성하기 (0) | 2024.11.19 |