Spring Data JPA Specification DSL for Kotlin

Overview

Spring Data JPA Specification DSL for Kotlin

Build Status License Download

This library provides a fluent DSL for querying spring data JPA repositories using spring data Specifications (i.e. the JPA Criteria API), without boilerplate code or a generated metamodel.

Hat tip to Mike Buhot for the initial implementation.

Quick Start

repositories {
    jcenter()
}

dependencies {
    compile("au.com.console:kotlin-jpa-specification-dsl:2.0.0")
}

Example

= emptySet()) @Entity data class StarRating( @Id @GeneratedValue val id: Int = 0, val stars: Int = 0) //// // 3. Declare JPA Repository with JpaSpecificationExecutor @Repository interface TvShowRepository : CrudRepository , JpaSpecificationExecutor //// // 4. Kotlin Properties are now usable to create fluent specifications @Service class MyService @Inject constructor(val tvShowRepo: TvShowRepository) { fun findShowsReleasedIn2010NotOnNetflix(): List { return tvShowRepo.findAll(TvShow::availableOnNetflix.isFalse() and TvShow::releaseDate.equal("2010")) } /* Fall back to spring API with some extra helpers for more complex join queries */ fun findShowsWithComplexQuery(): List { return tvShowRepo.findAll(where { equal(it.join(TvShow::starRatings).get(StarRating::stars), 2) }) } }">
import au.com.console.jpaspecificationdsl.*   // 1. Import Kotlin magic

////
// 2. Declare JPA Entities
@Entity
data class TvShow(
    @Id
    @GeneratedValue
    val id: Int = 0,
    val name: String = "",
    val synopsis: String = "",
    val availableOnNetflix: Boolean = false,
    val releaseDate: String? = null,
    @OneToMany(cascade = arrayOf(javax.persistence.CascadeType.ALL))
    val starRatings: Set<StarRating> = emptySet())

@Entity
data class StarRating(
    @Id
    @GeneratedValue
    val id: Int = 0,
    val stars: Int = 0)


////
// 3. Declare JPA Repository with JpaSpecificationExecutor
@Repository
interface TvShowRepository : CrudRepository<TvShow, Int>, JpaSpecificationExecutor<TvShow>


////
// 4. Kotlin Properties are now usable to create fluent specifications
@Service
class MyService @Inject constructor(val tvShowRepo: TvShowRepository) {
   fun findShowsReleasedIn2010NotOnNetflix(): List<TvShow> {
     return tvShowRepo.findAll(TvShow::availableOnNetflix.isFalse() and TvShow::releaseDate.equal("2010"))
   }

   /* Fall back to spring API with some extra helpers for more complex join queries */
   fun findShowsWithComplexQuery(): List<TvShow> {
       return tvShowRepo.findAll(where { equal(it.join(TvShow::starRatings).get(StarRating::stars), 2) })
   }
}

Advanced Usage

For more complex and dynamic queries it's good practice to create functions that use the DSL to make queries more readable, and to allow for their composition in complex dynamic queries.

fun hasName(name: String?): Specification<TvShow>? = name?.let {
    TvShow::name.equal(it)
}

fun availableOnNetflix(available: Boolean?): Specification<TvShow>? = available?.let {
    TvShow::availableOnNetflix.equal(it)
}

fun hasReleaseDateIn(releaseDates: List<String>?): Specification<TvShow>? = releaseDates?.let {
    TvShow::releaseDate.`in`(releaseDates)
}

fun hasKeywordIn(keywords: List<String>?): Specification<TvShow>? = keywords?.let {
    or(keywords.map(::hasKeyword))
}

fun hasKeyword(keyword: String?): Specification<TvShow>? = keyword?.let {
    TvShow::synopsis.like("%$keyword%")
}

These functions can be combined with and() and or() for complex nested queries:

    val shows = tvShowRepo.findAll(
            or(
                    and(
                            availableOnNetflix(false),
                            hasKeywordIn(listOf("Jimmy"))
                    ),
                    and(
                            availableOnNetflix(true),
                            or(
                                    hasKeyword("killer"),
                                    hasKeyword("monster")
                            )
                    )
            )
    )

Or they can be combined with a service-layer query DTO and mapping extension function

    /**
     * A TV show query DTO - typically used at the service layer.
     */
    data class TvShowQuery(
            val name: String? = null,
            val availableOnNetflix: Boolean? = null,
            val keywords: List<String> = listOf(),
            val releaseDates: List<String> = listOf()
    )

    /**
     * A single TvShowQuery is equivalent to an AND of all supplied criteria.
     * Note: any criteria that is null will be ignored (not included in the query).
     */
    fun TvShowQuery.toSpecification(): Specification<TvShow> = and(
            hasName(name),
            availableOnNetflix(availableOnNetflix),
            hasKeywordIn(keywords),
            hasReleaseDateIn(releaseDates)
    )

