Spring Data MongoDB索引、注解和转换器

2023/05/18

1. 概述

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

2. 索引

2.1 @Indexed

此注解将字段标记为在MongoDB中已编入索引

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

    // ...
}

现在name字段已被索引-让我们在MongoDB shell中获取所有索引:

db.user.getIndexes();

这是我们得到的:

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

我们可能会感到惊讶,到处都没有name字段的迹象!

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

但是,我们可以通过在MongoConfig中显式覆盖autoIndexCreation()方法来更改该行为:

public class MongoConfig extends AbstractMongoClientConfiguration {

    // rest of the config goes here

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

让我们再次在MongoDB shell中检查索引:

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

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

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

2.2 以编程方式创建索引

我们还可以通过编程方式创建索引:

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

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

2.3 复合索引

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

让我们看一个使用复合索引的简单示例:

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

我们创建了一个包含电子email和age字段的复合索引。现在让我们检查一下实际的索引:

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

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

3. 常用注解

3.1 @Transient

顾名思义,这个简单的注解排除了字段在数据库中的持久化:

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

结果将为null。

3.2 @Field

@Field表示要用于JSON文档中的字段的键:

@Field("email")
private EmailAddress emailAddress;

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

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表达式来转换从数据库中检索到的键值,然后再将其用于构造域对象。这是一个非常强大且非常有用的功能。

在我们的示例中,如果未设置age,则默认情况下它将设置为0。

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

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

我们的数据库为:

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

所以age字段为null,但是当我们查询文档并检索age时:

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

结果将为0。

4. 转换器

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

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

我们有两个选择-我们可以使用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="cn.tuyucheng.taketoday.converter">
    <mongo:custom-converters base-package="cn.tuyucheng.taketoday.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上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章