상황 설명
현재 프로젝트에는 환경 퀴즈를 푸는 서비스가 있고, 유저가 제출한 퀴즈를 복습하는 기능이 있다.
특이한 점은 복습 퀴즈들을 랜덤으로 보여줘야 한다는 것.
즉 문제들의 정렬 방식을 랜덤으로 두어서 유저가 요청할 때마다 다른 퀴즈를 봐야한다.
발단
문제는 랜덤 정렬을 구현하면서 발생했다.
@Override
public Slice<Quiz> findRecentQuizzes(Long userId, int limit) {
JPAQuery<Quiz> query = queryFactory.selectFrom(quiz)
.where(...)
.orderBy(NumberExpression.random().asc()) // 이 부분
// ...
}
발생한 에러:
org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
Caused by: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near
SQL 문법에 오류가 있다고 하지만 더 근본적인 오류였다.
JPA Hibernate에서는 MySQL의 RAND 함수를 지원하지 않았던 것.
해결
결국 직접 SQL 함수 (RAND)를 입력해줘야 했다.
다행히 이렇게 Template이란 클래스 관련 메서드를 쓰면 직접 SQL 호환되는 함수를 만들 수 있는 모양이다.
(이 정도 코드는... 만들어줬으면 좋겠다. 다음 QueryDSL 버전에 기여할 수 있으려나?)
public class CustomOrderSpecifierUtils {
public static OrderSpecifier<Double> makeRandom() { // Mysql RAND 함수를 지원하지 않아서 만든 함수
return Expressions.numberTemplate(Double.class, "function('rand')").asc();
}
}
@Override
public Slice<Quiz> findRecentQuizzes(Long userId, int limit) {
JPAQuery<Quiz> query = queryFactory.selectFrom(quiz)
.where(...)
.orderBy(CustomOrderSpecifierUtils.makeRandom()) // 이 부분
// ...
}
정상적으로 RAND() 가 호출되어 랜덤 조회 로직이 구현됐다.
만약 이렇게 커스텀 JPA 함수를 만들어야 할 경우가 많다면, 별도의 Template을 만들어서 Java스럽게 사용하는 방법이 좋을 듯하다.
Expressions는 어디서 온거지?
QueryDSL 공식 문서에 따르면 Expressions 클래스는 동적인 표현식 생성을 위한 정적 팩토리 클래스라고 한다. (번역)
솔직히 무슨 뜻인지는 모르겠다.
확실한건, 내 경험상 QueryDSL에서 어려운 쿼리문을 짤 때마다 빛처럼 등장하는 클래스였다. SQL이 생각나려고 할 때 거의 항상 Expressions를 사용했던 것 같다.
궁금해서 소스코드를 뒤져봤다.
Expression 생성을 위한 팩토리 클래스라고 한다.
JPAExpressions, StringExpressions 등등 이름이 비슷한 클래스들이 있는데, 공통된 부모 클래스를 상속하고 있지는 않다.
그럼에도 공통적인 것은 Expression을 만들기 위한 클래스라는 것.
간단히 얘기하면... 쌩 쿼리문 혹은 JPQL을 대체하기 위해 만든 클래스라고 생각한다.
그리고 Expression은 SQL 표현인 것.
이 정도까지만 알아보고, 추후에 깊게 커스터마이징을 해야할 때 더 알아보려고 한다.
참고:
'Back-end > Spring Boot' 카테고리의 다른 글
[Spring JPA] 엔티티 컬렉션 필드를 초기화해야 하는 이유 (0) | 2023.09.18 |
---|---|
[Spring Boot] 커서 페이징(no offset)에서 Page 대신 Slice 사용하기 (1) | 2023.09.11 |
[Spring Boot] 개발 환경 분리와 ddl-auto 재앙 방지 + @Profile (0) | 2023.09.03 |
[Spring Boot] @Valid 유효성 검사 (jakarta validation) (0) | 2023.08.27 |
[Spring Boot] 연관관계 생성 메서드 삽질 (0) | 2023.08.23 |