Predicates

Kotlin JDSL์€ JPQL์˜ conditional expression์„ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด Predicate ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

Logical operators

๋…ผ๋ฆฌ ์—ฐ์‚ฐ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด, ๋‹ค์Œ ํ•จ์ˆ˜๋“ค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • AND (and)

  • OR (or)

  • NOT (not)

๋งŒ์•ฝ and() ์™€ or()๋กœ ๋„˜์–ด์˜จ Predicate๊ฐ€ ๋ชจ๋‘ null ์ด๊ฑฐ๋‚˜ ๋น„์–ด ์žˆ์œผ๋ฉด, and()์˜ ๊ฒฝ์šฐ์—๋Š” 1 = 1๋กœ or()์˜ ๊ฒฝ์šฐ์—๋Š” 0 = 1๋กœ ํ•ด์„๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋–„๋ฌธ์— ๋‹ค์ด๋‚˜๋ฏน ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“ค ๋•Œ ์กฐ์‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

path(Employee::name).eq("Employee01").and(path(Employee::nickname).eq("E01"))
and(path(Employee::name).eq("Employee01"), path(Employee::nickname).eq("E01"))

path(Employee::name).eq("Employee01").or(path(Employee::nickname).eq("E01"))
or(path(Employee::name).eq("Employee01"), path(Employee::nickname).eq("E01"))

not(path(Employee::name).eq("Employee01"))

Parentheses

ํ™•์žฅ ํ•จ์ˆ˜๊ฐ€ ์•„๋‹Œ ์ผ๋ฐ˜ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋…ผ๋ฆฌ ์—ฐ์‚ฐ์ž์— ์—ฐ์‚ฐ ์ˆœ์„œ๋ฅผ ์œ„ํ•œ ์†Œ๊ด„ํ˜ธ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ™•์žฅ ํ•จ์ˆ˜์˜ ๊ฒฝ์šฐ ์—ฐ์‚ฐ ์ˆœ์„œ๊ฐ€ ๋ชจํ˜ธํ•ด์„œ ์†Œ๊ด„ํ˜ธ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

// ์ผ๋ฐ˜ ํ•จ์ˆ˜: (Employee.name = 'Employee01' AND Employee.nickname = 'E01') or (Employee.name = 'Employee02' AND Employee.nickname = 'E02')
or(
    path(Employee::name).eq("Employee01").and(path(Employee::nickname).eq("E01")),
    path(Employee::name).eq("Employee02").and(path(Employee::nickname).eq("E02")),
)

// ํ™•์žฅ ํ•จ์ˆ˜: Employee.name = 'Employee01' AND Employee.nickname = 'E01' or Employee.name = 'Employee02' AND Employee.nickname = 'E02'
path(Employee::name).eq("Employee01").and(path(Employee::nickname).eq("E01")).or(path(Employee::name).eq("Employee02").and(path(Employee::nickname).eq("E02")))

Comparison operators

๋น„๊ต ์—ฐ์‚ฐ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด, ๋‹ค์Œ ํ•จ์ˆ˜๋“ค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • = (equal or eq)

  • <> (notEqual or ne)

  • > (greaterThan or gt)

  • >= (greaterThanOrEqualTo or ge)

  • < (lessThan or lt)

  • <= (lessThanOrEqualTo or le)

path(Book::price).equal(BigDecimal.valueOf(100))
path(Book::price).eq(BigDecimal.valueOf(100))

path(Book::price).notEqual(BigDecimal.valueOf(100))
path(Book::price).ne(BigDecimal.valueOf(100))

path(Book::price).greaterThan(BigDecimal.valueOf(100))
path(Book::price).gt(BigDecimal.valueOf(100))

path(Book::price).greaterThanOrEqualTo(BigDecimal.valueOf(100))
path(Book::price).ge(BigDecimal.valueOf(100))

path(Book::price).lessThan(BigDecimal.valueOf(100))
path(Book::price).lt(BigDecimal.valueOf(100))

path(Book::price).lessThanOrEqualTo(BigDecimal.valueOf(100))
path(Book::price).le(BigDecimal.valueOf(100))

All or Any

ํ•จ์ˆ˜ ์ด๋ฆ„ ๋งˆ์ง€๋ง‰์— all๊ณผ any๋ฅผ ๋ถ™์ด๋Š” ๊ฒƒ์œผ๋กœ subquery์— ๋Œ€ํ•œ All๊ณผ Any ์—ฐ์‚ฐ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

val query = jpql {
    val annualSalaries = select(
        path(FullTimeEmployee::annualSalary)(EmployeeSalary::value),
    ).from(
        entity(FullTimeEmployee::class),
        join(FullTimeEmployee::departments),
    ).where(
        path(EmployeeDepartment::departmentId).eq(3L),
    ).asSubquery()

    select(
        path(FullTimeEmployee::employeeId),
    ).from(
        entity(FullTimeEmployee::class),
    ).where(
        path(FullTimeEmployee::annualSalary)(EmployeeSalary::value).gtAll(annualSalaries),
    )
}

Null

null ๋น„๊ต ์—ฐ์‚ฐ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด, isNull()๊ณผ isNotNull()์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

path(Employee::nickname).isNull()

path(Employee::nickname).isNotNull()

Like

like ๋น„๊ต ์—ฐ์‚ฐ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด, like()์™€ notLike()๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

path(Employee::nickname).like("E%")
path(Employee::nickname).like("E_", escape = '_')

path(Employee::nickname).notLike("E%")
path(Employee::nickname).notLike("E_", escape = '_')

Between

between ๋น„๊ต ์—ฐ์‚ฐ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด, between()๊ณผ notBetween()์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

path(Employee::price).between(BigDecimal.valueOf(100), BigDecimal.valueOf(200))

path(Employee::price).notBetween(BigDecimal.valueOf(100), BigDecimal.valueOf(200))

In

in ๋น„๊ต ์—ฐ์‚ฐ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด, in()๊ณผ notIn()์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

path(Employee::price).`in`(BigDecimal.valueOf(100), BigDecimal.valueOf(200))

path(Employee::price).notIn(BigDecimal.valueOf(100), BigDecimal.valueOf(200))

Exists

exists ์—ฐ์‚ฐ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด, exists()์™€ notExists()์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

exists(subquery)

notExists(subquery)

Empty

empty ์—ฐ์‚ฐ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด, isEmpty()์™€ isNotEmpty()์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

path(Employee::departments).isEmpty()

path(Employee::departments).isNotEmpty()

Database function

DB ํ•จ์ˆ˜๋‚˜ ์‚ฌ์šฉ์ž ์ •์˜ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด, KClass<Boolean>๊ณผ ํ•จ๊ป˜ function()์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function(Boolean::class, "myFunction", path(Book::isbn))

์‚ฌ์šฉํ•  ํ•จ์ˆ˜์˜ ์ •๋ณด๋ฅผ JPA ์ œ๊ณต์ž์— ๋“ฑ๋กํ•  ํ•„์š”๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด Hibernate๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด FunctionContributor๋ฅผ ๋ฐ˜๋“œ์‹œ ๋“ฑ๋กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Custom predicate

์ปค์Šคํ…€ predicate๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด, customPredicate()์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

customPredicate("{0} MEMBER OF {1}", value(author), path(Book::authors))

๋งŒ์•ฝ customPredicate()์„ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๋‚˜๋งŒ์˜ DSL์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”.

Last updated