Dynamic Queries in Spring Data JPA

前端 未结 3 996
忘了有多久
忘了有多久 2020-12-09 10:39

I am looking for a solution to dynamically build queries using Spring Data JPA. I have a GameController which has a RESTful service endpoint /games which takes 4 optional pa

3条回答
  •  臣服心动
    2020-12-09 11:11

    For those using Kotlin (and Spring Data JPA), we've just open-sourced a Kotlin JPA Specification DSL library which lets you create type-safe dynamic queries for a JPA Repository.

    It uses Spring Data's JpaSpecificationExecutor (i.e. JPA criteria queries), but without the need for any boilerplate or generated metamodel.

    The readme has more details on how it works internally, but here's the relevant code examples for a quick intro.

    import au.com.console.jpaspecificationsdsl.*   // 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 = 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) })
       }
    }
    

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

    fun hasName(name: String?): Specifications? = name?.let {
        TvShow::name.equal(it)
    }
    
    fun availableOnNetflix(available: Boolean?): Specifications? = available?.let {
        TvShow::availableOnNetflix.equal(it)
    }
    
    fun hasKeywordIn(keywords: List?): Specifications? = keywords?.let {
        or(keywords.map { hasKeyword(it) })
    }
    
    fun hasKeyword(keyword: String?): Specifications? = 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 = 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(): Specifications = and(
            hasName(name),
            availableOnNetflix(availableOnNetflix),
            hasKeywordIn(keywords)
    )
    

    for powerful dynamic queries:

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

    JpaSpecificationExecutor supports paging, so you can achieve pageable, type-safe, dynamic queries!

提交回复
热议问题