Dynamic Queries in Spring Data JPA

前端 未结 3 987
忘了有多久
忘了有多久 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:01

    I would say that using QueryDSL is one way of doing what you want.

    For example I have a repository defined as below:

    public interface UserRepository extends PagingAndSortingRepository<User, Long>, QueryDslPredicateExecutor<User> {
    
        public Page<User> findAll(Predicate predicate, Pageable p);
    }
    

    I can call this method with any combination of parameters, like below:

    public class UserRepositoryTest{
    
        @Autowired
        private UserRepository userRepository;
    
        @Test
        public void testFindByGender() {
            List<User> users = userRepository.findAll(QUser.user.gender.eq(Gender.M));
            Assert.assertEquals(4, users.size());
    
            users = userRepository.findAll(QUser.user.gender.eq(Gender.F));
            Assert.assertEquals(2, users.size());
        }
    
        @Test
        public void testFindByCity() {
    
            List<User> users = userRepository.findAll(QUser.user.address.town.eq("Edinburgh"));
            Assert.assertEquals(2, users.size());
    
            users = userRepository.findAll(QUser.user.address.town.eq("Stirling"));
            Assert.assertEquals(1, users.size());
        }
    
        @Test
        public void testFindByGenderAndCity() {
            List<User> users = userRepository.findAll(QUser.user.address.town.eq("Glasgow").and(QUser.user.gender.eq(Gender.M)));
            Assert.assertEquals(2, users.size());
    
            users = userRepository.findAll(QUser.user.address.town.eq("Glasgow").and(QUser.user.gender.eq(Gender.F)));
            Assert.assertEquals(1, users.size());
        }
    }
    
    0 讨论(0)
  • 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<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) })
       }
    }
    

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

    0 讨论(0)
  • 2020-12-09 11:17

    I have got a solution for this. I wrote some code to extend the spring-data-jpa .

    I call it spring-data-jpa-extra

    spring-data-jpa-extra comes to solve three problem:

    1. dynamic native query support like mybatis
    2. return type can be anything
    3. no code, just sql

    You can try it : )

    0 讨论(0)
提交回复
热议问题