사용할 스택은 Spring boot에서 가장 많이 사용되는 JPA의 구현체인 Hibernate & 쿼리 작성을 위해서 Querydsl사용함
현재 적용 할 수 있는 페이징은 Covering Index, NoOffset 방식 존재
package io.web.chewing.repository;
import io.web.chewing.domain.booking.Booking;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface BookingRepository extends JpaRepository<Booking, Long> {
Optional<Booking> findByIdAndMember_Nickname(Long id, String nickname);
/*List<BookingDTO> findBookingListPaging(String store, String storeName);*/
Page<Booking> findAllByMember_Nickname(Pageable pageable, String nickname);
}
public List<BookingPaginationDto> paginationCoveringIndexByEntityToDto(String name, int pageNo, int pageSize) {
// 1) 커버링 인덱스로 대상 조회
List<Long> ids = queryFactory.select(booking.id)
.from(booking)
.where(booking.member.nickname.eq(name))
.orderBy(booking.id.desc())
.limit(pageSize)
.offset((long) pageNo * pageSize)
.fetch();
// 1-1) 대상이 없을 경우 추가 쿼리 수행 할 필요 없이 바로 반환
if (CollectionUtils.isEmpty(ids)) {
return new ArrayList<>();
}
// 2)
return queryFactory.select(Projections.fields(BookingPaginationDto.class,
booking.id.as("id"),
booking.real_name,
booking.people,
booking.date,
booking.time,
booking.member.nickname.as("member_nickname"),
booking.store.name.as("store_name"),
booking.bookingState))
.from(booking)
.innerJoin(booking.store, store)
.innerJoin(booking.member, member)
.where(booking.id.in(ids))
.orderBy(booking.id.desc())
.fetch();
}
@Test
public void CoveringIndexByEntityToDto() {
// given
String searchName = "testnick10k";
//when
List<BookingPaginationDto> bookings = bookingCustomRepository.paginationCoveringIndexByEntityToDto(searchName, 50000, 10);
//then
assertThat(bookings).hasSize(10);
}
@Test
public void NoOffsetFirstPage() {
//given
String searchNick = "testnick10k";
//when
List<BookingPaginationDto> bookings = bookingCustomRepository.paginationNoOffset(null, searchNick, 10);
//then
assertThat(bookings).hasSize(10);
}
@Test
public void NoOffsetFirstPage() {
//given
String searchNick = "testnick10k";
//when
List<BookingPaginationDto> bookings = bookingCustomRepository.paginationNoOffset(null, searchNick, 10);
//then
assertThat(bookings).hasSize(10);
}
@Test
public void NoOffsetSecondPage() {
//given
String searchName = "testnick10k";
//when
List<BookingPaginationDto> bookings = bookingCustomRepository.paginationNoOffset(9970L, searchName, 10);
//then
assertThat(bookings).hasSize(10);
}
public List<BookingPaginationDto> paginationLegacy(String searchName, int pageNo, int PageSize) {
return queryFactory.select(Projections.fields(BookingPaginationDto.class,
booking.id.as("id"),
booking.real_name,
booking.people,
booking.member.nickname.as("member_nickname"),
booking.store.name.as("store_name"),
booking.date,
booking.time,
booking.member.nickname,
booking.bookingState))
.from(booking)
.where(
booking.member.nickname.eq(searchName)
)
.orderBy(booking.id.desc())
.limit(PageSize)
.offset((long) pageNo * PageSize)
.fetch();
}
@Test
public void legacy() throws Exception {
//given
String searchName = "testnick10k";
//when
List<BookingPaginationDto> bookings = bookingCustomRepository.paginationLegacy(searchName, 50000, 10);
//then
assertThat(bookings).hasSize(10);
}
데이터 100만건 기준으로 속도를 junit 테스트 실행 결과 다음과 같은 차이가 나왔다.
첫 배포 때 보다 테스트 환경에서 1초 정도의 의미있는 차이를 냈다
하지만 첫 배포시엔 JpaRepository 인터페이스에 기본 crud 메서드로 쿼리 효율같은걸 생각하지 않고 사용했기 때문에 이런 큰 차이가 났다고 생각한다.
이걸 개선하면서 내 단점인 너무 미래의 상황까지 고려해서 생각한다는 점을 다시 상기했다.
따라서 너무 먼 미래를 보고 설계하기 보단 가까운 미래까지만 고려해서 설계를 해야 한다는 경험을 얻은거 같다.
그리고 내용 정리를 할때 좀 보기 쉽게 할 수 있도록 다른 분들의 글을 더 많이 참고하고 읽어봐야겠다
출처
https://jojoldu.tistory.com/528
https://www.youtube.com/watch?v=zMAX7g6rO_Y
create table persistent_logins
(
username varchar(64) not null ,
series varchar(64) primary key ,
token varchar(64) not null ,
last_used timestamp not null
);