Verwenden Sie Kriterienabfragen in einer Spring Data-Anwendung

Verwenden Sie Kriterienabfragen in einer Spring-Datenanwendung

1. Einführung

Spring Data JPA bietet viele Möglichkeiten für den Umgang mit Entitäten, einschließlichquery methods und benutzerdefiniertenJPQL queries. Manchmal brauchen wir jedoch einen programmatischeren Ansatz: zum BeispielCriteria API oderQueryDSL.

Criteria API offers a programmatic way to create typed queries, wodurch wir Syntaxfehler vermeiden können. Wenn wir es mit der Metamodel-API verwenden, überprüft es außerdem beim Kompilieren, ob die richtigen Feldnamen und -typen verwendet wurden.

Es hat jedoch seine Schattenseiten: Wir müssen eine ausführliche Logik schreiben, die mit Boilerplate-Code überfüllt ist.

In diesem Tutorial erfahren Sie, wie wir unsere benutzerdefinierte DAO-Logik mithilfe von Kriterienabfragen implementieren können und wie Spring dabei hilft, den Code für Boilerplates zu reduzieren.

2. Beispielanwendung

Der Einfachheit halber implementieren wir in den Beispielen dieselbe Abfrage auf verschiedene Arten: Suchen von Büchern anhand des Namens des Autors und des Titels mit einemString.

Die EntitätBookdafür sieht folgendermaßen aus:

@Entity
class Book {

    @Id
    Long id;
    String title;
    String author;

    // getters and setters

}

Da wir die Dinge einfach halten möchten, verwenden wir in diesem Lernprogramm keine Metamodel-API.

3. @Repository Klasse

Wie wir wissen, ist im Federkomponentenmodellwe should place our data access logic in @Repository beans. Natürlich kann diese Logik eine beliebige Implementierung verwenden, beispielsweise eine Kriterien-API.

Dazu benötigen wir nur eineEntityManager-Instanz, die wir automatisch verdrahten können:

@Repository
class BookDao {

    EntityManager em;

    // constructor

    List findBooksByAuthorNameAndTitle(String authorName, String title) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery cq = cb.createQuery(Book.class);

        Root book = cq.from(Book.class);
        Predicate authorNamePredicate = cb.equal(book.get("author"), authorName);
        Predicate titlePredicate = cb.like(book.get("title"), "%" + title + "%");
        cq.where(authorNamePredicate, titlePredicate);

        TypedQuery query = em.createQuery(cq);
        return query.getResultList();
    }

}

Der obige Code folgt einem Standard-Workflow für die Kriterien-API:

  • Zuerst erhalten wir eineCriteriaBuilder-Referenz, mit der wir verschiedene Teile der Abfrage erstellen können

  • MitCriteriaBuilder erstellen wir einCriteriaQuery<Book>, das beschreibt, was wir in der Abfrage tun möchten. Außerdem wird der Typ einer Zeile im Ergebnis deklariert

  • MitCriteriaQuery<Book> deklarieren wir den Startpunkt der Abfrage (Book Entität) und speichern ihn zur späteren Verwendung in der Variablenbook

  • Als nächstes erstellen wir mitCriteriaBuilder Prädikate für unsere EntitätBook. Beachten Sie, dass diese Prädikate noch keine Wirkung haben

  • Wir wenden beide Prädikate auf unsereCriteriaQuery.CriteriaQuery.where(Predicate…) an und kombinieren ihre Argumente in einem logischenand. Dies ist der Punkt, an dem wir diese Prädikate mit der Abfrage verknüpfen

  • Danach erstellen wir eineTypedQuery<Book>-Instanz aus unserenCriteriaQuery

  • Schließlich geben wir alle übereinstimmendenBook-Entitäten zurück

Beachten Sie, dass, da wir die DAO-Klasse mit@Repository markiert haben,Spring enables exception translation für diese Klasse.

4. Repository mit benutzerdefinierten Methoden erweitern

automatic custom queries zu haben, ist eine leistungsstarke Spring Data-Funktion. Manchmal benötigen wir jedoch eine ausgefeiltere Logik, die wir mit automatischen Abfragemethoden nicht erstellen können.

Wir können diese Abfragen in separaten DAO-Klassen implementieren (wie im vorherigen Abschnitt).

Weiterhinif we want a @Repository interface to have a method with a custom implementation, we can use composable repositories.

Die benutzerdefinierte Oberfläche sieht folgendermaßen aus:

interface BookRepositoryCustom {
    List findBooksByAuthorNameAndTitle(String authorName, String title);
}

Und die@Repository-Schnittstelle:

interface BookRepository extends JpaRepository, BookRepositoryCustom {}

