읽기에 앞서
- 프로젝트를 진행하는 도중 무작위 랜덤 조회 기능이 필요하여 어떤 방식으로 사용하는지에 대해 공부한 내용을 정리를 하고자 합니다.
SQL에서의 랜덤 조회 방식
- 이 포스팅에서의 원하는 내용이 아니므로 간단하게 정리하고 넘어간다.
- 현재 DB로 사용하고 있는 MariaDB의 경우, 랜덤으로 필드를 조회하는 쿼리는 아래와 같다.
- 이는 인덱스를 사용하지 못하여 데이터가 방대해질 수록 성능의 저하가 심한 듯 하다.
# 참고 블로그 [https://creds.tistory.com/158]
# 랜덤 데이터 조회
SELECT * FROM [TABLE] ORDER BY RAND();
# 랜덤으로 N개의 데이터 조회
SELECT * FROM [TABLE] ORDER BY RAND() LIMIT 1;
# ORDER BY 2번 사용 시
# DESC를 먼저 수행 후, 중복 데이터 중 하나를 랜덤으로 뽑아서 사용
SELECT * FROM [TABLE] ORDER BY COLUMN1 DESC, RAND() LIMIT 1;
JPA에서는?
그렇다면 방법이 없을까..?
- ORDER BY RAND()의 성능 이슈 문제도 있고, 네이티브 쿼리를 사용하고 싶지 않아 방법을 계속 찾던 중, 유사한 방식의 조회법이 있다는 것을 아래의 게시글을 통해 알게 되었다.
- StackOverFlow 참고
- 위의 게시글을 참고하면 JPA의 페이징 기법을 활용하여 마치 랜덤으로 조회하는 듯한 효과를 낼 수 있다!
페이징을 활용하여 랜덤 조회를 구현해보자
- 위의 게시글은 JPA를 바탕으로 구현돠었으나, 난 프로젝트의 환경에 맞게 QueryDSL을 활용하여 필요한 기능을 구현하고자 한다.
- (페이징에 관한 포스팅은 추후에 작성 예정)
- 구현하고자 하는 기능 : 특정 범위 내의 7개 매장 정보를 랜덤으로 조회
Repository 구현체
/*
특정 범위 상위 7개 조회
*/
@Override
public List<SimpleSearchStoreDTO> searchTop7Random(Polygon<G2D> polygon, Pageable pageable) {
final int dist = 3;
return queryFactory
.select(Projections.constructor(SimpleSearchStoreDTO.class,
store.id, store.name, store.lon, store.lat, store.phoneNumber,
store.status, store.businessTime, store.address, store.ratingTotal, store.file.uploadImageUrl
)).distinct()
.from(store)
.where(stContains(polygon), stDistance(polygon).loe(dist))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
}
/*
특정 범위 개수 조회
*/
@Override
public Long countStoreInPolygon(Polygon<G2D> polygon) {
return queryFactory
.select(store.count())
.from(store)
.where(store.file.uploadImageUrl.isNotNull())
.where(stContains(polygon), stDistance(polygon).loe(3))
.fetchOne();
}
- QueryDSL의 사용법은 별도로 설명하지 않는다.
Service
public List<SimpleSearchStoreDTO> searchTop7Random(double lat, double lon, double dist) {
final int size = 7; // 한 페이지 당 데이터 개수
/*
범위 생성
*/
Polygon<G2D> polygon = getPolygon(lat, lon, dist);
/*
특정 범위 개수 조회
*/
Long count = storeRepository.countStoreInPolygon(polygon);
/*
참고 블로그 - https://mine-it-record.tistory.com/141
Math.random()을 활용하여 난수를 생성
+1을 해주면 0 ~ (count / n)까지의 값이 선택됨
아니면 0 ~ (count / n) - 1까지의 값이 선택됨
*/
int pageNum = count % n != 0
? (int) (Math.random() * (count / size + 1))
: (int) (Math.random() * (count / size));
/*
페이징 정보를 넘겨줌
idx쪽의 n개 데이터를 세팅함
*/
PageRequest pageRequest = PageRequest.of(pageNum, size);
return storeRepository.searchTop7Random(polygon, pageRequest);
}
- 다소 헷갈릴 수 있는 부분이 있는데 바로 idx의 값의 연산이다.
- count가 n으로 나누어 떨어지지 않는 경우, 나머지가 존재한다는 의미이다.
- 나머지 데이터들 또한 하나의 페이지에 담아줘야 하므로 페이지가 하나 더 필요해진다. 따라서 +1을 하여 난수 생성 범위를 1 늘려준다.
어떤 방식인가?
- 전체 동작 방식은 아래와 같다
- 전체 데이터의 개수를 구하고 이를 원하는 데이터 크기만큼 나누어 전체 페이지 개수를 구함.
- Math.random()을 활용한 난수 생성을 이용하여 하나의 페이지 넘버를 구함.
- 이를 PageRequest에 사이즈와 함께 담아서 넘겨줌.
결론
- 흡사 책을 들고 아무런 페이지나 열어보는 경우와 비슷합니다.
- 데이터를 랜덤 조회한다기 보단 추출한 데이터들의 페이지를 랜덤 조회하는 것입니다..
- 현재 제 프로젝트에선 완전한 랜덤 조회 기능은 필요없기에 위의 방식에 만족하고 알맞은 용도로 잘 사용하고 있으나, 100% 완벽하게 랜덤 데이터를 조회하는 방법이 아닙니다.
- 현재로써 그 방법을 원하면 네이티브 쿼리나 jdbcTemplate을 사용해야 하는 것 같습니다.
댓글남기기