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

Spring Data MongoDB — индексы, аннотации и конвертеры

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

1. Обзор

В этом руководстве мы рассмотрим некоторые основные функции Spring Data MongoDB — индексирование, общие аннотации и конвертеры.

2. Индексы

2.1. @Индексированный

Эта аннотация помечает поле как проиндексированное в MongoDB:

@QueryEntity
@Document
public class User {
@Indexed
private String name;

...
}

Теперь, когда поле имени проиндексировано, давайте посмотрим на индексы в оболочке MongoDB:

db.user.getIndexes();

Вот что мы получаем:

[
{
"v" : 1,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "test.user"
}
]

Мы можем быть удивлены, что нигде нет признаков поля имени !

Это связано с тем, что в Spring Data MongoDB 3.0 автоматическое создание индекса по умолчанию отключено .

Однако мы можем изменить это поведение, явно переопределив метод autoIndexCreation() в нашем MongoConfig :

public class MongoConfig extends AbstractMongoClientConfiguration {

// rest of the config goes here

@Override
protected boolean autoIndexCreation() {
return true;
}
}

Давайте снова проверим индексы в оболочке MongoDB:

[
{
"v" : 1,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "test.user"
},
{
"v" : 1,
"key" : {
"name" : 1
},
"name" : "name",
"ns" : "test.user"
}
]

Как мы видим, на этот раз у нас есть два индекса — один из них _id — который был создан по умолчанию из-за аннотации @Id , а второй — это наше поле имени .

В качестве альтернативы, если мы используем Spring Boot, мы можем установить для свойства spring.data.mongodb.auto-index-creation значение true .

2.2. Создайте индекс программно

Мы также можем создать индекс программно:

mongoOps.indexOps(User.class).
ensureIndex(new Index().on("name", Direction.ASC));

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

2.3. Составные индексы

MongoDB поддерживает составные индексы, в которых одна структура индекса содержит ссылки на несколько полей.

Давайте рассмотрим быстрый пример с использованием составных индексов:

@QueryEntity
@Document
@CompoundIndexes({
@CompoundIndex(name = "email_age", def = "{'email.id' : 1, 'age': 1}")
})
public class User {
//
}

Мы создали составной индекс с полями электронной почты и возраста . Давайте теперь проверим фактические индексы:

{
"v" : 1,
"key" : {
"email.id" : 1,
"age" : 1
},
"name" : "email_age",
"ns" : "test.user"
}

Обратите внимание, что поле DBRef не может быть помечено @Index — это поле может быть только частью составного индекса.

3. Общие аннотации

3.1. @транзиент

Как и следовало ожидать, эта простая аннотация исключает сохранение поля в базе данных:

public class User {

@Transient
private Integer yearOfBirth;
// standard getter and setter

}

Давайте вставим пользователя с полем настройки yearOfBirth :

User user = new User();
user.setName("Alex");
user.setYearOfBirth(1985);
mongoTemplate.insert(user);

Теперь, если мы посмотрим на состояние базы данных, мы увидим, что поле yearOfBirth не было сохранено:

{
"_id" : ObjectId("55d8b30f758fd3c9f374499b"),
"name" : "Alex",
"age" : null
}

Итак, если мы запросим и проверим:

mongoTemplate.findOne(Query.query(Criteria.where("name").is("Alex")), User.class).getYearOfBirth()

Результат будет нулевым .

3.2. @Поле

@Field указывает ключ, который будет использоваться для поля в документе JSON:

@Field("email")
private EmailAddress emailAddress;

Теперь emailAddress будет сохраняться в базе данных с использованием ключевого адреса электронной почты:

User user = new User();
user.setName("Brendan");
EmailAddress emailAddress = new EmailAddress();
emailAddress.setValue("a@gmail.com");
user.setEmailAddress(emailAddress);
mongoTemplate.insert(user);

И состояние базы:

{
"_id" : ObjectId("55d076d80bad441ed114419d"),
"name" : "Brendan",
"age" : null,
"email" : {
"value" : "a@gmail.com"
}
}

3.3. @PersistenceConstructor и @Value

