- Criteria
- 프로그램 언어로 빌더 패턴을 차용한 방식으로 질의를 빌드합니다.
- 사용자 정의의 Criteria 를 정의하여 사용할 수 있습니다.
- Criteria 를 실제 SQL 문으로 변환하는데 비용이 드는 단점이 있습니다.
- HQL (Hibernate Query Language)
- hibernate 고유의 질의어입니다.
- SQL 표준 문법과 유사하지만 새로 배워야 합니다.
- 변환에 드는 비용이 한번만 있고, 캐시하여 사용하므로 성능 상 유리합니다.
- SQL String
- 기존 표준 문법이므로 장단점이 따로 없습니다.
- ORM 에서도 그냥 사용해도 된다는 점
이 있다.
SQL 에 익숙한 개발자들은 Criteria 의 가능성 및 효용성에 대해 느껴보지 못하고, 과소평가하는 경우가 많은데, 이 때 제가 아주 간단한 과제를 통해, Criteria 의 장점을 체험하게 합니다.
간단한 과제: 작업 기간에 대해 검색 조건으로 시작시각과 완료시각을 입력 받아 겹치는 기간에 있는 작업 정보를 조회하는 SQL 쿼리를 작성해 보시오.
위의 과제를 주면 99.9% 다 틀립니다. 0.1% 해결한다 하더라도, 너무 복잡해 다른 사람들은 사용하기에도 힘들 것입니다.
위 문제의 첫번째 난관은 기간에 해당하는 시작시각과 완료시각이 nullable 임을 인지하는 것입니다. 두 번째는 검색하고자하는 시작시각과 완료시각도 nullable 일 수 있다는 것입니다. 그럼 이런 조건 하에서 검색을 위한 질의어를 만드는데 경우의 수는 몇이나 될까요?
간단하게 2*2*2*2 = 2^4 이죠?
뭐 중딩 1차원 그래프까지 그려가면서 설명할 필요 없겠죠?
이걸 SQL 쿼리문으로 나타내라구요?
뭐 할 수는 있겠지요... 다만, 차라리 Criteria 라는 좋은 방식이 있으니 그걸 이용해 보라는 겁니다.
결론부터 말하면 다음과 같이 구현하면 됩니다.
OverlapCriteria
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** 지정한 범위 값이 두 속성 값 구간과 겹치는지를 알아보기 위한 질의어 */ | |
public static Criterion getIsOverlapCriterion(String loPropertyName, | |
String hiPropertyName, | |
Object lo, | |
Object hi, | |
boolean includeLo, | |
boolean includeHi) { | |
if (lo == null && hi == null) | |
throw new IllegalArgumentException("lo, hi 모두 null 값이면 질의어를 만들 수 없습니다."); | |
if (log.isDebugEnabled()) | |
log.debug(String.format("build getIsOverlapCriterion... loPropertyName=[%s], hiPropertyName=[%s]" + | |
" lo=[%s], hi=[%s], includeLo=[%s], includeHi=[%s]", | |
loPropertyName, hiPropertyName, lo, hi, includeLo, includeHi)); | |
if (lo != null && hi != null) { | |
return Restrictions | |
.disjunction() | |
.add(getIsInRangeCriterion(loPropertyName, hiPropertyName, lo, includeLo, includeHi)) | |
.add(getIsInRangeCriterion(loPropertyName, hiPropertyName, hi, includeLo, includeHi)) | |
.add(getIsBetweenCriterion(loPropertyName, lo, hi, includeLo, includeHi)) | |
.add(getIsBetweenCriterion(hiPropertyName, lo, hi, includeLo, includeHi)); | |
} | |
if (lo != null) { | |
return Restrictions | |
.disjunction() | |
.add(getIsInRangeCriterion(loPropertyName, hiPropertyName, lo, includeLo, includeHi)) | |
.add((includeLo) ? ge(loPropertyName, lo) | |
: gt(loPropertyName, lo)) | |
.add((includeLo) ? ge(hiPropertyName, lo) | |
: gt(hiPropertyName, lo)); | |
} else { | |
return Restrictions | |
.disjunction() | |
.add(getIsInRangeCriterion(loPropertyName, hiPropertyName, hi, includeLo, includeHi)) | |
.add((includeLo) ? le(loPropertyName, hi) | |
: lt(loPropertyName, hi)) | |
.add((includeLo) ? le(hiPropertyName, hi) | |
: lt(hiPropertyName, hi)); | |
} | |
} |
헐... 근데 내부에 보니 Between 과 InRange 라는 연산자를 사용하네요... 이건 뭐죠?
Between (일반적인 쿼리문에서는 A <= X <= B 이다. 경계값을 포함하는 closed 이다)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static Criterion getIsBetweenCriterion(String propertyName, Object lo, Object hi, | |
boolean includeLo, boolean includeHi) { | |
shouldNotBeEmpty(propertyName, "propertyName"); | |
if (lo == null && hi == null) | |
throw new IllegalArgumentException("상하한 값 모두 null 이면 안됩니다."); | |
if (lo != null && hi != null && includeLo && includeHi) | |
return between(propertyName, lo, hi); | |
// lo, hi 값 중 하나가 없다면 | |
Conjunction result = conjunction(); | |
if (lo != null) | |
result.add((includeLo) ? ge(propertyName, lo) | |
: gt(propertyName, lo)); | |
if (hi != null) | |
result.add((includeHi) ? le(propertyName, hi) | |
: lt(propertyName, hi)); | |
return result; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** 지정한 값이 두 속성 값 사이에 존재하는지 여부 */ | |
public static Criterion getIsInRangeCriterion(final String loPropertyName, | |
final String hiPropertyName, | |
Object value, | |
boolean includeLo, | |
boolean includeHi) { | |
Guard.shouldNotBeNull(value, "value"); | |
Criterion loCriteria = (includeLo) ? le(loPropertyName, value) | |
: lt(loPropertyName, value); | |
Criterion hiCritiera = (includeHi) ? ge(hiPropertyName, value) | |
: gt(hiPropertyName, value); | |
return conjunction() | |
.add(disjunction() | |
.add(isNull(loPropertyName)) | |
.add(loCriteria)) | |
.add(disjunction() | |
.add(isNull(hiPropertyName)) | |
.add(hiCritiera)); | |
} |
자 이제 Criteria 가 SQL 문보다 논리적으로 쉽게 구현되는지 알겠는가?
SQL문을 먼저 배웠다고, 좀 더 익숙하다고 그게 진리는 아닙니다.
java가 되었건, csharp 이 되었건, scala 가 되었건 익숙한 것보다 유용하게 표현할 수 있는 방식이 있다면 그게 진리입니다.
댓글 없음:
댓글 쓰기