복붙노트

[SPRING] 스프링 데이터 JPA의 동적 쿼리

SPRING

스프링 데이터 JPA의 동적 쿼리

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

나는이 기사를 찾았지만, 오해를하지 않으면 이것이 필요한 것 같지 않습니다. http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/

JPA에는 Query Criteria 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);
}

해결법

  1. ==============================

    1.QueryDSL을 사용하면 원하는 것을 수행하는 한 가지 방법이라고 말할 수 있습니다.

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

    2.Kotlin (및 Spring Data JPA)을 사용하는 사람들을 위해 우리는 JPA 저장소에 대한 유형 안전 동적 쿼리를 만들 수있는 Kotlin JPA Specification DSL 라이브러리를 공개했습니다.

    Kotlin (및 Spring Data JPA)을 사용하는 사람들을 위해 우리는 JPA 저장소에 대한 유형 안전 동적 쿼리를 만들 수있는 Kotlin JPA Specification DSL 라이브러리를 공개했습니다.

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

    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는 페이징을 지원하므로 페이지 가능하고 형식이 안전한 동적 쿼리를 얻을 수 있습니다!

  3. ==============================

    3.나는 이것을위한 해결책을 가지고있다. 스프링 - 데이터 -JPA를 확장하는 코드를 작성했습니다.

    나는 이것을위한 해결책을 가지고있다. 스프링 - 데이터 -JPA를 확장하는 코드를 작성했습니다.

    나는 이것을 spring-data-jpa-extra라고 부른다.

    spring-data-jpa-extra는 세 가지 문제를 해결합니다.

    그것을 시도 할 수 있습니다 :)

  4. from https://stackoverflow.com/questions/27193337/dynamic-queries-in-spring-data-jpa by cc-by-sa and MIT license