Перейти к основному содержимому

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

· 5 мин. чтения

1. Обзор

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

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

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

2. Добавление тегов

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

@Entity
public class Student {
// ...

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

// ...
}

Обратите внимание на использование аннотации ElementCollection в нашем новом поле. Поскольку мы работаем перед хранилищем данных, нам нужно сообщить ему, как хранить наши теги.

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

Это создает отношение «один ко многим» между нашей сущностью и тегами! Здесь мы реализуем самую простую версию тегов. Из-за этого у нас потенциально может быть много дублирующихся тегов (по одному для каждой сущности, у которой он есть). Мы поговорим об этой концепции позже.

3. Создание запросов

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

3.1. Поиск тегов

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

Вот как мы ищем сущность, содержащую определенный тег:

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

Мы вернем первого студента в репозиторий с тегом full time . Это именно то, что мы хотели.

Кроме того, мы можем расширить этот пример, чтобы показать, как фильтровать больший набор данных. Вот пример:

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

Мы видим, что этот запрос почти идентичен приведенному выше. Тег — это не что иное, как еще одно ограничение для использования в нашем запросе .

Наш пример использования также будет выглядеть знакомо:

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

Следовательно, мы можем применить фильтр тегов к любому запросу этой сущности. Это дает пользователю много возможностей в интерфейсе, чтобы найти точные данные, которые ему нужны.

4. Расширенная маркировка

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

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

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

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

Отношения «многие ко многим» решат большинство наших проблем. Чтобы узнать, как использовать аннотацию @manytomany , ознакомьтесь с этой статьей (поскольку это выходит за рамки данной статьи).

5. Вывод

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

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

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