Spring Data MongoDB – Indexes, Annotations and Converters – Spring Data MongoDB – 索引、注解和转换器

最后修改: 2015年 8月 22日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial we’ll explore some of the core features of Spring Data MongoDB – indexing, common annotations and converters.

在本教程中,我们将探索Spring Data MongoDB的一些核心功能–索引、常用注解和转换器。

2. Indexes

2.索引

2.1. @Indexed

2.1.@Indexed

This annotation marks the field as indexed in MongoDB:

该注释将该字段标记为MongoDB中的索引

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

Now that the name field is indexed – let’s have a look at the indexes in MongoDB shell:

现在,name字段已经有了索引–让我们看一下MongoDB shell中的索引。

db.user.getIndexes();

Here’s what we get:

下面是我们得到的结果。

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

We may be surprised there’s no sign of the name field anywhere!

我们可能会惊讶地发现,在任何地方都没有姓名字段的踪迹!

This is because, as of Spring Data MongoDB 3.0, automatic index creation is turned off by default.

这是因为,从Spring Data MongoDB 3.0开始,自动索引创建默认是关闭的

We can, however, change that behavior by explicitly overriding autoIndexCreation() method in our MongoConfig:

但是,我们可以通过在MongoConfig中明确地覆盖autoIndexCreation()方法来改变这种行为。

public class MongoConfig extends AbstractMongoClientConfiguration {

    // rest of the config goes here

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

Let’s again check out the indexes in MongoDB shell:

让我们再次检查一下MongoDB shell中的索引。

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

As we can see, this time, we have two indexes – one of them is _id – which was created by default due to the @Id annotation and the second one is our name field.

正如我们所看到的,这一次,我们有两个索引–其中一个是_id–由于@Id注释而默认创建,第二个是我们的name字段。

Alternatively, if we use Spring Boot, we could set the spring.data.mongodb.auto-index-creation property to true.

另外,如果我们使用Spring Boot,我们可以将spring.data.mongodb.auto-index-creation属性设置为true

2.2. Create an Index Programmatically

2.2.以编程方式创建索引

We can also create an index programmatically:

我们也可以通过编程创建一个索引。

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

We’ve now created an index for the field name and the result will be the same as in the previous section.

现在我们已经为字段name创建了一个索引,结果将与上一节相同。

2.3. Compound Indexes

2.3.复合指数

MongoDB supports compound indexes, where a single index structure holds references to multiple fields.

MongoDB支持复合索引,其中一个索引结构持有对多个字段的引用。

Let’s see a quick example using compound indexes:

让我们看一个使用复合指数的快速例子。

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

We created a compound index with the email and age fields. Let’s now check out the actual indexes:

我们用emailage字段创建了一个复合索引。现在让我们检查一下实际的索引。

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

Note that a DBRef field cannot be marked with @Index – that field can only be part of a compound index.

请注意,DBRef字段不能被标记为@Index – 该字段只能是复合索引的一部分。

3. Common Annotations

3.常见的注释

3.1. @Transient

3.1.@Transient

As we’d expect, this simple annotation excludes the field from being persisted in the database:

正如我们所期望的,这个简单的注释排除了该字段在数据库中的持久化。

public class User {
    
