【问题标题】:Dynamic Queries in Spring Data JPASpring Data JPA 中的动态查询
【发布时间】:2015-01-27 09:20:27
【问题描述】:

我正在寻找一种使用 Spring Data JPA 动态构建查询的解决方案。我有一个 GameController,它有一个 RESTful 服务端点 /games,它接受 4 个可选参数:流派、平台、年份、标题。 API 可以不通过任何一个,所有 4 个,以及它们之间的每个组合。如果未传递任何参数,则默认为 null。我需要存储库中的一个方法来构建适当的查询,并且理想情况下还允许 Spring Data JPA 分页,尽管我不确定这是否可能。

我找到了这篇文章,但这似乎不是我需要的,除非我误解了。 http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/

我知道 JPA 有一个 Query Criteria API,但真的不知道如何实现它。

我意识到我可以为每个可能的场景创建一个方法,但这似乎是一种非常糟糕的做法,并且有很多不必要的代码。

游戏存储库:

package net.jkratz.igdb.repository;

import net.jkratz.igdb.model.Game;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface GameRepository extends JpaRepository<Game, Long> {

    @Query("select g from Game g, GamePlatformMap gpm, Platform p where g = gpm.game and gpm.platform = p and p.id = :platform")
    Page<Game> getGamesByPlatform(@Param("platform") Long platformId, Pageable pageable);

    @Query("select g from Game g where g.title like :title")
    Page<Game> getGamesByTitle(@Param("title") String title, Pageable pageable);

    @Query("select g from Game g, GameGenreMap ggm, Genre ge where g = ggm.game and ggm.genre = ge and ge.id = :genreId")
    Page<Game> getGamesByGenre(@Param("genre") Long genreId, Pageable pageable);
}

【问题讨论】:

    标签: spring hibernate jpa spring-data spring-data-jpa


    【解决方案1】:

    我会说使用 QueryDSL 是做你想做的事情的一种方式。

    例如,我有一个如下定义的存储库:

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

    我可以使用任何参数组合调用此方法,如下所示:

    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());
        }
    }
    

    【讨论】:

    • @Alan Hay,使用 QueryDsl + Spring Data Repository 时是否可以指定 Join Fetch(我的大部分关系通常在 LAZY 中)?
    • @AlanHay QUser 变量从何而来??
    • QueryDsl 生成这些查询对象。参见,例如:stackoverflow.com/questions/35850436/…
    【解决方案2】:

    我有一个解决方案。我写了一些代码来扩展 spring-data-jpa 。

    我叫它spring-data-jpa-extra

    spring-data-jpa-extra 来解决三个问题:

    1. 动态原生查询支持如 mybatis
    2. 返回类型可以是任何东西
    3. 没有代码,只有 sql

    你可以试试:)

    【讨论】:

      【解决方案3】:

      对于那些使用 Kotlin(和 Spring Data JPA)的人,我们刚刚开源了一个 Kotlin JPA Specification DSL library,它允许您为 JPA 存储库创建类型安全的动态查询。

      它使用 Spring Data 的 JpaSpecificationExecutor(即 JPA 标准查询),但不需要任何样板文件或生成的元模型。

      readme 有更多关于其内部工作原理的详细信息,但这里有相关的代码示例以供快速介绍。

      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) })
         }
      }
      

      对于更复杂和动态的查询,最好创建使用 DSL 的函数以使查询更具可读性(就像您对 QueryDSL 所做的那样),并允许在复杂的动态查询中组合它们。

      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%")
      }
      

      这些函数可以与and()or() 组合用于复杂的嵌套查询:

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

      或者它们可以与服务层查询 DTO 和映射扩展功能相结合

      /**
       * 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)
      )
      

      对于强大的动态查询:

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

      JpaSpecificationExecutor支持分页,因此可以实现分页、类型安全、动态查询!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-02-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-05
        • 2020-07-31
        • 1970-01-01
        • 2019-02-28
        相关资源
        最近更新 更多