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

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

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

1. Обзор

В этом руководстве мы рассмотрим простую реализацию тегов с использованием Java и MongoDB.

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

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

Давайте посмотрим, как мы можем реализовать этот сценарий.

2. Зависимость

Чтобы запросить базу данных, нам нужно будет включить зависимость драйвера MongoDB в наш pom.xml :

<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.6.3</version>
</dependency>

Актуальную версию этой зависимости можно найти здесь .

3. Модель данных

Прежде всего, давайте начнем с планирования того, как должен выглядеть почтовый документ.

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

Мы будем хранить теги внутри массива, так как пост, вероятно, будет иметь более одного:

{
"_id" : "Java 8 and MongoDB",
"author" : "Donato Rimenti",
"tags" : ["Java", "MongoDB", "Java 8", "Stream API"]
}

Мы также создадим соответствующий класс модели Java:

public class Post {
private String title;
private String author;
private List<String> tags;

// getters and setters
}

4. Обновление тегов

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

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

public boolean addTags(String title, List<String> tags) {
UpdateResult result = collection.updateOne(
new BasicDBObject(DBCollection.ID_FIELD_NAME, title),
Updates.addEachToSet(TAGS_FIELD, tags));
return result.getModifiedCount() == 1;
}

public boolean removeTags(String title, List<String> tags) {
UpdateResult result = collection.updateOne(
new BasicDBObject(DBCollection.ID_FIELD_NAME, title),
Updates.pullAll(TAGS_FIELD, tags));
return result.getModifiedCount() == 1;
}

Мы использовали метод addEachToSet вместо push для добавления, так что если теги уже есть, мы не будем добавлять их снова.

Обратите также внимание, что оператор addToSet также не будет работать, поскольку он добавит новые теги в виде вложенного массива, а это не то, что нам нужно.

Другой способ, которым мы можем выполнять наши обновления, — через оболочку Mongo. Например, давайте обновим сообщение JUnit5 с помощью Java. В частности, мы хотим добавить теги Java и J Unit5 и убрать теги Spring и REST :

db.posts.updateOne(
{ _id : "JUnit 5 with Java" },
{ $addToSet :
{ "tags" :
{ $each : ["Java", "JUnit5"] }
}
});

db.posts.updateOne(
{_id : "JUnit 5 with Java" },
{ $pull :
{ "tags" : { $in : ["Spring", "REST"] }
}
});

5. Запросы

И последнее, но не менее важное: давайте рассмотрим некоторые из наиболее распространенных запросов, которые могут нас заинтересовать при работе с тегами. Для этой цели мы воспользуемся, в частности, тремя операторами массива:

  • $in — возвращает документы, в которых поле содержит любое значение из указанного массива
  • $nin – возвращает документы, в которых поле не содержит ни одного значения указанного массива
  • $all — возвращает документы, в которых поле содержит все значения указанного массива

Мы определим три метода для запроса сообщений относительно набора тегов, переданных в качестве аргументов . Они будут возвращать сообщения, которые соответствуют хотя бы одному тегу, всем тегам и ни одному тегу. Мы также создадим метод сопоставления для обработки преобразования между документом и нашей моделью с помощью Stream API Java 8:

public List<Post> postsWithAtLeastOneTag(String... tags) {
FindIterable<Document> results = collection
.find(Filters.in(TAGS_FIELD, tags));
return StreamSupport.stream(results.spliterator(), false)
.map(TagRepository::documentToPost)
.collect(Collectors.toList());
}

public List<Post> postsWithAllTags(String... tags) {
FindIterable<Document> results = collection
.find(Filters.all(TAGS_FIELD, tags));
return StreamSupport.stream(results.spliterator(), false)
.map(TagRepository::documentToPost)
.collect(Collectors.toList());
}

public List<Post> postsWithoutTags(String... tags) {
FindIterable<Document> results = collection
.find(Filters.nin(TAGS_FIELD, tags));
return StreamSupport.stream(results.spliterator(), false)
.map(TagRepository::documentToPost)
.collect(Collectors.toList());
}

private static Post documentToPost(Document document) {
Post post = new Post();
post.setTitle(document.getString(DBCollection.ID_FIELD_NAME));
post.setAuthor(document.getString("author"));
post.setTags((List<String>) document.get(TAGS_FIELD));
return post;
}

Опять же, давайте также взглянем на эквивалентные запросы оболочки . Мы получим три разные коллекции сообщений, соответственно помеченные MongoDB или Stream API, помеченные как Java 8 , так и JUnit 5 и не помеченные Groovy или Scala :

db.posts.find({
"tags" : { $in : ["MongoDB", "Stream API" ] }
});

db.posts.find({
"tags" : { $all : ["Java 8", "JUnit 5" ] }
});

db.posts.find({
"tags" : { $nin : ["Groovy", "Scala" ] }
});

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

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

Если вы заинтересованы в дальнейшем изучении MongoDB, мы рекомендуем вам прочитать эту вводную статью .

Как всегда, весь код в примере доступен в проекте Github .