Quick Start
Write your first null-safe dynamic query in 5 minutes.
Step 1: Add the dependency
kotlin
implementation("io.github.harryjhin:querydsl-ktx-spring-boot-starter:1.1.0")groovy
implementation 'io.github.harryjhin:querydsl-ktx-spring-boot-starter:1.1.0'See Installation for Maven and module selection details.
Step 2: Extend QuerydslRepository
kotlin
@Repository
class MemberQueryRepository : QuerydslRepository<Member>() {
private val member = QMember.member
}QuerydslRepository<T> gives you:
- All 8 null-safe infix extension interfaces
JPAQueryFactorywrappers (selectFrom,select,update,delete)- Pagination helpers (
page,slice,fetch) - Bulk DML helper (
modifying)
The type parameter T is resolved automatically, so there is no need to override domainClass.
Step 3: Write a dynamic query
kotlin
@Repository
class MemberQueryRepository : QuerydslRepository<Member>() {
private val member = QMember.member
fun search(
name: String?, // nullable parameter
status: Status?,
minAge: Int?,
maxAge: Int?,
pageable: Pageable,
): Page<Member> =
selectFrom(member)
.where(
member.name contains name, // null ⟹ skip
member.status eq status, // null ⟹ skip
member.age between (minAge to maxAge), // null pair ⟹ skip
)
.page(pageable) // auto count query
}Step 4: Call it
kotlin
@Service
class MemberService(
private val memberQueryRepository: MemberQueryRepository,
) {
// All filters applied
fun searchAll() = memberQueryRepository.search(
name = "John",
status = Status.ACTIVE,
minAge = 20,
maxAge = 60,
pageable = PageRequest.of(0, 20),
)
// Only name filter; other conditions skipped automatically
fun searchByName() = memberQueryRepository.search(
name = "John",
status = null,
minAge = null,
maxAge = null,
pageable = PageRequest.of(0, 20),
)
}sql
SELECT m.*
FROM member m
WHERE m.name LIKE '%John%'
AND m.status = 'ACTIVE'
AND m.age BETWEEN 20 AND 60
LIMIT 20 OFFSET 0sql
SELECT m.*
FROM member m
WHERE m.name LIKE '%John%'
LIMIT 20 OFFSET 0No BooleanBuilder, no if-checks
The same repository method handles any combination of filters. Null parameters are simply ignored. No conditional logic needed.
Full Example
kotlin
@Entity
class Member(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
val name: String,
@Enumerated(EnumType.STRING)
val status: Status,
val age: Int,
)
enum class Status { ACTIVE, INACTIVE, DELETED }
@Repository
class MemberQueryRepository : QuerydslRepository<Member>() {
private val member = QMember.member
fun search(
name: String?,
status: Status?,
minAge: Int?,
maxAge: Int?,
pageable: Pageable,
): Page<Member> =
selectFrom(member)
.where(
member.name contains name,
member.status eq status,
member.age between (minAge to maxAge),
)
.page(pageable)
fun findByStatus(status: Status): List<Member> =
selectFrom(member)
.where(member.status eq status)
.fetch()
}What's Next?
- Dynamic Queries: Understand the null-safety contract in depth
- Extension Reference: All 8 interfaces with function listings
- Pagination: slice vs page vs fetch
- Bulk DML: Safe update and delete operations