JPAによる簡単なタグ付けの実装

JPAを使用した簡単なタグ付けの実装

1. 概要

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

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

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

2. タグを追加する

First, we’re going to explore the most straightforward implementation of tagging: a List of Strings.次のようにエンティティに新しいフィールドを追加することで、タグを実装できます。

@Entity
public class Student {
    // ...

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

    // ...
}

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

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

This creates a One-To-Many relationship between our entity and tags!ここでは最も単純なバージョンのタグ付けを実装しています。 このため、重複するタグが多数存在する可能性があります(タグを持つエンティティごとに1つ)。 この概念については後で詳しく説明します。

3. クエリの作成

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

3.1. タグの検索

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

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

@Query("SELECT s FROM Student s JOIN s.tags t WHERE t = LOWER(:tag)")
List 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());

リポジトリ内の最初の学生をfull timeタグで取得します。 これはまさに私たちが望んでいたものです。

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

List 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 retrieveByNameFilterByTag(@Param("name") String name, @Param("tag") String tag);

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

使用例も見慣れたものになります。

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

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

4. 高度なタグ付け

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

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

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

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

Many-To-Many relationships will solve most of our problems.@manytomanyアノテーションの使用方法については、this articleを確認してください(これはこの記事の範囲を超えているため)。

5. 結論

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

単純な実装が常に最も適切であるとは限りませんが、そのような状況を解決するのに役立つルートを強調しました。

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