@PersistenceConstructor помечает конструктор, даже защищенный пакетом, как основной конструктор, используемый логикой сохраняемости. Аргументы конструктора сопоставляются по имени со значениями ключа в извлеченном DBObject .

Давайте посмотрим на этот конструктор для нашего класса User :

@PersistenceConstructor
public User(String name, @Value("#root.age ?: 0") Integer age, EmailAddress emailAddress) {
this.name = name;
this.age = age;
this.emailAddress = emailAddress;
}

Обратите внимание на использование здесь стандартной аннотации Spring @Value . Именно с помощью этой аннотации мы можем использовать выражения Spring для преобразования значения ключа, полученного из базы данных, прежде чем оно будет использовано для создания объекта предметной области. Это очень мощная и очень полезная функция.

В нашем примере, если возраст не установлен, по умолчанию он будет равен 0 .

Давайте теперь посмотрим, как это работает:

User user = new User();
user.setName("Alex");
mongoTemplate.insert(user);

Наша база данных будет выглядеть:

{
"_id" : ObjectId("55d074ca0bad45f744a71318"),
"name" : "Alex",
"age" : null
}

Таким образом, поле age равно null , но когда мы запрашиваем документ и получаем возраст :

mongoTemplate.findOne(Query.query(Criteria.where("name").is("Alex")), User.class).getAge();

Результат будет 0.

4. Преобразователи

Давайте теперь взглянем на еще одну очень полезную функцию в Spring Data MongoDB — конвертеры, а конкретно на MongoConverter .

Это используется для обработки сопоставления всех типов Java с объектами DBObject при сохранении и запросе этих объектов.

У нас есть два варианта — мы можем работать либо с MappingMongoConverter , либо с SimpleMongoConverter в более ранних версиях (это устарело в Spring Data MongoDB M3, и его функциональность перенесена в MappingMongoConverter ) .

Или мы можем написать свой собственный конвертер. Для этого нам нужно реализовать интерфейс Converter и зарегистрировать реализацию в MongoConfig.

Давайте посмотрим на быстрый пример . Как мы видели здесь в некоторых выходных данных JSON, все объекты, сохраненные в базе данных, имеют поле _class , которое сохраняется автоматически. Однако, если мы хотим пропустить это конкретное поле во время сохранения, мы можем сделать это с помощью MappingMongoConverter .

Во-первых, вот реализация пользовательского конвертера:

@Component
public class UserWriterConverter implements Converter<User, DBObject> {
@Override
public DBObject convert(User user) {
DBObject dbObject = new BasicDBObject();
dbObject.put("name", user.getName());
dbObject.put("age", user.getAge());
if (user.getEmailAddress() != null) {
DBObject emailDbObject = new BasicDBObject();
emailDbObject.put("value", user.getEmailAddress().getValue());
dbObject.put("email", emailDbObject);
}
dbObject.removeField("_class");
return dbObject;
}
}

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

Теперь нам нужно зарегистрировать пользовательский конвертер:

private List<Converter<?,?>> converters = new ArrayList<Converter<?,?>>();

@Override
public MongoCustomConversions customConversions() {
converters.add(new UserWriterConverter());
return new MongoCustomConversions(converters);
}

Конечно, мы можем добиться того же результата и с конфигурацией XML, если нам нужно:

<bean id="mongoTemplate" 
class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongo" ref="mongo"/>
<constructor-arg ref="mongoConverter" />
<constructor-arg name="databaseName" value="test"/>
</bean>

<mongo:mapping-converter id="mongoConverter" base-package="org.foreach.converter">
<mongo:custom-converters base-package="com.foreach.converter" />
</mongo:mapping-converter>

Теперь, когда мы сохраняем нового пользователя:

User user = new User();
user.setName("Chris");
mongoOps.insert(user);

Результирующий документ в базе данных больше не содержит информации о классе:

{
"_id" : ObjectId("55cf09790bad4394db84b853"),
"name" : "Chris",
"age" : null
}

5. Вывод

В этом руководстве мы рассмотрели некоторые основные концепции работы с Spring Data MongoDB — индексирование, общие аннотации и конвертеры.

Реализацию всех этих примеров и фрагментов кода можно найти на GitHub .