1. 概要

タグ付けは、データモデル内のアイテムを分類およびフィルタリングできるようにする標準のデザインパターンです。

この記事では、SpringとJPAを使用してタグ付けを実装します。 タスクを実行するためにSpring Dataを使用します。 さらに、この実装は、Hibernateを使用する場合に役立ちます。

これは、タグ付けの実装に関するシリーズの2番目の記事です。 Elasticsearchで実装する方法については、こちらにアクセスしてください。

2. タグの追加

最初に、タグ付けの最も簡単な実装である文字列のリストについて説明します。次のようにエンティティに新しいフィールドを追加することで、タグを実装できます。

@Entity
public class Student {
    // ...

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

    // ...
}

新しいフィールドでElementCollectionアノテーションが使用されていることに注意してください。 データストアの前で実行しているため、タグの保存方法をデータストアに指示する必要があります。

注釈を追加しなかった場合、それらは1つのブロブに格納されるため、操作が難しくなります。 このアノテーションは、という別のテーブルを作成します STUDENT_TAGS (すなわち、 _ )これにより、クエリがより堅牢になります。

これにより、エンティティとタグの間に1対多の関係が作成されます!ここでは、最も単純なバージョンのタグ付けを実装しています。 このため、重複するタグが多数存在する可能性があります(タグを持つエンティティごとに1つ)。 この概念については後で詳しく説明します。

3. クエリの作成

タグを使用すると、データに対していくつかの興味深いクエリを実行できます。 特定のタグを持つエンティティを検索したり、テーブルスキャンをフィルタリングしたり、特定のクエリで返される結果を制限したりすることもできます。 これらの各ケースを見てみましょう。

3.1. タグを検索する

データモデルに追加したtagフィールドは、モデルの他のフィールドと同様に検索できます。 クエリを作成するときは、タグを別のテーブルに保持します。

特定のタグを含むエンティティを検索する方法は次のとおりです。

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

タグは別のテーブルに保存されているため、クエリでそれらを結合する必要があります。これにより、一致するタグを持つすべてのStudentエンティティが返されます。

まず、いくつかのテストデータを設定しましょう。

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

次に、それをテストして、それが機能することを確認しましょう:

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

フルタイムタグを使用して、リポジトリ内の最初の学生を取得します。 これはまさに私たちが望んでいたことです。

さらに、この例を拡張して、より大きなデータセットをフィルタリングする方法を示すことができます。 次に例を示します。

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

少しリファクタリングするだけで、リポジトリを変更して複数のタグをフィルタとして取り込むことができるため、結果をさらに絞り込むことができます。

3.2. クエリのフィルタリング

単純なタグ付けのもう1つの便利なアプリケーションは、特定のクエリにフィルターを適用することです。 前の例でもフィルタリングを実行できましたが、テーブル内のすべてのデータを処理しました。

他の検索もフィルタリングする必要があるため、例を見てみましょう。

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

このクエリは上記のクエリとほぼ同じであることがわかります。 tag は、クエリで使用するもう1つの制約にすぎません。

私たちの使用例もおなじみのように見えます:

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

したがって、タグfilterをこのエンティティの任意のクエリに適用できます。 これにより、ユーザーは必要なデータを正確に見つけるためのインターフェイスを強化できます。

4. 高度なタグ付け

単純なタグ付けの実装は、開始するのに最適な場所です。 しかし、1対多の関係のために、いくつかの問題が発生する可能性があります。

まず、重複するタグでいっぱいのテーブルができあがります。 これは小規模なプロジェクトでは問題になりませんが、大規模なシステムでは、数百万(または数十億)の重複エントリが発生する可能性があります。

また、Tagモデルはそれほど堅牢ではありません。 タグが最初に作成された日時を追跡したい場合はどうなりますか? 現在の実装では、それを行う方法はありません。

最後に、タグを複数のエンティティタイプ間で共有することはできません。 これにより、さらに多くの重複が発生し、システムのパフォーマンスに影響を与える可能性があります。

多対多の関係は、私たちの問題のほとんどを解決します。 使用方法を学ぶには @manytomany 注釈、チェックアウトこの記事 (これはこの記事の範囲を超えているため)。

5. 結論

タグ付けは、データをクエリできるシンプルでわかりやすい方法であり、Java Persistence APIと組み合わせると、簡単に実装できる強力なフィルタリング機能を利用できます。

単純な実装が常に最適であるとは限りませんが、その状況を解決するために取るべきルートを強調しました。

いつものように、この記事で使用されているコードは、GitHubにあります。