1. 概述

本文将深入探讨 Spring Data MongoDB 的三大核心功能:索引管理、常用注解使用以及自定义转换器(Converter)。我们将基于 Spring Boot 环境进行演示,依赖会自动引入,无需手动添加。

这些内容在实际项目中非常实用,尤其在性能优化和数据映射定制方面,掌握它们能帮你少踩不少坑。✅


2. 索引(Indexes)

MongoDB 的查询性能高度依赖索引。Spring Data MongoDB 提供了多种方式来定义和管理索引。

2.1 @Indexed 注解

使用 @Indexed 可以标记某个字段需要创建索引:

@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() 方法:

public class MongoConfig extends AbstractMongoClientConfiguration {

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

或者更简单粗暴的方式,在 application.yml 中直接开启:

spring:
  data:
    mongodb:
      auto-index-creation: true

再次查看索引,就能看到 name 字段的索引已成功创建 ✅

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

🔗 官方文档参考:索引自动创建

2.2 编程式创建索引

除了注解,也可以通过代码动态创建索引,适用于运行时条件判断场景:

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

效果与 @Indexed 一致,但更灵活,比如可以结合环境变量或配置中心动态决定是否建索引。

2.3 复合索引(Compound Indexes)

MongoDB 支持对多个字段建立复合索引,提升多条件查询效率。

使用 @CompoundIndex@CompoundIndexes 定义:

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

📌 注意事项:

  • @Index 不能用于 DBRef 类型字段
  • DBRef 字段可以参与复合索引(如上例中的 email.id

3. 常用注解(Common Annotations)

Spring Data MongoDB 提供了一系列注解,用于控制实体映射行为。

3.1 @Transient

标记为 @Transient 的字段不会被持久化到数据库:

public class User {
    
    @Transient
    private Integer yearOfBirth;

    // getter/setter
}

插入数据示例:

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

数据库实际存储内容:

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

yearOfBirth 字段完全消失,查询时返回 null

类比 JPA 的 @Transient,行为一致,很好理解。

3.2 @Field

指定字段在 MongoDB 中的键名(key),实现 Java 字段与 DB 字段的映射:

@Field("email")
private EmailAddress emailAddress;

保存操作:

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

数据库结果:

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

✅ 成功将 emailAddress 映射为 email 键。

3.3 @PersistenceConstructor@Value

这两个注解组合使用,能实现构造器注入 + 表达式默认值,非常强大。

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

关键点:

  • @PersistenceConstructor 指定 Spring Data 用哪个构造器还原对象
  • @Value 支持 SpEL 表达式,这里表示:如果 age 为 null,则默认赋值为 0

验证逻辑:

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

数据库中 agenull

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

但查询时:

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

返回结果是 0

⚠️ 踩坑提醒:如果你的类有多个构造器,务必用 @PersistenceConstructor 明确指定,否则可能因反射选错构造器导致 NullPointerException


4. 转换器(Converters)

MongoConverter 是 Spring Data MongoDB 的核心组件,负责 Java 对象与 DBObject 之间的双向转换。

默认使用 MappingMongoConverter,它会自动处理类型映射、嵌套对象、集合等。

但有时我们需要更精细的控制,比如:不想保存 _class 字段

MongoDB 默认会在文档中添加 _class,用于反序列化时确定类型。但在某些场景下(如跨服务共享数据),我们希望去掉它。

自定义写入转换器

实现 Converter<S, T> 接口,定义从 UserDBObject 的转换逻辑:

@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"); // 关键:移除 _class
        return dbObject;
    }
}

注册自定义转换器

MongoConfig 中注册:

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

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

💡 注意:MongoCustomConversions 是 Spring Data 提供的注册入口,必须通过它才能生效。

XML 配置方式(可选)

如果你还在用 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>

验证效果

保存一个用户:

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

数据库结果(无 _class):

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

✅ 成功去除了类型元信息。


5. 总结

本文覆盖了 Spring Data MongoDB 的三大实用功能:

  • ✅ 索引管理:@Indexed、复合索引、编程式创建
  • ✅ 常用注解:@Transient@Field@PersistenceConstructor + @Value
  • ✅ 自定义转换器:控制序列化行为,如移除 _class

这些技巧在实际开发中非常有用,尤其在性能调优和数据模型定制方面。建议集合备用。

所有示例代码均可在 GitHub 获取:
🔗 https://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-data-mongodb


原始标题:Spring Data MongoDB - Indexes, Annotations and Converters