Spring数据JPA中的动态查询

我正在寻找一个解决方案来动态构建使用Spring Data JPA的查询。 我有一个GameController,它有一个REST风格的服务端点/游戏,它需要4个可选参数:流派,平台,年份,标题。 这些API可能不会被传递,全部4个,以及它们之间的每个组合。 如果有任何参数未被传递,则默认为null。 我需要一个在仓库中的方法,将建立适当的查询,理想情况下,仍然允许Spring Data JPA分页,但我不知道这是可能的。

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

我知道JPA有一个查询标准的API,但真的不知道如何实现这一点。

我意识到我可以为每个可能的场景创建一个方法,但这似乎是非常糟糕的做法和大量不必要的代码。

GameRepository:

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

我会说,使用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()); } } 

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

我把它称为spring-data-jpa-extra

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

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

你可以试试看)

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

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

自述文件有更多关于内部工作原理的细节,但下面是相关的快速介绍的代码示例。

 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支持分页,所以你可以实现可分页,类型安全的动态查询!