Spring DataJPAを使用して動的にクエリを作成するソリューションを探しています。ジャンル、プラットフォーム、年、タイトルの4つのオプションパラメーターを受け取るRESTfulサービスエンドポイント/ gamesを持つGameControllerがあります。 APIには、これらのいずれも、4つすべて、およびその間のすべての組み合わせを渡すことができます。パラメータが渡されない場合、デフォルトでnullになります。適切なクエリを作成し、理想的にはSpring Data JPAページングも許可するメソッドがリポジトリに必要ですが、それが可能かどうかはわかりません。
私はこの記事を見つけましたが、私が誤解しない限り、これは私が必要としているものではないようです。 http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
JPAにQueryCriteria 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を使用することは、あなたが望むことを行う1つの方法だと思います。
たとえば、次のように定義されたリポジトリがあります。
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());
}
}
Kotlin(およびSpring Data JPA)を使用している場合は、JPAリポジトリのタイプセーフな動的クエリを作成できる Kotlin JPA仕様DSLライブラリ をオープンソース化しました。
SpringDataの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
はページングをサポートしているため、ページング可能でタイプセーフな動的クエリを実現できます。
私はこれに対する解決策を持っています。 spring-data-jpaを拡張するコードをいくつか作成しました。
私はそれを spring-data-jpa-extra と呼びます
spring-data-jpa-extraは、次の3つの問題を解決するようになります。
あなたはそれを試すことができます : )