2014-11-28 3 views
14

스프링 데이터 JPA를 사용하여 동적으로 쿼리를 작성하는 솔루션을 찾고 있습니다. 장르, 플랫폼, 연도, 제목 등 네 가지 옵션 매개 변수를 사용하는 RESTful 서비스 끝점/게임이있는 GameController가 있습니다. API는 그 중 4 개 모두와 모든 조합을 전달할 수 없습니다. 매개 변수가 전달되지 않으면 기본값이 null입니다. 리포지토리에 적절한 쿼리를 작성하는 메소드가 필요하며 가능하다면 확실하지는 않지만 스프링 데이터 JPA 페이징을 허용하는 것이 이상적입니다.스프링 데이터의 동적 쿼리 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); 
} 

답변

12

내가 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()); 
    } 
} 
+0

감사합니다. 좋은 해결책처럼 보입니다. – greyfox

+0

@Alan Hay, QueryDsl + Spring Data Repository를 사용할 때 Join Fetch (보통 저는 LAZY에서 대부분의 관계가 있습니다)를 지정할 수 있습니까? – sendreams

0

나는 이것에 대한 해결책을 가지고

예를 들어 나는 저장소는 다음과 같이 정의했습니다. 봄 - 데이터 -JPA 확장 코드를 작성했습니다. MyBatis로

  • 반환 형식과 같은

    1. 동적 기본 쿼리 지원이 무엇을
    2. 없는 코드가 될 수 있습니다

      가 나는 spring-data-jpa-extra

      스프링 데이터 JPA-추가 세 가지 문제를 해결하기 위해 오는 전화 , 그냥 SQL

    시도해 볼 수 있습니다 :)

  • 1

    Kotlin (및 Spring Data JPA)을 사용하는 사용자를 위해 우리는 Kotlin JPA Specification DSL library을 오픈 소스로 사용하여 JPA 저장소에 대한 유형 안전 동적 쿼리를 만들 수 있습니다.

    스프링 데이터의 JpaSpecificationExecutor (예 : JPA criteria queries)을 사용하지만, 상용구 또는 생성 된 메타 모델이 필요하지 않습니다.

    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에 대해서와 같은).

    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) 
    ) 
    
    와 결합 될 수있다 :

    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 query = TvShowQuery(availableOnNetflix = false, keywords = listOf("Rick", "Jimmy")) 
    val shows = tvShowRepo.findAll(query.toSpecification()) 
    

    JpaSpecificationExecutor은 페이징을 지원하므로 페이지 가능하고 형식이 안전한 동적 쿼리를 얻을 수 있습니다!