QueryDSL의 NumberExpression을 사용하면서 계산 결과가 예상과 달라 문제를 겪은 경험이 있다. 해결 방법은 간단했지만 같은 문제를 여러 번 겪었고, 방심해서 놓치기 쉬운 부분이라 생각이 되어 블로그로 과정을 남겨두려 한다!
⭐️ QueryDSL에서 NumberExpression의 메서드 체이닝으로 수식을 작성하면, 코드상으로는 연산 순서가 명확해 보이지만 체이닝을 SQL로 변환할 때 괄호가 자동으로 생성되지 않는다. ⭐️
자바 코드에서 a.add(b).divide(c)라고 작성하면, (a + b) / c를 의도하고 작성했을 확률이 높다. 체이닝 순서대로 먼저 a + b를 계산한 뒤 c로 나누는 것처럼 읽히기 때문이다. 하지만 QueryDSL은 이 체이닝을 SQL로 변환할 때 괄호 없이 연산자를 나열하기 때문에, SQL의 연산자 우선순위 규칙이 그대로 적용되어 의도와 다른 결과가 나올 수 있다.
에러가 발생한 실제 코드는 외부에 공개할 수 없기 때문에 간단한 예시 코드로 대체한다
// 개발자의 의도: (a + b) / c
NumberExpression<Integer> result = a.add(b).divide(c);
(a + b) / c = (10 + 20) / 5 = 6
-- 괄호 없이 생성됨
a + b / c
-- SQL 연산자 우선순위에 의해 나눗셈이 먼저 수행됨
10 + 20 / 5 = 14
자바 코드의 메서드 호출 순서는 SQL의 연산 순서를 보장하지 않는다.
Expressions.numberTemplate()을 사용하여 괄호를 명시적으로 포함할 수 있다.
// ✅ numberTemplate으로 괄호를 직접 명시
NumberExpression<Integer> result = Expressions.numberTemplate(
Integer.class,
"({0} + {1}) / {2}",
a, b, c
);
| 상황 | 비고 |
|---|---|
단순 체이닝 (a.multiply(b)) | 우선순위 이슈가 없는 경우 결과에 영향 없음 |
| 덧셈/뺄셈 후 곱셈/나눗셈 체이닝 | SQL 우선순위와 충돌 → numberTemplate 필요 |
| 복잡한 수식 | 전체를 numberTemplate으로 작성 권장 |
메서드 체이닝이 코드의 가독성을 높여주지만, QueryDSL은 체이닝 순서를 SQL 괄호로 변환해주지 않는다. 연산 순서가 중요한 수식에서는 numberTemplate을 통해 괄호를 직접 작성해야 한다!