for powerful dynamic queries:

    val query = TvShowQuery(availableOnNetflix = false, keywords = listOf("Rick", "Jimmy"))
    val shows = tvShowRepo.findAll(query.toSpecification())

For more details, refer to JPASpecificationDSLTest.kt in the unit tests.

How it works

This DSL builds on Spring Data's Specifications abstraction, sprinkling some Kotlin sugar over it to remove the boilerplate and the need to generate a metamodel.

The code TvShow::releaseDate.equal("2010") is a call to the Kotlin extension function:

fun <T, R> KProperty1<T, R?>.equal(x: R): Specification<T> = spec { equal(it, x) }

This is a bit dense, but makes sense when it's broken down:

  • T: The type of the object that the property is declared on, in this case TvShow
  • R: The property type, for TvShow::releaseDate it is String
  • KProperty1 : Kotlin reflection API representation of the property TvShow::releaseDate. The 1 refers to a property with 1 receiver, and R? is declared as nullable for the method to work on nullable properties as well as non-null properties.
  • x: The value to test against
  • Specification : The Spring data specifications result

This is implemented using a private helper function spec that captures the common use case of taking an Entity property, and using a CriteriaBuilder to create a Predicate:

private fun <T, R> KProperty1<T, R?>.spec(makePredicate: CriteriaBuilder.(path: Path<R>) -> Predicate): Specification<T> =
    this.let { property -> where { root -> makePredicate(root.get(property)) } }

This uses the where factory method, which expects a callback with the signature: CriteriaBuilder.(Root ) -> Predicate

The code converts a KProperty1 to a Path using root.get (property) .

Once it has a Path to work with, it delegates to the makePredicate function to configure the CriteriaBuilder given the Path.

The makePredicate function passed to spec is an extension function on CriteraiBuilder. So when equal(it, x) is called from inside the spec block, it is invoking CriteriaBuilder::equal.

Contributing to the Project

If you'd like to contribute code to this project you can do so through GitHub by forking the repository and generating a pull request.

By contributing your code, you agree to license your contribution under the terms of the Apache License v2.0.

License

