확장 인터페이스
querydsl-ktx는 8개의 확장 인터페이스를 제공하며, 각각 특정 QueryDSL 표현식 타입에 스코프가 지정됩니다. 모든 함수는 null-safe합니다: null 인자는 조건을 건너뛰게 합니다.
개요
| 인터페이스 | 표현식 타입 | 주요 함수 |
|---|---|---|
| BooleanExpressionExtensions | BooleanExpression | and, or, andAnyOf, orAllOf, eq, nullif, coalesce |
| SimpleExpressionExtensions | SimpleExpression<T> | eq, ne, in, notIn |
| ComparableExpressionExtensions | ComparableExpression<T> | gt, goe, lt, loe, between, nullif, coalesce, rangeTo |
| NumberExpressionExtensions | NumberExpression<T> | gt, goe, lt, loe, between, nullif, coalesce, rangeTo |
| StringExpressionExtensions | StringExpression | contains, startsWith, endsWith, like, matches, nullif, coalesce |
| TemporalExpressionExtensions | TemporalExpression<T> | after, before |
| CollectionExpressionExtensions | CollectionExpressionBase<T, E> | contains |
| SubQueryExtensions | EntityPath<T> | exists, notExists |
BooleanExpressionExtensions
Null-safe AND/OR 결합자. 동적 WHERE 절 구성의 기반입니다.
함수
| 함수 | 시그니처 | SQL |
|---|---|---|
and | BooleanExpression?.and(BooleanExpression?) | a AND b |
or | BooleanExpression?.or(BooleanExpression?) | a OR b |
andAnyOf | BooleanExpression?.andAnyOf(List<BooleanExpression?>) | a AND (b OR c OR ...) |
orAllOf | BooleanExpression?.orAllOf(List<BooleanExpression?>) | a OR (b AND c AND ...) |
eq | BooleanExpression?.eq(Boolean?) | active = true |
nullif | BooleanExpression?.nullif(Boolean?) | NULLIF(active, true) |
coalesce | BooleanExpression?.coalesce(Boolean?) | COALESCE(active, false) |
예제
// AND: null 쪽은 무시됨
val predicate = (entity.active eq true) and (entity.name eq name)
// OR 그룹
val rolePredicate = (entity.role eq "ADMIN") or (entity.role eq "MANAGER")
// AND와 OR 서브그룹
val complex = (entity.active eq true) andAnyOf listOf(
entity.role eq role,
entity.department eq department,
)-- AND (name = 'John')
active = true AND name = 'John'
-- AND (name = null) -> 왼쪽만 남음
active = true
-- OR 그룹
role = 'ADMIN' OR role = 'MANAGER'
-- AND와 OR 서브그룹
active = true AND (role = ? OR department = ?)vararg 오버로드 v1.2.0+
andAnyOf와 orAllOf는 vararg BooleanExpression?도 받습니다. Kotlin은 infix 함수에 vararg를 허용하지 않으므로 점 표기법으로 호출합니다: predicate.andAnyOf(p1, p2, p3).
SimpleExpressionExtensions
모든 표현식 타입에 대한 동등성 및 멤버십 연산자.
함수
| 함수 | 시그니처 | SQL |
|---|---|---|
eq | SimpleExpression<T>?.eq(T?) | status = ? |
eq | SimpleExpression<T>?.eq(Expression<in T>?) | status = default_status |
eq | SimpleExpression<T>?.eq(SubQueryExpression<T>?) | price = (SELECT ...) |
ne | SimpleExpression<T>?.ne(T?) | status != ? |
ne | SimpleExpression<T>?.ne(Expression<in T>?) | status != default_status |
in | SimpleExpression<T>?.in(Collection<T>?) | status IN (?, ?) |
in | SimpleExpression<T>?.in(SubQueryExpression<T>?) | id IN (SELECT ...) |
notIn | SimpleExpression<T>?.notIn(Collection<T>?) | status NOT IN (?, ?) |
notIn | SimpleExpression<T>?.notIn(SubQueryExpression<T>?) | id NOT IN (SELECT ...) |
inChunked | SimpleExpression<T>?.inChunked(Collection<T>?) | col IN (?) OR col IN (?) |
eqAll | SimpleExpression<T>?.eqAll(CollectionExpression<*, in T>?) / eqAll(SubQueryExpression<T>?) | col = ALL (...) |
eqAny | SimpleExpression<T>?.eqAny(CollectionExpression<*, in T>?) / eqAny(SubQueryExpression<T>?) | col = ANY (...) |
neAll | SimpleExpression<T>?.neAll(CollectionExpression<*, in T>?) | col <> ALL (...) |
neAny | SimpleExpression<T>?.neAny(CollectionExpression<*, in T>?) | col <> ANY (...) |
예제
// 동등성
entity.status eq "ACTIVE" // status = 'ACTIVE'
entity.status eq null // null (건너뛰기)
// 부등식
entity.status ne "DELETED" // status != 'DELETED'
// IN / NOT IN
entity.status `in` listOf("A", "B") // status IN ('A', 'B')
entity.status notIn listOf("C") // status NOT IN ('C')
entity.status `in` null // null (건너뛰기)
// 대량 IN절 자동 분할 (기본 1000개씩)
entity.id inChunked largeIdList // id IN (1..1000) OR id IN (1001..2000)
entity.id.inChunked(ids, 500) // 커스텀 청크 사이즈status = 'ACTIVE'
status != 'DELETED'
status IN ('A', 'B')
status NOT IN ('C')Oracle의 IN 절 제한 대응
Oracle은 단일 IN 절에 1000개 항목 제한이 있습니다. inChunked는 대량 컬렉션을 자동으로 여러 IN 절로 분할하고 OR로 연결합니다. 기본 청크 사이즈는 1000이며, 커스텀 사이즈 지정이 가능합니다.
서브쿼리 비교 v1.2.0+
eq, in, notIn은 SubQueryExpression<T>? 오버로드도 제공합니다. JPAExpressions로 만든 서브쿼리와 null-safe하게 비교할 수 있습니다.
import com.querydsl.jpa.JPAExpressions
// price가 모든 상품 중 최대 가격과 같은 상품
val maxPriceSubQuery = JPAExpressions.select(product.price.max()).from(product)
selectFrom(product).where(product.price eq maxPriceSubQuery).fetch()
// category가 서브쿼리 결과에 포함된 상품
val cheapCategoriesSubQuery = JPAExpressions
.selectDistinct(product.category)
.from(product)
.where(product.price.lt(10000))
selectFrom(product).where(product.category `in` cheapCategoriesSubQuery).fetch()서브쿼리 인자가 null이면 조건 자체가 건너뛰어지므로 (null 반환), 선택적 서브쿼리 필터를 if 분기 없이 그대로 끼울 수 있습니다.
ComparableExpressionExtensions
Comparable 타입(날짜, 문자열, enum 등)에 대한 비교 및 범위 연산자.
함수
| 함수 | 시그니처 | SQL |
|---|---|---|
gt | ComparableExpression<T>?.gt(T?) | col > ? |
goe | ComparableExpression<T>?.goe(T?) | col >= ? |
lt | ComparableExpression<T>?.lt(T?) | col < ? |
loe | ComparableExpression<T>?.loe(T?) | col <= ? |
between | ComparableExpression<T>?.between(Pair<T?, T?>) | col BETWEEN ? AND ? |
between | ComparableExpression<T>?.between(ClosedRange<T>) | col BETWEEN ? AND ? |
notBetween | ComparableExpression<T>?.notBetween(Pair<T?, T?>) | col NOT BETWEEN ? AND ? |
between (역방향) | T?.between(Pair<ComparableExpression<T>?, ComparableExpression<T>?>) | lower <= ? AND upper >= ? |
nullif | ComparableExpression<T>?.nullif(T?) | NULLIF(col, ?) |
coalesce | ComparableExpression<T>?.coalesce(T?) | COALESCE(col, ?) |
rangeTo | ComparableExpression<T>..ComparableExpression<T> | (between용 Pair 생성) |
gtAll | ComparableExpression<T>?.gtAll(CollectionExpression<*, in T>?) / gtAll(SubQueryExpression<T>?) | col > ALL (...) |
gtAny | ComparableExpression<T>?.gtAny(CollectionExpression<*, in T>?) / gtAny(SubQueryExpression<T>?) | col > ANY (...) |
goeAll | ComparableExpression<T>?.goeAll(CollectionExpression<*, in T>?) / goeAll(SubQueryExpression<T>?) | col >= ALL (...) |
goeAny | ComparableExpression<T>?.goeAny(CollectionExpression<*, in T>?) / goeAny(SubQueryExpression<T>?) | col >= ANY (...) |
ltAll | ComparableExpression<T>?.ltAll(CollectionExpression<*, in T>?) / ltAll(SubQueryExpression<T>?) | col < ALL (...) |
ltAny | ComparableExpression<T>?.ltAny(CollectionExpression<*, in T>?) / ltAny(SubQueryExpression<T>?) | col < ANY (...) |
loeAll | ComparableExpression<T>?.loeAll(CollectionExpression<*, in T>?) / loeAll(SubQueryExpression<T>?) | col <= ALL (...) |
loeAny | ComparableExpression<T>?.loeAny(CollectionExpression<*, in T>?) / loeAny(SubQueryExpression<T>?) | col <= ANY (...) |
모든 비교 함수에는 다른 컬럼과 비교하기 위한 Expression<T> 오버로드도 있습니다.
예제
// 단순 비교
entity.date gt startDate // date > ?
entity.date goe startDate // date >= ?
entity.date lt endDate // date < ?
entity.date loe endDate // date <= ?
// Pair를 사용한 BETWEEN (부분 범위 지원)
entity.date between (from to to) // BETWEEN ? AND ?
entity.date between (from to null) // date >= ?
entity.date between (null to to) // date <= ?
entity.date between (null to null) // null (건너뛰기)
// ClosedRange를 사용한 BETWEEN
entity.age between (20..60) // BETWEEN 20 AND 60
// 역방향 BETWEEN: 값이 왼쪽, 표현식 경계가 오른쪽
now between (sale.startAt to sale.endAt)
// -> start_at <= now AND end_at >= now
// rangeTo 연산자 (..): Pair 생성 syntactic sugar
entity.date between (entity.startDate..entity.endDate)
// 동일: entity.date between (entity.startDate to entity.endDate)-- 전체 범위
created_at BETWEEN '2024-01-01' AND '2024-12-31'
-- 단측 (from만)
created_at >= '2024-01-01'
-- 단측 (to만)
created_at <= '2024-12-31'
-- ClosedRange
age BETWEEN 20 AND 60선택적 날짜 범위를 위한 Pair 기반 between
Pair 오버로드는 날짜 범위 필터에서 가장 강력한 기능입니다. 하나의 표현식으로 네 가지 조합(양쪽 값, from만, to만, 둘 다 없음)을 모두 처리합니다. 그렇지 않으면 4분기 if/else가 필요합니다.
역방향 Between: 실전 활용 사례
역방향 between은 값이 왼쪽, 컬럼 경계가 오른쪽에 옵니다. 실무에서 의외로 자주 필요한 패턴입니다:
할인 기간 검증: 쿠폰이 지금 유효한지 확인:
val now = LocalDateTime.now()
selectFrom(coupon)
.where(now between (coupon.validFrom to coupon.validUntil))
.fetch()
// SQL: valid_from <= '2025-04-10T12:00' AND valid_until >= '2025-04-10T12:00'주문 금액이 할인 구간에 해당하는지 확인:
val orderAmount = 50000
selectFrom(discountTier)
.where(orderAmount between (discountTier.minAmount to discountTier.maxAmount))
.fetch()
// SQL: min_amount <= 50000 AND max_amount >= 50000이벤트 기간 내 주문 조회:
// 주문일이 이벤트 기간 안에 있는지
selectFrom(order)
.where(order.orderedAt between (event.startAt to event.endAt))
.fetch()역방향 between도 null-safe입니다: 값이 null이면 전체 표현식이 null을 반환합니다(건너뜀).
인프런에서 배운 BooleanExpression 패턴과의 비교
김영한 강의에서 배운 BooleanExpression 반환 메서드 패턴을 기억하시나요?
// 강의 스타일: 필드마다 메서드 하나씩
fun isWithinPeriod(now: LocalDateTime?): BooleanExpression? {
if (now == null) return null
return event.startAt.loe(now).and(event.endAt.goe(now))
}querydsl-ktx에서는 이 패턴이 별도 메서드 없이 인라인으로 표현됩니다:
// querydsl-ktx: 메서드 추출 불필요
now between (event.startAt to event.endAt)NumberExpressionExtensions
ComparableExpressionExtensions와 동일한 API이지만 NumberExpression용입니다.
산술 연산자 v1.2.0+
add, subtract, multiply, divide, mod도 동일한 null-safety 계약으로 사용할 수 있습니다. 어느 한쪽이 null이면 null을 반환하여 산술 표현식 자체가 건너뜁니다. 각 연산자는 값 오버로드와 Expression<T> 오버로드를 모두 제공합니다.
entity.price add 1000 // price + 1000
entity.price multiply taxRate // price * tax_rate (다른 컬럼)
entity.total divide quantity // total / quantityKotlin 연산자 오버로드 v1.2.0+
Kotlin 산술 연산자 (+, -, *, /, %, 단항 -)도 표현식 빌딩 (정렬, 프로젝션, computed column) 용도로 제공됩니다. 위의 null-skip infix 형태와 달리 non-null 계약입니다 (양쪽 모두 필수).
entity.price + 1000 // price + 1000
entity.price * taxRate // price * tax_rate (다른 컬럼)
-entity.price // -price
(entity.price + entity.tax) * 2 // 괄호로 결합 가능양쪽이 모두 보장될 때 + / - / * / / / %를 사용하고, 어느 한쪽이 null일 수 있어 표현식 자체를 건너뛰고 싶다면 add / subtract 등을 사용하세요.
별도 인터페이스인 이유
QueryDSL의 타입 계층에서 NumberExpression은 ComparableExpression을 확장하지 않습니다. 이는 QueryDSL의 설계 결정입니다. NumberExpression은 ComparableExpressionBase를 상속하고, ComparableExpression은 별도의 분기입니다. 따라서 querydsl-ktx는 NumberExpression에 특화된 병렬 연산자 세트를 제공합니다.
함수
| 함수 | 시그니처 | SQL |
|---|---|---|
gt | NumberExpression<T>?.gt(T?) | col > ? |
goe | NumberExpression<T>?.goe(T?) | col >= ? |
lt | NumberExpression<T>?.lt(T?) | col < ? |
loe | NumberExpression<T>?.loe(T?) | col <= ? |
between | NumberExpression<T>?.between(Pair<T?, T?>) | col BETWEEN ? AND ? |
between | NumberExpression<T>?.between(ClosedRange<T>) | col BETWEEN ? AND ? |
notBetween | NumberExpression<T>?.notBetween(Pair<T?, T?>) | col NOT BETWEEN ? AND ? |
between (역방향) | T?.between(Pair<NumberExpression<T>?, NumberExpression<T>?>) | lower <= ? AND upper >= ? |
nullif | NumberExpression<T>?.nullif(T?) | NULLIF(col, ?) |
coalesce | NumberExpression<T>?.coalesce(T?) | COALESCE(col, ?) |
rangeTo | NumberExpression<T>..NumberExpression<T> | (between용 Pair 생성) |
add | NumberExpression<T>?.add(T?) / add(Expression<T>?) | col + ? |
subtract | NumberExpression<T>?.subtract(T?) / subtract(Expression<T>?) | col - ? |
multiply | NumberExpression<T>?.multiply(T?) / multiply(Expression<T>?) | col * ? |
divide | NumberExpression<T>?.divide(T?) / divide(Expression<T>?) | col / ? |
mod | NumberExpression<T>?.mod(T?) / mod(Expression<T>?) | col % ? |
+ / - / * / / / % | operator NumberExpression<T>.plus/minus/times/div/rem(T | Expression<T>) | col + ? (non-null 계약) |
단항 - | operator NumberExpression<T>.unaryMinus() | -col |
gtAll | NumberExpression<T>?.gtAll(CollectionExpression<*, in T>?) / gtAll(SubQueryExpression<T>?) | col > ALL (...) |
gtAny | NumberExpression<T>?.gtAny(CollectionExpression<*, in T>?) / gtAny(SubQueryExpression<T>?) | col > ANY (...) |
goeAll | NumberExpression<T>?.goeAll(CollectionExpression<*, in T>?) | col >= ALL (...) |
goeAny | NumberExpression<T>?.goeAny(CollectionExpression<*, in T>?) | col >= ANY (...) |
ltAll | NumberExpression<T>?.ltAll(CollectionExpression<*, in T>?) | col < ALL (...) |
ltAny | NumberExpression<T>?.ltAny(CollectionExpression<*, in T>?) | col < ANY (...) |
loeAll | NumberExpression<T>?.loeAll(CollectionExpression<*, in T>?) | col <= ALL (...) |
loeAny | NumberExpression<T>?.loeAny(CollectionExpression<*, in T>?) | col <= ANY (...) |
예제
entity.price gt 10000
entity.price between (minPrice to maxPrice)
entity.score between (0..100)
entity.quantity loe maxQuantity
// 역방향 BETWEEN: 값이 왼쪽, 표현식 경계가 오른쪽
orderAmount between (tier.minAmount to tier.maxAmount)
// -> min_amount <= orderAmount AND max_amount >= orderAmount
// rangeTo 연산자 (..): Pair 생성 syntactic sugar
orderAmount between (tier.minAmount..tier.maxAmount)price > 10000
price BETWEEN ? AND ?
score BETWEEN 0 AND 100
quantity <= ?ALL/Any 비교 v1.2.0+
QueryDSL은 컬렉션 또는 서브쿼리와 비교하기 위한 <op>All / <op>Any 멤버를 제공합니다. querydsl-ktx는 동일한 null-skip 시맨틱으로 래핑합니다.
import com.querydsl.jpa.JPAExpressions
// Stationery 카테고리의 모든 price보다 큰 상품
val stationeryPrices = JPAExpressions.select(product.price).from(product)
.where(product.category.eq("Stationery"))
selectFrom(product).where(product.price gtAll stationeryPrices).fetch()
// cheap 카테고리의 어떤 price와도 일치하는 상품
val cheapPrices = JPAExpressions.select(product.price).from(product)
.where(product.price.lt(10000))
selectFrom(product).where(product.price eqAny cheapPrices).fetch()QueryDSL 5.1.0의 멤버 커버리지는 비대칭이며 래퍼는 이를 그대로 미러링합니다.
| 함수 | CollectionExpression | SubQueryExpression |
|---|---|---|
eqAll / eqAny (Simple, Comparable, Number) | ✓ | ✓ |
neAll / neAny (Simple) | ✓ | ✗ (멤버 부재) |
gtAll / gtAny (Comparable, Number) | ✓ | ✓ |
goeAll / ltAll / loeAll 및 *Any (Comparable) | ✓ | ✓ |
goeAll / ltAll / loeAll 및 *Any (Number) | ✓ | ✗ (NumberExpression 멤버 부재) |
Number 컬럼에 누락된 SubQuery 변형이 필요하면, 기반 타입이 Comparable<T>일 경우 ComparableExpression view로 캐스팅하여 사용합니다.
StringExpressionExtensions
패턴 매칭 및 문자열 비교 연산자.
함수
| 함수 | 시그니처 | SQL |
|---|---|---|
contains | StringExpression?.contains(String?) | LIKE '%val%' |
containsIgnoreCase | StringExpression?.containsIgnoreCase(String?) | LOWER(col) LIKE LOWER('%val%') |
startsWith | StringExpression?.startsWith(String?) | LIKE 'val%' |
startsWithIgnoreCase | StringExpression?.startsWithIgnoreCase(String?) | LOWER(col) LIKE LOWER('val%') |
endsWith | StringExpression?.endsWith(String?) | LIKE '%val' |
endsWithIgnoreCase | StringExpression?.endsWithIgnoreCase(String?) | LOWER(col) LIKE LOWER('%val') |
equalsIgnoreCase | StringExpression?.equalsIgnoreCase(String?) | LOWER(col) = LOWER(?) |
notEqualsIgnoreCase | StringExpression?.notEqualsIgnoreCase(String?) | LOWER(col) != LOWER(?) |
like | StringExpression?.like(String?) | LIKE ? |
likeIgnoreCase | StringExpression?.likeIgnoreCase(String?) | LOWER(col) LIKE LOWER(?) |
notLike | StringExpression?.notLike(String?) | NOT LIKE ? |
matches | StringExpression?.matches(String?) | REGEXP ? |
contains | StringExpression?.contains(Expression<String>?) | LIKE '%' || other_col || '%' |
startsWith | StringExpression?.startsWith(Expression<String>?) | LIKE other_col || '%' |
endsWith | StringExpression?.endsWith(Expression<String>?) | LIKE '%' || other_col |
nullif | StringExpression?.nullif(Expression<String>?) | NULLIF(col, other_col) |
nullif | StringExpression?.nullif(String?) | NULLIF(col, ?) |
coalesce | StringExpression?.coalesce(Expression<String>?) | COALESCE(col, other_col) |
coalesce | StringExpression?.coalesce(String?) | COALESCE(col, ?) |
contains, startsWith, endsWith에는 Expression<String> 오버로드도 있습니다.
예제
// 부분 문자열 검색
entity.name contains keyword // name LIKE '%keyword%'
entity.name containsIgnoreCase keyword // 대소문자 무시
// 접두사 / 접미사
entity.name startsWith prefix // name LIKE 'prefix%'
entity.name endsWith suffix // name LIKE '%suffix'
// 패턴과 정규식
entity.name like "J%n" // name LIKE 'J%n'
entity.email matches "^[a-z]+@.*" // name REGEXP '^[a-z]+@.*'
// 대소문자 무시 동등성
entity.email equalsIgnoreCase email // LOWER(email) = LOWER(?)name LIKE '%keyword%'
LOWER(name) LIKE LOWER('%keyword%')
name LIKE 'prefix%'
name LIKE '%suffix'
name LIKE 'J%n'
email REGEXP '^[a-z]+@.*'
LOWER(email) = LOWER(?)리터럴 % / _ 매칭을 위한 escape 문자 v1.2.0+
패턴에 와일드카드가 아닌 리터럴 %나 _가 들어갈 때는 like, notLike, likeIgnoreCase 다음에 escape '\'를 체이닝합니다. SQL의 LIKE pattern ESCAPE 'char' 구문을 그대로 미러링합니다.
name like "10\\%off" escape '\\' // LIKE '10\%off' ESCAPE '\'
name notLike "10\\%off" escape '\\' // NOT LIKE '10\%off' ESCAPE '\'
name likeIgnoreCase "10\\%OFF" escape '\\' // LIKE '10\%OFF' ESCAPE '\' (대소문자 무시)escape는 BooleanExpression?.escape(Char) 형태입니다. like 계열 결과가 아닌 표현식에 호출하면 ExpressionException을 던집니다.
TemporalExpressionExtensions
날짜/시간 표현식을 위한 시간 비교 연산자.
함수
| 함수 | 시그니처 | SQL |
|---|---|---|
after | TemporalExpression<T>?.after(T?) | col > ? |
after | TemporalExpression<T>?.after(Expression<T>?) | col > other_col |
before | TemporalExpression<T>?.before(T?) | col < ? |
before | TemporalExpression<T>?.before(Expression<T>?) | col < other_col |
예제
entity.createdAt after startDate // created_at > ?
entity.createdAt before endDate // created_at < ?
// 컬럼 비교
entity.endDate after entity.startDate // end_date > start_date
// Null-safe
entity.createdAt after null // null (건너뛰기)created_at > '2024-01-01'
created_at < '2024-12-31'
end_date > start_dateafter/before vs gt/goe/lt/loe
TemporalExpression(날짜, 타임스탬프)에는 after/before를 사용하고, ComparableExpression이나 NumberExpression에는 gt/goe/lt/loe를 사용하세요. 생성되는 SQL은 같지만 서로 다른 QueryDSL 타입에 정의되어 있습니다.
CollectionExpressionExtensions
매핑된 컬렉션 필드(예: @ElementCollection, @ManyToMany)에 대한 멤버십 검사.
함수
| 함수 | 시그니처 | SQL |
|---|---|---|
contains | CollectionExpressionBase<T, E>?.contains(E?) | ? IN (col) |
contains | CollectionExpressionBase<T, E>?.contains(Expression<E>?) | other_col IN (col) |
예제
entity.roles contains "ADMIN" // 'ADMIN' IN (roles)
entity.tags contains tag // ? IN (tags), null-safe'ADMIN' IN (roles)SubQueryExtensions
EXISTS / NOT EXISTS 서브쿼리 단축 빌더.
함수
| 함수 | 시그니처 | SQL |
|---|---|---|
exists | EntityPath<T>.exists(vararg Predicate?) | EXISTS (SELECT 1 FROM ...) |
notExists | EntityPath<T>.notExists(vararg Predicate?) | NOT EXISTS (SELECT 1 FROM ...) |
예시
// Before: 장황한 서브쿼리
JPAExpressions.selectOne()
.from(orderItem)
.where(orderItem.orderId.eq(order.id))
.exists()
// After: 간결
orderItem.exists(orderItem.orderId eq order.id)
// NOT EXISTS
orderItem.notExists(orderItem.orderId eq order.id)EXISTS (SELECT 1 FROM order_item WHERE order_item.order_id = order.id)
NOT EXISTS (SELECT 1 FROM order_item WHERE order_item.order_id = order.id)Null 처리
vararg의 null predicate는 자동으로 필터링됩니다.
선택적 구현
8개의 인터페이스를 모두 사용할 필요는 없습니다. 필요한 것만 선택하세요:
옵션 1: QuerydslRepository (전체 포함)
@Repository
class MyRepository : QuerydslRepository<MyEntity>() {
// 8개 인터페이스 모두 사용 가능
}옵션 2: QuerydslSupport + 선택한 인터페이스
@Repository
class MyRepository : QuerydslSupport<MyEntity>(),
SimpleExpressionExtensions,
StringExpressionExtensions {
override val domainClass = MyEntity::class.java
// eq, ne, in, notIn, contains, startsWith 등만 사용 가능
// number/comparable/temporal/collection 확장은 없음
}옵션 3: 아무 클래스에서 구현
class PredicateBuilder : BooleanExpressionExtensions, SimpleExpressionExtensions {
// 리포지토리뿐만 아니라 어디서든 확장 함수 사용 가능
}QuerydslRepository vs QuerydslSupport
QuerydslRepository<T> | QuerydslSupport<T> | |
|---|---|---|
| 확장 인터페이스 | 8개 전체 포함 | 없음 (필요한 것만 추가) |
domainClass | 제네릭을 통해 자동 추론 | 수동 오버라이드 필요 |
| 사용 시기 | 전체 기능이 필요할 때 | 최소한의 API 표면만 원할 때 |