Außerdem müssen wir unsere vorherige DAO-Klasse ändern, umBookRepositoryCustom zu implementieren und inBookRepositoryImpl umzubenennen:

@Repository
class BookRepositoryImpl implements BookRepositoryCustom {

    EntityManager em;

    // constructor

    @Override
    List findBooksByAuthorNameAndTitle(String authorName, String title) {
        // implementation
    }

}

Wenn wirBookRepository als Abhängigkeit deklarieren, findet SpringBookRepositoryImpl und verwendet es, wenn wir die benutzerdefinierten Methoden aufrufen.

Angenommen, wir möchten auswählen, welche Prädikate in unserer Abfrage verwendet werden sollen. Wenn wir beispielsweise die Bücher nicht nach Autor und Titel suchen möchten, muss nur der Autor übereinstimmen.

Es gibt mehrere Möglichkeiten, dies zu tun, z. B. ein Prädikat nur anzuwenden, wenn das übergebene Argument nichtnull ist:

@Override
List findBooksByAuthorNameAndTitle(String authorName, String title) {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery cq = cb.createQuery(Book.class);

    Root book = cq.from(Book.class);
    List predicates = new ArrayList<>();

    if (authorName != null) {
        predicates.add(cb.equal(book.get("author"), authorName));
    }
    if (title != null) {
        predicates.add(cb.like(book.get("title"), "%" + title + "%"));
    }
    cq.where(predicates.toArray(new Predicate[0]));

    return em.createQuery(cq).getResultList();
}

Dieser Ansatz ist jedochmakes the code hard to maintain, insbesondere wenn wir viele Prädikate haben und diese optional machen möchten.

Es wäre eine praktische Lösung, diese Prädikate zu externalisieren. Mit JPA-Spezifikationen können wir genau das tun. und noch mehr.

5. Verwendung der JPA-Spezifikationen

Spring Data führte dieorg.springframework.data.jpa.domain.Specification-Schnittstelle ein, um ein einzelnes Prädikat zu kapseln:

interface Specification {
    Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb);
}

Wir können Methoden bereitstellen, umSpecification-Instanzen zu erstellen:

static Specification hasAuthor(String author) {
    return (book, cq, cb) -> cb.equal(book.get("author"), author);
}

static Specification titleContains(String title) {
    return (book, cq, cb) -> cb.like(book.get("title"), "%" + title + "%");
}

Um sie zu verwenden, benötigen wir unser Repository, umorg.springframework.data.jpa.repository.JpaSpecificationExecutor<T> zu erweitern:

interface BookRepository extends JpaRepository, JpaSpecificationExecutor {}

Diese Schnittstelledeclares handy methods to work with specifications. Zum Beispiel können wir jetzt alleBook-Instanzen mit dem angegebenen Autor mit diesem Einzeiler finden:

bookRepository.findAll(hasAuthor(author));

Leider erhalten wir keine Methoden, an die wir mehrereSpecification-Argumente übergeben können. Vielmehr erhalten wir Dienstprogrammmethoden in derorg.springframework.data.jpa.domain.Specification-Schnittstelle.

Kombinieren Sie beispielsweise zweiSpecification Instanzen mit logischen und:

bookRepository.findAll(where(hasAuthor(author)).and(titleContains(title)));

Im obigen Beispiel istwhere() eine statische Methode der KlasseSpecification.

Auf diese Weise können wir unsere Abfragen modular gestalten. Außerdem mussten wir das Criteria API-Boilerplate nicht schreiben: Spring hat es für uns bereitgestellt.

Beachten Sie, dass dies nicht bedeutet, dass wir keine Kriterien mehr schreiben müssen. Dieser Ansatz kann nur den Workflow verarbeiten, den wir gesehen haben: Auswahl von Entitäten, die die bereitgestellten Bedingungen erfüllen.

Eine Abfrage kann viele Strukturen haben, die sie nicht unterstützt, z. B. Gruppierung, Rückgabe einer anderen Klasse, aus der wir auswählen, oder Unterabfragen.

6. Fazit

In diesem Lernprogramm haben wir drei Möglichkeiten zur Verwendung von Kriterienabfragen in unserer Spring-Anwendung vorgestellt:

  • Das Erstellen einer DAO-Klasse ist der einfachste und flexibelste Weg

  • Erweiterung der@Repository-Schnittstelle auf nahtlose Integration mit automatischen Abfragen

  • Verwenden von Prädikaten inSpecification Instanzen, um die einfachen Fälle sauberer und weniger ausführlich zu gestalten

Wie üblich sind die Beispiele inover on GitHub verfügbar.