Copyright 2016 RES INFORMATION SERVICES PTY LTD

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Comments
  • Support for Spring Data Moore (Removed Specifications type)

    Support for Spring Data Moore (Removed Specifications type)

    In Spring Data Moore / Spring Data JPA 2.2 the Specifications type was removed. In DATAJPA-1170 and DATAJPA-1449.

    This breaks this library, but it should be rather easy to fix, simply replacing Specifications with Specification.

    Additionally, there are @Nullable annotations now in Specification.java, and thus it will be necessary to handle the nullable return types in Kotlin (with ? on the return type, or !! on the value).

    opened by darioseidl 1
  • Configure OSSRH publishing to Maven Central

    Configure OSSRH publishing to Maven Central

    I've configured my Sonatype credentials in Settings -> Secrets.

    After this is merged, I'll create a new release which should trigger the github action to publish ?

    opened by loia 1
  • Incorporating treat function

    Incorporating treat function

    Hi,

    Wondering if there is any future plan to include JPA Criteria Treat()? https://docs.oracle.com/javaee/7/api/javax/persistence/criteria/CriteriaBuilder.html#treat-javax.persistence.criteria.CollectionJoin-java.lang.Class-

    Usage: From our main entity, we are getting a field which is joined to another entity. However, that entity is from abstract class and we wanted a field from that entity's base class. e.g. Accessing book's price and title

    data class Store (
    val book: BookInfo
    )
    
    abstract class BookInfo(
    val id: Long
    )
    
    open class FictionBook(
    val id: Long
    val title: String
    val price: String
    ) : BookInfo( id)
    {
    toInfo() =toBookInfo(id)
    }
    
    

    -this9is3me

    opened by this9is3me 0
  • error in dsl combineSpec...

    error in dsl combineSpec...

    Type inference failed: inline fun <reified T> combineSpecification(specs: Iterable<Specification<T#1 (type parameter of app.foodin.entity.common.combineSpecification)>?>, operation: Specification<T#1>.(Specification<T#1>) -> Specification<T#1>): Specification<T#1>
    cannot be applied to
    (Iterable<Specification<T#2 (type parameter of app.foodin.entity.common.and)>?>,KFunction2<Specification<T#2>, @ParameterName Specification<T#2>, Specification<T#2>>)
    

    When I build it, this error occurs. I am using Kotlin 1.3.61 and spring boot 2.2.4

    opened by kimyoon21 4
  • integrate klinq?

    integrate klinq?

    Hi,

    after I didn't get any feedback on my pull request (add variance) I added more freatures and put in into https://github.com/klinq/klinq-jpaspec.

    Since this now has a good feature set (used in a couple of projects) - my question is if you want to integrate the changes back here.

    If you are inerested, I can make a pull request.

    opened by zeitlinger 0
Releases(v2.0.0)
Owner
Console Australia
Console Australia
Spring Boot built using Kotlin, H2, Postgres, Hibernate and JPA

Spring-Boot-Kotlin-Sample Spring Boot built using Kotlin, H2, Postgres, Hibernate and JPA Getting Started Reference Documentation For further referenc

Reza Nur Rochmat 0 Jan 7, 2022
Spring-graphql-getting-started - Spring for GraphQL provides support for Spring applications built on GraphQL Java

Getting Started with GraphQL and Spring Boot Spring for GraphQL provides support

Shinya 0 Feb 2, 2022
Reactive setup with Spring WebFlux , Kotlin, Postgres and Spring Data R2DBC

Reactive Spring with Kotlin and Pg Spring WebFlux with Netty instead of Spring Web with Tomcat Mono and Flux in all layers (controller, service, repo)

Bimal Raj Gyawali 7 Dec 9, 2022
Demo Spting REST Service on Kotlin. Works with PostgreSQL via Spring Data. Data initialization provided by liquibase

Spring Boot REST API with Kotlin Spring Boot REST API service. Spring Data with PostgreSQL. Data initialization with Liquibase. Swagger UI Reference D

null 0 Jun 10, 2022
HQ OpenAPI Specification and Client & Server SDK Generators

HQ-API HQ OpenAPI Specification and Client & Server SDK Generators Cloning Github Repository Get access to Flocktory team in Github Adding a new SSH k

Flocktory Spain, S.L. 1 Sep 2, 2022
Spring-kotlin - Learning API Rest with Kotlin, Spring and PostgreSQL

Kotlin, Spring, PostgreSQL and Liquibase Database Migrations Learning Kotlin for

Andre L S Ferreira 2 Feb 14, 2022
This repository contains RabbitMQ Protobuf starters with its usage samples for spring-rabbit and spring-cloud-starter-stream-rabbit modules

This repository contains RabbitMQ Protobuf starters with its usage samples for spring-rabbit and spring-cloud-starter-stream-rabbit modules

Maksim Kostromin 2 Nov 29, 2021
Spring-with-maven - Spring Boot App with Postgresql and maven

Spring Boot Api Aplikasi ini dibuat menggunakan bahasa kotlin dan untuk database

Aldi Aulia Rosyad 1 Jan 12, 2022
Android login spring - Android login against spring backend

Android Jetpack Compose login implementation with JWT tokens against our own bac

null 1 Feb 13, 2022
Kotlin cli maven spring failsafe findbugs cucumber mockito junit car data

Kotlin cli maven spring failsafe findbugs cucumber mockito junit car data

null 0 Feb 1, 2022
A spring-boot project that demonstrates data caching using Redis

A spring-boot project that demonstrates data caching using Redis

Sakawa Bob 1 Mar 26, 2022
Modular Android architecture which showcase Kotlin, MVVM, Navigation, Hilt, Coroutines, Jetpack compose, Retrofit, Unit test and Kotlin Gradle DSL.

SampleCompose Modular Android architecture which showcase Kotlin, MVVM, Navigation, Hilt, Coroutines, Jetpack compose, Retrofit, Unit test and Kotlin

Mohammadali Rezaei 7 Nov 28, 2022
Nice and simple DSL for Espresso Compose UI testing in Kotlin

Kakao Compose Nice and simple DSL for Espresso Compose in Kotlin Benefits Readability Reusability Extensible DSL How to use it Create Screen Create yo

null 74 Dec 26, 2022
An Android template you can use to build your project with gradle kotlin dsl

Android Gradle KTS An Android template you can use to build your project with gradle kotlin dsl Build.gradle.kts You can use your project's build.grad

Deep 17 Sep 12, 2022
An idiomatic Kotlin DSL for creating regular expressions.

Ketex An idiomatic Kotlin DSL for creating regular expressions. For documentation and usage instructions, please take a look at the docs. Here's the m

TheOnlyTails 24 Nov 8, 2022
Detailing about the data provided (Data Visualization Application)

Detailing about the data provided (Data Visualization Application): • In the application, the data provided in the CSV is used for the Scatter plot cr

Neha Sharma 0 Nov 20, 2021
Use Android Data Binding wih Live Data to glue View Model and Android

Gruop-C Spliff Summary Use Android Data Binding wih Live Data to glue View Model and Android. Asynchronous communications implemented with KotlinX Cor

null 2 Nov 21, 2021
A sample skeleton backend app built using Spring Boot kotlin, Expedia Kotlin Graphql, Reactive Web that can be deployed to Google App Engine Flexible environmennt

spring-kotlin-gql-gae This is a sample skeleton of a backend app that was built using: Spring Boot(Kotlin) Reactive Web Sprinng Data R2DBC with MYSQL

Dario Mungoi 7 Sep 17, 2022
Gradle plugin which allows to use typed DSL for generating kubernetes/openshift YAML files

gr8s Gradle plugin which allows using typed DSL for generating kubernetes/openshift YAML files. Based on kuberig Usage import io.github.guai.gr8s.Gene

null 0 Jan 3, 2022