Eine einfache Tagging-Implementierung mit JPA

Eine einfache Tagging-Implementierung mit JPA

1. Überblick

Tagging ist ein Standard-Entwurfsmuster, mit dem wir Elemente in unserem Datenmodell kategorisieren und filtern können.

In diesem Artikel implementieren wir das Tagging mit Spring und JPA. Wir werden Spring Data verwenden, um die Aufgabe zu erfüllen. Darüber hinaus ist diese Implementierung hilfreich, wenn Sie den Ruhezustand verwenden möchten.

Dies ist der zweite Artikel in einer Reihe zur Implementierung von Tagging. Um zu sehen, wie es mit Elasticsearch implementiert wird, gehen Sie zuhere.

2. Tags hinzufügen

First, we’re going to explore the most straightforward implementation of tagging: a List of Strings. Wir können Tags implementieren, indem wir unserer Entität ein neues Feld wie folgt hinzufügen:

@Entity
public class Student {
    // ...

    @ElementCollection
    private List tags = new ArrayList<>();

    // ...
}

Beachten Sie die Verwendung der AnnotationElementCollectionin unserem neuen Feld. Da wir vor einem Datenspeicher ausgeführt werden, müssen wir ihm mitteilen, wie unsere Tags gespeichert werden sollen.

Wenn wir die Anmerkung nicht hinzufügen, werden sie in einem einzelnen Blob gespeichert, mit dem es schwieriger wäre, zu arbeiten. Diese Annotation erstellt eine weitere Tabelle mit dem NamenSTUDENT_TAGS (d. H.<entity> _), wodurch unsere Abfragen robuster werden.

This creates a One-To-Many relationship between our entity and tags! Wir implementieren hier die einfachste Version des Tagging. Aus diesem Grund haben wir möglicherweise viele doppelte Tags (eines für jede Entität, die es hat). Wir werden später mehr über dieses Konzept sprechen.

3. Abfragen erstellen

Mithilfe von Tags können wir einige interessante Abfragen zu unseren Daten durchführen. Wir können nach Entitäten mit einem bestimmten Tag suchen, einen Tabellenscan filtern oder sogar einschränken, welche Ergebnisse in einer bestimmten Abfrage zurückkommen. Schauen wir uns jeden dieser Fälle an.

3.1. Tags suchen

Das Feldtag, das wir unserem Datenmodell hinzugefügt haben, kann ähnlich wie andere Felder in unserem Modell durchsucht werden. Wir speichern die Tags in einer separaten Tabelle, wenn wir die Abfrage erstellen.

So suchen wir nach einer Entität mit einem bestimmten Tag:

@Query("SELECT s FROM Student s JOIN s.tags t WHERE t = LOWER(:tag)")
List retrieveByTag(@Param("tag") String tag);

Da die Tags in einer anderen Tabelle gespeichert sind, müssen wir sie in unserer Abfrage VERBINDEN. Dadurch werden alleStudent-Entitäten mit einem übereinstimmenden Tag zurückgegeben.

Lassen Sie uns zunächst einige Testdaten einrichten:

Student student = new Student(0, "Larry");
student.setTags(Arrays.asList("full time", "computer science"));
studentRepository.save(student);

Student student2 = new Student(1, "Curly");
student2.setTags(Arrays.asList("part time", "rocket science"));
studentRepository.save(student2);

Student student3 = new Student(2, "Moe");
student3.setTags(Arrays.asList("full time", "philosophy"));
studentRepository.save(student3);

Student student4 = new Student(3, "Shemp");
student4.setTags(Arrays.asList("part time", "mathematics"));
studentRepository.save(student4);

Als nächstes testen wir es und stellen sicher, dass es funktioniert:

// Grab only the first result
Student student2 = studentRepository.retrieveByTag("full time").get(0);
assertEquals("name incorrect", "Larry", student2.getName());

Wir erhalten den ersten Schüler im Repository mit dem Tagfull timezurück. Genau das wollten wir.

Darüber hinaus können wir dieses Beispiel erweitern, um zu zeigen, wie ein größerer Datensatz gefiltert wird. Hier ist das Beispiel:

List students = studentRepository.retrieveByTag("full time");
assertEquals("size incorrect", 2, students.size());

Mit ein wenig Überarbeitung können wir das Repository so ändern, dass mehrere Tags als Filter verwendet werden, damit wir unsere Ergebnisse noch weiter verfeinern können.

3.2. Eine Abfrage filtern

Eine weitere nützliche Anwendung unserer einfachen Kennzeichnung ist das Anwenden eines Filters auf eine bestimmte Abfrage. In den vorherigen Beispielen konnten wir zwar auch filtern, sie haben jedoch alle Daten in unserer Tabelle bearbeitet.

Da wir auch andere Suchanfragen filtern müssen, sehen wir uns ein Beispiel an:

@Query("SELECT s FROM Student s JOIN s.tags t WHERE s.name = LOWER(:name) AND t = LOWER(:tag)")
List retrieveByNameFilterByTag(@Param("name") String name, @Param("tag") String tag);

Wir können sehen, dass diese Abfrage fast identisch mit der obigen ist. Eintag ist nichts anderes als eine weitere Einschränkung, die in unserer Abfrage verwendet werden soll.

Unser Anwendungsbeispiel wird Ihnen auch bekannt vorkommen:

Student student2 = studentRepository.retrieveByNameFilterByTag(
  "Moe", "full time").get(0);
assertEquals("name incorrect", "moe", student2.getName());

Folglich können wir das Tagfilter auf jede Abfrage für diese Entität anwenden. Dies gibt dem Benutzer viel Macht in der Schnittstelle, um die genauen Daten zu finden, die er benötigt.

4. Erweiterte Kennzeichnung

Unsere einfache Tagging-Implementierung ist ein guter Anfang. Aufgrund der Eins-zu-Viele-Beziehung können wir jedoch auf einige Probleme stoßen.

Zunächst erhalten wir eine Tabelle mit doppelten Tags. Dies ist bei kleinen Projekten kein Problem, aber größere Systeme können Millionen (oder sogar Milliarden) doppelte Einträge verursachen.

Außerdem ist unserTag-Modell nicht sehr robust. Was wäre, wenn wir wissen wollten, wann das Tag ursprünglich erstellt wurde? In unserer aktuellen Implementierung haben wir keine Möglichkeit, dies zu tun.

Schließlich können wir unseretagsnicht auf mehrere Entitätstypen verteilen. Dies kann zu noch mehr Duplikaten führen, die sich auf die Systemleistung auswirken können.

Many-To-Many relationships will solve most of our problems. Um zu erfahren, wie Sie die Annotation@manytomany verwenden, lesen Siethis article (da dies den Rahmen dieses Artikels sprengt).

5. Fazit

Tagging ist eine einfache und unkomplizierte Möglichkeit, Daten abzufragen. In Kombination mit der Java Persistence API verfügen wir über eine leistungsstarke Filterfunktion, die einfach implementiert werden kann.

Auch wenn die einfache Implementierung nicht immer am besten geeignet ist, haben wir die Wege aufgezeigt, die zur Behebung dieser Situation erforderlich sind.

Wie immer befindet sich der in diesem Artikel verwendete Code aufover on GitHub.