    @Transient
    private Integer yearOfBirth;
    // standard getter and setter

}

Let’s insert user with the setting field yearOfBirth:

让我们插入具有设置字段yearOfBirth的用户。

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

Now if we look the state of database, we see that the filed yearOfBirth was not saved:

现在,如果我们看一下数据库的状态,我们会发现归档的yearOfBirth没有被保存。

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

So if we query and check:

因此,如果我们查询和检查。

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

The result will be null.

结果将是null

3.2. @Field

3.2.@Field

@Field indicates the key to be used for the field in the JSON document:

@Field表示JSON文档中的字段要使用的键。

@Field("email")
private EmailAddress emailAddress;

Now emailAddress will be saved in the database using the key email:

现在emailAddress将使用键email:保存在数据库中。

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

And the state of the database:

以及数据库的状况。

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

3.3. @PersistenceConstructor and @Value

3.3.@PersistenceConstructor@Value

@PersistenceConstructor marks a constructor, even one that’s package protected, to be the primary constructor used by the persistence logic. The constructor arguments are mapped by name to the key values in the retrieved DBObject.

@PersistenceConstructor标记一个构造函数,即使是被包保护的构造函数,也是持久化逻辑使用的主要构造函数。构造函数参数通过名称被映射到检索的DBObject中的键值。

Let’s look at this constructor for our User class:

让我们看看我们的User类的这个构造函数。

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

Notice the use of the standard Spring @Value annotation here. It’s with the help of this annotation that we can use the Spring Expressions to transform a key’s value retrieved from the database before it is used to construct a domain object. That is a very powerful and highly useful feature here.

注意这里使用了标准的Spring @Value注解。正是在这个注解的帮助下,我们可以使用Spring表达式来转换从数据库中获取的键值,然后再用于构造域对象。这是一个非常强大和非常有用的功能。

In our example if age isn’t set, it’ll be set to 0 by default.

在我们的例子中,如果age没有被设置,它将被默认设置为0

Let’s now see how it works:

现在让我们看看它是如何工作的。

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

Our database will look:

我们的数据库将查看。

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

So the age field is null, but when we query the document and retrieve age:

因此,age字段是null,但当我们查询文档并检索age时。

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

The result will be 0.

结果将是0。

4. Converters

4.转换器

Let’s now take a look at another very useful feature in Spring Data MongoDB – converters, and specifically at the MongoConverter.

现在让我们来看看Spring Data MongoDB中另一个非常有用的功能–转换器,特别是MongoConverter

This is used to handle the mapping of all Java types to DBObjects when storing and querying these objects.

在存储和查询这些对象时,这被用来处理所有Java类型到DBObjects的映射。

We have two options – we can either work with MappingMongoConverter – or SimpleMongoConverter in earlier versions (this was deprecated in Spring Data MongoDB M3 and its functionality has been moved into MappingMongoConverter).

我们有两个选择–我们可以使用MappingMongoConverter–或早期版本的SimpleMongoConverter(这在Spring Data MongoDB M3中被废弃,其功能已被移至MappingMongoConverter

Or we can write our own custom converter. To do that, we would need to implement the Converter interface and register the implementation in MongoConfig.

或者我们可以编写我们自己的自定义转换器。要做到这一点,我们需要实现Converter 接口,并在MongoConfig.中注册该实现。

Let’s look at a quick example. As we’ve seen in some of the JSON output here, all objects saved in a database have the field _class which is saved automatically. If however we’d like to skip that particular field during persistence, we can do that using a MappingMongoConverter.

让我们看一下一个快速的例子。正如我们在这里的一些JSON输出中所看到的,所有保存在数据库中的对象都有_class字段,它是自动保存的。然而,如果我们想在持久化过程中跳过该特定字段,我们可以使用MappingMongoConverter来实现。

First – here’s the custom converter implementation:

首先–这里是自定义转换器的实现。

@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;
    }
}

Notice how we can easily hit the goal of not persisting _class by specifically removing the field directly here.

请注意,我们可以很容易地达到不持久化_class的目的,在这里直接删除这个字段。

Now we need to register the custom converter:

现在我们需要注册自定义转换器。

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

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

We can of course achieve the same result with XML configuration as well, if we need to:

当然,如果需要的话,我们也可以用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.baeldung.converter">
    <mongo:custom-converters base-package="com.baeldung.converter" />
</mongo:mapping-converter>

Now, when we save a new user:

现在,当我们保存一个新的用户。

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

The resulting document in the database no longer contains the class information:

数据库中产生的文件不再包含类的信息。

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

5. Conclusion

5.结论

In this tutorial we’ve covered some core concepts of working with Spring Data MongoDB – indexing, common annotations and converters.

在本教程中,我们已经涵盖了与Spring Data MongoDB合作的一些核心概念–索引、常用注解和转换器。

The implementation of all these examples and code snippets can be found over on GitHub.

所有这些例子和代码片段的实现都可以在GitHub上找到。