wake-up-neo.com

Dynamische Abfragen in Spring Data JPA

Ich suche nach einer Lösung zum dynamischen Erstellen von Abfragen mit Spring Data JPA. Ich habe einen GameController mit einem RESTful-Service-Endpunkt Spielen, der 4 optionale Parameter akzeptiert: Genre, Plattform, Jahr, Titel. Die API kann keine dieser, alle 4 und jede Kombination dazwischen übergeben werden. Wenn ein Parameter nicht übergeben wird, wird standardmäßig null verwendet. Ich benötige eine Methode im Repository, die die entsprechende Abfrage erstellt und im Idealfall auch noch Spring Data JPA-Paging zulässt, obwohl ich nicht sicher bin, ob dies möglich ist.

Ich habe diesen Artikel gefunden, aber das scheint nicht das zu sein, was ich brauche, es sei denn, ich habe ein Missverständnis./- http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/

Ich weiß, dass JPA eine Abfragekriterien-API hat, habe aber keine Ahnung, wie dies implementiert werden soll.

Mir ist klar, dass ich für jedes mögliche Szenario eine Methode erstellen könnte, aber das scheint wirklich eine schlechte Übung und eine Menge unnötigen Codes zu sein.

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

Ich würde sagen, dass die Verwendung von QueryDSL eine Möglichkeit ist, das zu tun, was Sie wollen.

Zum Beispiel habe ich ein Repository wie folgt definiert:

public interface UserRepository extends PagingAndSortingRepository<User, Long>, QueryDslPredicateExecutor<User> {

    public Page<User> findAll(Predicate predicate, Pageable p);
}

Ich kann diese Methode mit einer beliebigen Kombination von Parametern aufrufen, wie unten:

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

Für diejenigen, die Kotlin (und Spring Data JPA) verwenden, haben wir gerade eine Kotlin JPA Specification DSL-Bibliothek mit der Sie typsichere dynamische Abfragen für ein JPA-Repository erstellen können.

Es verwendet die JpaSpecificationExecutor von Spring Data (d. H. JPA-Kriterienabfragen), ohne dass ein Boilerplate oder ein generiertes Metamodell erforderlich ist.

In der Readme finden Sie weitere Informationen zur internen Funktionsweise. Hier finden Sie jedoch die entsprechenden Codebeispiele für eine kurze Einführung.

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

Bei komplexeren und dynamischeren Abfragen empfiehlt es sich, Funktionen zu erstellen, die die DSL verwenden, um Abfragen besser lesbar zu machen (wie bei QueryDSL) und ihre Komposition in komplexen dynamischen Abfragen zu ermöglichen.

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

Diese Funktionen können mit and() und or() für komplexe verschachtelte Abfragen kombiniert werden:

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

Oder sie können mit einem Service-Layer-Abfrage-DTO und einer Mapping-Erweiterungsfunktion kombiniert werden

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

für leistungsstarke dynamische Abfragen:

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

JpaSpecificationExecutor unterstützt Paging, sodass Sie pageable, typsichere und dynamische Abfragen durchführen können!

2
James Bassett

Ich habe eine Lösung dafür. Ich habe Code geschrieben, um das Spring-Data-JPA zu erweitern.

Ich nenne es spring-data-jpa-extra

spring-data-jpa-extra löst drei Probleme:

  1. unterstützung für dynamische native Abfragen wie mybatis
  2. rückgabetyp kann alles sein
  3. kein Code, nur SQL

Du kannst es versuchen : )

1
stormning