Простая реализация тегов с JPA

Простая реализация тегов с помощью JPA

1. обзор

Пометка - это стандартный шаблон проектирования, который позволяет нам категоризировать и фильтровать элементы в нашей модели данных.

В этой статье мы реализуем теги с помощью Spring и JPA. Мы будем использовать Spring Data для выполнения этой задачи. Кроме того, эта реализация будет полезна, если вы хотите использовать Hibernate.

Это вторая статья из серии о внедрении тегов. Чтобы узнать, как реализовать это с помощью 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 в нашем новом поле. Поскольку мы работаем перед хранилищем данных, нам нужно указать ему, как хранить наши теги.

Если бы мы не добавляли аннотации, они бы хранились в одном большом двоичном объекте, с которым было бы труднее работать. Эта аннотация создает другую таблицу с именемSTUDENT_TAGS (т.е.<entity> _), которая сделает наши запросы более надежными.

This creates a One-To-Many relationship between our entity and tags! Здесь мы реализуем самую простую версию тегов. Из-за этого у нас потенциально может быть много повторяющихся тегов (по одному на каждую сущность, у которой он есть). Мы поговорим об этом подробнее позже.

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. Фильтрация запроса

Еще одно полезное приложение нашей простой маркировки - это применение фильтра к конкретному запросу. В то время как предыдущие примеры также позволяли нам выполнять фильтрацию, они работали со всеми данными в нашей таблице.

Поскольку нам также необходимо фильтровать другие поисковые запросы, давайте рассмотрим пример:

@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. Расширенная маркировка

Наша простая реализация тегов - отличное место для начала. Но из-за отношения «один ко многим» мы можем столкнуться с некоторыми проблемами.

Во-первых, у нас получится таблица, полная повторяющихся тегов. Это не проблема для небольших проектов, но в более крупных системах могут возникнуть миллионы (или даже миллиарды) повторяющихся записей.

Кроме того, наша модельTag не очень надежна. Что если мы хотим отслеживать, когда тег был изначально создан? В нашей текущей реализации у нас нет способа сделать это.

Наконец, мы не можем разделять нашиtagsмежду несколькими типами сущностей. Это может привести к еще большему дублированию, которое может повлиять на производительность нашей системы.

Many-To-Many relationships will solve most of our problems. Чтобы узнать, как использовать аннотацию@manytomany, ознакомьтесь сthis article (поскольку это выходит за рамки данной статьи).

5. Заключение

Добавление тегов - это простой и понятный способ запроса данных, а в сочетании с Java Persistence API мы получили мощную функцию фильтрации, которую легко реализовать.

Хотя простая реализация не всегда может быть наиболее подходящей, мы выделили маршруты, которые помогут решить эту ситуацию.

Как всегда, код, использованный в этой статье, можно найти наover on GitHub.