Une simple implémentation de marquage avec JPA

Une mise en œuvre simple de marquage avec JPA

1. Vue d'ensemble

Le balisage est un modèle de conception standard qui nous permet de catégoriser et de filtrer les éléments de notre modèle de données.

Dans cet article, nous allons mettre en œuvre le balisage à l'aide de Spring et JPA. Nous utiliserons Spring Data pour accomplir cette tâche. De plus, cette implémentation sera utile si vous souhaitez utiliser Hibernate.

Ceci est le deuxième article d'une série sur la mise en œuvre du marquage. Pour voir comment l'implémenter avec Elasticsearch, accédez àhere.

2. Ajout de balises

First, we’re going to explore the most straightforward implementation of tagging: a List of Strings. Nous pouvons implémenter des balises en ajoutant un nouveau champ à notre entité comme ceci:

@Entity
public class Student {
    // ...

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

    // ...
}

Notez l'utilisation de l'annotationElementCollection sur notre nouveau champ. Puisque nous courons devant un magasin de données, nous devons lui indiquer comment stocker nos balises.

Si nous n’ajoutions pas l’annotation, elles seraient stockées dans un seul objet blob avec lequel il serait plus difficile de travailler. Cette annotation crée une autre table appeléeSTUDENT_TAGS (c'est-à-dire<entity> _) qui rendra nos requêtes plus robustes.

This creates a One-To-Many relationship between our entity and tags! Nous mettons en œuvre la version la plus simple du balisage ici. Pour cette raison, nous aurons potentiellement un grand nombre de balises en double (une pour chaque entité qui en possède). Nous parlerons plus en détail de ce concept plus tard.

3. Créer des requêtes

Les balises nous permettent d'effectuer des requêtes intéressantes sur nos données. Nous pouvons rechercher des entités avec une balise spécifique, filtrer une analyse de table ou même limiter les résultats renvoyés par une requête particulière. Jetons un œil à chacun de ces cas.

3.1. Recherche de tags

Le champtag que nous avons ajouté à notre modèle de données peut être recherché de la même manière que les autres champs de notre modèle. Nous conservons les balises dans une table séparée lors de la construction de la requête.

Voici comment nous recherchons une entité contenant une balise spécifique:

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

Étant donné que les balises sont stockées dans une autre table, nous devons les JOINDRE dans notre requête - cela renverra toutes les entitésStudent avec une balise correspondante.

Commençons par configurer quelques données de test:

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

Ensuite, testons-le et assurez-vous qu'il fonctionne:

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

Nous récupérerons le premier étudiant du référentiel avec le tagfull time. C'est exactement ce que nous voulions.

De plus, nous pouvons étendre cet exemple pour montrer comment filtrer un ensemble de données plus volumineux. Voici l'exemple:

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

Avec un peu de refactoring, nous pouvons modifier le référentiel pour prendre en charge plusieurs balises en tant que filtre afin d'affiner encore davantage nos résultats.

3.2. Filtrer une requête

Une autre application utile de notre marquage simple consiste à appliquer un filtre à une requête spécifique. Bien que les exemples précédents nous aient également permis de filtrer, ils ont travaillé sur toutes les données de notre tableau.

Puisque nous devons également filtrer d'autres recherches, prenons un exemple:

@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);

Nous pouvons voir que cette requête est presque identique à celle ci-dessus. Untag n'est rien de plus qu'une autre contrainte à utiliser dans notre requête.

Notre exemple d'utilisation vous semblera également familier:

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

Par conséquent, nous pouvons appliquer la balisefilter à n'importe quelle requête sur cette entité. Cela donne à l'utilisateur beaucoup de pouvoir dans l'interface pour trouver les données exactes dont il a besoin.

4. Balisage avancé

Notre implémentation de balisage simple est un excellent point de départ. Mais, en raison de la relation un-à-plusieurs, nous pouvons rencontrer certains problèmes.

Tout d'abord, nous nous retrouverons avec un tableau rempli de balises en double. Ce ne sera pas un problème pour les petits projets, mais des systèmes plus importants pourraient se retrouver avec des millions (voire des milliards) d'entrées en double.

De plus, notre modèleTag n’est pas très robuste. Et si nous voulions savoir quand le tag a été créé? Dans notre mise en œuvre actuelle, nous n’avons aucun moyen de le faire.

Enfin, nous ne pouvons pas partager nostags entre plusieurs types d'entités. Cela peut entraîner encore plus de duplication qui peut avoir un impact sur les performances de notre système.

Many-To-Many relationships will solve most of our problems. Pour savoir comment utiliser l'annotation@manytomany, consultezthis article (car cela dépasse le cadre de cet article).

5. Conclusion

Le balisage est un moyen simple et direct d'interroger des données et, combiné à l'API Java Persistence, nous disposons d'une fonctionnalité de filtrage puissante qui est facilement mise en œuvre.

Bien que la mise en œuvre simple ne soit pas toujours la plus appropriée, nous avons mis en évidence les voies à suivre pour aider à résoudre cette situation.

Comme toujours, le code utilisé dans cet article se trouve surover on GitHub.