μ΄μ κΈμμ 무νμ€ν¬λ‘€, 컀μ κΈ°λ° νμ΄μ§λ€μ΄μ μ λν΄ μκ°νμ΅λλ€.
π€ μ€νμ κΈ°λ°μ μΈμ μ¬μ©ν κΉ?
μ΄μ κΈμμ μΈκΈνλ λλ‘ μ€νμ κΈ°λ° νμ΄μ§λ€μ΄μ μ 컀μ κΈ°λ° νμ΄μ§λ€μ΄μ μ λΉν΄ μ±λ₯μ λ¨μ΄μ§λλ€.
νμ§λ§ μ΄λ offset
κ°μ΄ 컀μ§μ λ°λΌ λ°μνλ λ¨μ μ΄κΈ° λλ¬Έμ μ‘°νν λ°μ΄ν°μ μμ΄
λ§μ§ μλ€λ©΄ μ€νμ κΈ°λ° νμ΄μ§λ€μ΄μ λ μΆ©λΆν νμ©ν μ μμ΅λλ€.
컀μ κΈ°λ° νμ΄μ§λ€μ΄μ μ μ λ ¬ μ‘°κ±΄μ΄ λ³΅μ‘ν΄μ§λ©΄ 볡μ‘ν΄μ§μλ‘ μ»€μλ₯Ό μ μ νλλ° μ΄λ €μμ΄ μμ΅λλ€.
νμ§λ§ μ€νμ
κΈ°λ° νμ΄μ§λ€μ΄μ
μ Pageable
μΈν°νμ΄μ€λ₯Ό νμ©ν΄ λ°μ΄ν° μ λ ¬μ νΈλ¦¬νκ² ν μ μμ΅λλ€.
π μ‘°νν λ°μ΄ν°κ° λ§μ§ μκ±°λ μ λ ¬ μ‘°κ±΄μ΄ λ³΅μ‘ν κ²½μ° μ€νμ κΈ°λ° νμ΄μ§λ€μ΄μ μ νμ©ν΄λ³΄μ.
π μ€νμ κΈ°λ° νμ΄μ§λ€μ΄μ ꡬν
μ€νμ κΈ°λ° νμ΄μ§λ€μ΄μ μ νμ©ν μ€μ 컨νΈλ‘€λ¬μ λλ€.
@ApiOperation("[μΈμ¦] νΉμ 쑰건μ ν΄λΉνλ ν°μΌ λͺ©λ‘μ νμ΄μ§λ€μ΄μ
μΌλ‘ μ‘°νν©λλ€.")
@Auth
@GetMapping("/v1/ticket")
public ApiResponse<TicketPagingResponse> retrieveTickets(
@Valid RetrieveTicketsRequestDto request,
@AllowedSortProperties({"createdAt", "rating"}) Pageable pageable,
@ApiIgnore @UserId Long userId) {
return ApiResponse.success(SuccessCode.READ_TICKET_SUCCESS, ticketRetrieveService.retrieveTicketsUsingPaging(request, pageable, userId));
}
- μ μμ²μ ν°μΌμ μΉ΄ν κ³ λ¦¬λ‘ νν°λ§ ν ν, μμ± μκ°, νμ μΌλ‘ μ λ ¬ν΄μ νμ΄μ§λ€μ΄μ μΌλ‘ μ‘°ννλ μμ²μ λλ€.
- 컨νΈλ‘€λ¬μμ μμ κ°μ΄
Pageable
μΈν°νμ΄μ€λ₯Ό νλΌλ―Έν°λ‘ λ°μ μ μμ΅λλ€.κ·Έλ¬λ©΄ λ€μκ³Ό κ°μ΄page
,size
,sort
νλΌλ―Έν°λ₯Ό μ λ ₯λ°κ² λ©λλ€.localhost:8080/v1/ticket?category=&page=&size=&sort=

ν΄λΌμ΄μΈνΈμκ² μ λ¬ν dtoμΈ TicketPagingResponse ν΄λμ€μ λλ€.
@ToString
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TicketPagingResponse {
private static final long LAST_PAGE = -1L;
private List<TicketInfoResponse> contents = new ArrayList<>();
private long lastPage;
private long nextPage;
private TicketPagingResponse(List<TicketInfoResponse> contents, long lastPage, long nextPage) {
this.contents = contents;
this.lastPage = lastPage;
this.nextPage = nextPage;
}
public static TicketPagingResponse of(Page<Ticket> ticketPaging) {
if (!ticketPaging.hasNext()) {
return TicketPagingResponse.newLastScroll(ticketPaging.getContent(), ticketPaging.getTotalPages() - 1);
}
return TicketPagingResponse.newPagingHasNext(ticketPaging.getContent(), ticketPaging.getTotalPages() - 1, ticketPaging.getPageable().getPageNumber() + 1);
}
private static TicketPagingResponse newLastScroll(List<Ticket> ticketPaging, long lastPage) {
return newPagingHasNext(ticketPaging, lastPage, LAST_PAGE);
}
private static TicketPagingResponse newPagingHasNext(List<Ticket> ticketPaging, long lastPage, long nextPage) {
return new TicketPagingResponse(getContents(ticketPaging), lastPage, nextPage);
}
private static List<TicketInfoResponse> getContents(List<Ticket> ticketPaging) {
return ticketPaging.stream()
.map(TicketInfoResponse::of)
.collect(Collectors.toList());
}
}
- μ΄μ κΈμμ μκ°ν Response dto μ ννμ μ μ¬νκΈ° λλ¬Έμ μμΈν μ€λͺ μ μλ΅νκ² μ΅λλ€.
- μ¬κΈ°μ μ§μ€ν΄μ λ΄μΌν κ²μ
of
λ©μλμ μΈμλ‘Page<T>
μΈν°νμ΄μ€λ₯Ό λ°λ κ²μ λλ€.
Page<T>
μΈν°νμ΄μ€λgetContent
,getTotalPages
,getPageable
κ°μ λ©μλλ₯Ό κ°μ§κ³ μκ³ μ΄ λ©μλλ€μ νμ©ν΄μ κΈ°μ‘΄μ λ°©μκ³Ό λμΌνκ² νμ¬ νμ΄μ§κ° λ§μ§λ§ νμ΄μ§μΈμ§,λ€μ νμ΄μ§λ λͺ νμ΄μ§μΈμ§λ₯Ό νμΈν΄μ ν΄λΌμ΄μΈνΈμκ² λ°μ΄ν°λ₯Ό κ°κ³΅νμ¬ μ λ¬ν©λλ€.
μλΉμ€ λ‘μ§μ λλ€.
public TicketPagingResponse retrieveTicketsUsingPaging(RetrieveTicketsRequestDto request, Pageable pageable, Long userId) {
User user = UserServiceUtils.findUserById(userRepository, userId);
return TicketPagingResponse.of(ticketRepository.findTicketByFilterConditionUsingPaging(user.getOnboarding(), request.getCategory(), pageable));
}
TicketPagingResponse.of
λ©μλμ μΈμλ‘Page<T>
μΈν°νμ΄μ€λ₯Ό λ°λλ°,μ΄λ₯Ό querydsl κΈ°λ₯μ νμ©ν΄μ μ‘°νν©λλ€.
import static com.ticco.domain.ticket.QTicket.ticket;
@RequiredArgsConstructor
public class TicketRepositoryImpl implements TicketRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public Page<Ticket> findTicketByFilterConditionUsingPaging(Onboarding onboarding, @Nullable TicketCategory category, Pageable pageable) {
List<OrderSpecifier> orders = getAllOrderSpecifiers(pageable);
List<Ticket> tickets = queryFactory
.selectFrom(ticket).distinct()
.where(
ticket.onboarding.eq(onboarding),
eqCategory(category)
)
.orderBy(orders.toArray(OrderSpecifier[]::new))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
return new PageImpl<>(tickets, pageable, queryFactory
.selectFrom(ticket).distinct()
.where(
ticket.onboarding.eq(onboarding),
eqCategory(category)
).fetch().size());
}
private BooleanExpression eqCategory(TicketCategory category) {
if (category == null) {
return null;
}
return ticket.category.eq(category);
}
private List<OrderSpecifier> getAllOrderSpecifiers(Pageable pageable) {
List<OrderSpecifier> orders = new ArrayList<>();
for (Sort.Order order : pageable.getSort()) {
Order direction = order.getDirection().isAscending() ? Order.ASC : Order.DESC;
Path<Object> fieldPath = Expressions.path(Object.class, ticket, order.getProperty());
orders.add(new OrderSpecifier(direction, fieldPath));
}
return orders;
}
}
eqCategory
λ©μλ : 컨νΈλ‘€λ¬μμcategory
μΈμλ‘null
μ λ°μΌλ©΄ νν°λ§μ νμ§ μκ³ ,μΉ΄ν κ³ λ¦¬λ₯Ό λ°μΌλ©΄ ν΄λΉ μΉ΄ν κ³ λ¦¬μ λ§λ ν°μΌλ§ μ‘°νν μ μλλ‘BooleanExpression
μ return ν΄μ£Όλ λ©μλμ λλ€.
getAllOrderSpecifiers
λ©μλ : 컨νΈλ‘€λ¬μμsort
νλΌλ―Έν°λ‘ λ°μλ κ°μΌλ‘μ λ ¬ 쑰건μ return ν΄μ£Όλ λ©μλμ λλ€.
Page<Ticket> findTicketByFilterConditionUsingPaging
λ©μλ : 쑰건μ λ§κ² ν°μΌμ νν°λ§νκ³sort
νλΌλ―Έν°λ‘ λ°μ μ λ ¬ 쑰건μ λ°λΌ λ°μ΄ν°λ€μ μ λ ¬ν©λλ€.κ·Έλ¦¬κ³ μ λ ₯ λ°μoffset
,limit
νλΌλ―Έν°μ λ°λΌ λ°μ΄ν°λ₯Ό μ‘°νν©λλ€.Page<T>
νμ μΌλ‘ 리ν΄νκΈ° μν΄return new PageImpl<>
μ μ¬μ©ν©λλ€.
π€© μ€μ Response νμΈ
μ΅μ΄ μμ²μ page
κ°μΌλ‘λ 첫λ²μ§Έ νμ΄μ§μΈ 0μ λ΄μμ 보λ
λλ€.
GET localhost:8080/v1/ticket?category=MUSICAL?page=0?size=1?sort=createdAt,DESC
{
"status": 200,
"success": true,
"message": "ν°μΌ λͺ©λ‘ μ‘°ν μ±κ³΅μ
λλ€.",
"data": {
"contents": [
{
"ticketId": 6,
"category": "MUSICAL",
"rating": 2.7
}
],
"lastPage": 4,
"nextPage": 1
}
}
λ€μ μμ²μΌλ‘λ page
μ nextPage
κ°μΈ 1 μ λ΄μμ 보λ
λλ€.
GET localhost:8080/v1/ticket?category=MUSICAL?page=1?size=1?sort=createdAt,DESC
{
"status": 200,
"success": true,
"message": "ν°μΌ λͺ©λ‘ μ‘°ν μ±κ³΅μ
λλ€.",
"data": {
"contents": [
{
"ticketId": 5,
"category": "MUSICAL",
"rating": 4.8
}
],
"lastPage": 4,
"nextPage": 2
}
}
page
μ lastPage
κ°μΈ 4 λ₯Ό λ΄μμ μμ²ν΄λ³΄κ² μ΅λλ€.
GET localhost:8080/v1/ticket?category=MUSICAL?page=4?size=1?sort=createdAt,DESC
{
"status": 200,
"success": true,
"message": "ν°μΌ λͺ©λ‘ μ‘°ν μ±κ³΅μ
λλ€.",
"data": {
"contents": [
{
"ticketId": 2,
"category": "MUSICAL",
"rating": 1
}
],
"lastPage": 4,
"nextPage": -1
}
}
λ§μ§λ§ νμ΄μ§μ΄κΈ° λλ¬Έμ nextPage
μ -1 μ΄ λ΄κΈ΄ λͺ¨μ΅μ νμΈν μ μμ΅λλ€.
μ΄λ²μλ MUSICAL
μΉ΄ν
κ³ λ¦¬μ ν°μΌλ€μ νμ κΈ°μ€μΌλ‘ μ λ ¬λ λͺ¨μ΅μ νμΈν΄λ³΄κ² μ΅λλ€.
GET localhost:8080/v1/ticket?category=MUSICAL?page=0?size=10?sort=rating,DESC
{
"status": 200,
"success": true,
"message": "ν°μΌ λͺ©λ‘ μ‘°ν μ±κ³΅μ
λλ€.",
"data": {
"contents": [
{
"ticketId": 4,
"category": "MUSICAL",
"rating": 5
},
{
"ticketId": 5,
"category": "MUSICAL",
"rating": 4.8
},
{
"ticketId": 3,
"category": "MUSICAL",
"rating": 3
},
{
"ticketId": 6,
"category": "MUSICAL",
"rating": 2.7
},
{
"ticketId": 2,
"category": "MUSICAL",
"rating": 1
}
],
"lastPage": 0,
"nextPage": -1
}
}
νμ κΈ°μ€μΌλ‘ μ λ ¬μ΄ μ λκ³ λ°μ΄ν°κ° 10 κ°κ° μλκΈ° λλ¬Έμ μ‘΄μ¬νλ λ°μ΄ν°κΉμ§λ§ μ‘°νλκ³ λ μ΄μ μ‘°νν λ°μ΄ν°κ° μκΈ° λλ¬Έμ nextPage
μ -1 μ΄ λ΄κΈ΄ λͺ¨μ΅μ νμΈν μ μμ΅λλ€.
'βοΈ λ°±μλ: Backend' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
[Spring] Slack API νμ©νμ¬ μλ¬ λͺ¨λν°λ§νκΈ° (0) | 2023.08.03 |
---|---|
[Spring] JPA μμμ± μ»¨ν μ€νΈ μ΄ν΄νκΈ° (0) | 2023.06.27 |
[Spring] νΌλλλ JPA κ°λ μ 리 (Hibernate, Spring Data JPA) (0) | 2023.06.27 |
[Spring] λ©ν° λͺ¨λ νλ‘μ νΈ μ μ© (0) | 2023.06.26 |
[Spring] JPA λΉκ΄μ λ½ μ μ©μΌλ‘ λμμ± λ¬Έμ ν΄κ²°νκΈ° (0) | 2023.06.15 |
[Spring] MySQLκ³Ό mongoDB λ°μ΄ν°λ² μ΄μ€ 2κ° μ°λνκΈ° (0) | 2023.05.10 |
Spring 무νμ€ν¬λ‘€ ꡬν (1) - 컀μ κΈ°λ° νμ΄μ§λ€μ΄μ (0) | 2023.01.01 |