Kotlin JPA Specification DSL

repository·master·Indexed 42 days ago

https://github.com/consoleau/kotlin-jpa-specification-dsl

A fluent Kotlin DSL for Spring Data JPA Specifications that enables type-safe, readable queries using Kotlin property references without requiring a generated JPA metamodel. Supports complex join queries via the where helper and dynamic query composition using and() and or().

Tokens
1.2K
Snippets
5
Records
5
Agent score
4%

What's inside kotlin-jpa-specification-dsl

  1. Compose dynamic queries using `and()` and `or()`

    master

    You can build highly readable and composable queries by wrapping DSL expressions in functions that return Specification<T>. These functions can then be combined using and() and or() to create nested logic.

    // 1. Define composable specification functions
    fun hasName(name: String?): Specification<TvShow>? = name?.let { TvShow::name.equal(it) }
    fun availableOnNetflix(available: Boolean?): Specification<TvShow>? = available?.let { TvShow::availableOnNetflix.equal(it) }
    
    // 2. Combine them using and() and or()
    val shows = tvShowRepo.findAll(
        or(
            and(
                availableOnNetflix(false),
                hasName("Jimmy")
            ),
            and(
                availableOnNetflix(true),
                hasName("Killer")
            )
        )
    )
  2. Use the DSL for fluent JPA queries

    master

    The library allows you to create fluent Spring Data JPA Specifications using Kotlin property references (e.g., Entity::property). This eliminates the need for a generated metamodel or boilerplate Criteria API code.

    To use it:

    1. Import au.com.console.jpaspecificationdsl.*.
    2. Ensure your repository extends JpaSpecificationExecutor<T>.
    3. Use property references with extension functions like .equal(), .isFalse(), or .like() to build queries.
    import au.com.console.jpaspecificationdsl.*
    
    // Assuming TvShowRepository extends JpaSpecificationExecutor<TvShow>
    fun findShowsReleasedIn2010NotOnNetflix(): List<TvShow> {
       return tvShowRepo.findAll(TvShow::availableOnNetflix.isFalse() and TvShow::releaseDate.equal("2010"))
    }
  3. Map Query DTOs to Specifications

    master

    A common pattern for service-layer queries is to create a Data Transfer Object (DTO) representing the search criteria and an extension function to convert that DTO into a single Specification. This allows you to pass a single object to your repository's findAll method.

    data class TvShowQuery(
        val name: String? = null,
        val availableOnNetflix: Boolean? = null
    )
    
    // Convert DTO to Specification. Null criteria are ignored by the 'and' composition.
    fun TvShowQuery.toSpecification(): Specification<TvShow> = and(
        hasName(name),
        availableOnNetflix(availableOnNetflix)
    )
    
    // Usage
    val query = TvShowQuery(availableOnNetflix = false)
    val shows = tvShowRepo.findAll(query.toSpecification())
  4. Perform complex join queries with the `where` helper

    master

    For complex queries involving joins that cannot be expressed via simple property references, use the where factory method. This provides access to the underlying CriteriaBuilder and allows you to manually navigate paths using .join() and .get().

    // Example of joining TvShow to StarRating and filtering by stars
    fun findShowsWithComplexQuery(): List<TvShow> {
        return tvShowRepo.findAll(where { equal(it.join(TvShow::starRatings).get(StarRating::stars), 2) })
    }