skip to content
back to til

Never use QueryDsl Predicate Executor

til

I like queries that the compiler can check. querydsl gives me a middle ground between raw SQL and an object-relational mapper (ORM), which maps database records to application objects. Spring Data JPA provides QueryDslPredicateExecutor for queries built from Predicate instances in querydsl, but it limits the control I want over joins, ordering, and result limits.

@Repository
interface UserRepository : JpaRepository<User, Long>, QueryDslPredicateExecutor<User>

Suppose we want a page of users sorted by name in descending order. This findAll call takes a predicate, or query condition, and a Pageable instance. The example does not specify the ordering:

import org.springframework.data.querydsl.QuerydslPredicateExecutor
 
@Service
class UserService(
    private val userRepository: UserRepository
) {
    fun findUsers(pageable: Pageable): Page<User> {
        // no ordering!
        val predicate = QUser.user.name.isNotNull
        return userRepository.findAll(predicate, pageable)
    }
}

Using Querydsl directly gives us control over the query while keeping the compiler's checks:

import com.querydsl.jpa.impl.JPAQueryFactory
 
data class Paginated<T>(
    val data: List<T>,
    val next: Long?
)
 
@Repository
class UserRepository(
    private val entityManager: EntityManager
) {
    private val queryFactory = JPAQueryFactory(entityManager)
 
    fun findUsers(pageSize: Int, next: Long?): Paginated<User> {
        val query = queryFactory
            .selectFrom(QUser.user)
            .orderBy(QUser.user.name.desc())
 
        next?.let {
          queryFactory
            .selectFrom(QUser.user)
            .where(QUser.user.id.eq(it))
            .fetchOne()?.let { cursor ->
              query.where(QUser.user.name.lt(cursor.name))
            }
        }
 
        return Paginated(
          items=query.limit((pageSize + 1).toLong()).fetch().take(pageSize),
          next=items.get(pageSize - 1)?.id
        )
    }
}

comments