MongoDB 集合可以包含代表各种类型实例的文档。如果你存储了一个类的层次结构,或者有一个具有 Object 类型属性的类,那么这个功能就很有用。在后一种情况下,当检索对象时,必须正确地读入该属性内的值。
为了实现这一点,MappingMongoConverter 使用了 MongoTypeMapper 接口的默认实现类 DefaultMongoTypeMapper。它的默认行为是在文档中的 _class 字段下存储完全限定的类名(如:{ _class:'com.hxstrive.Test' })。
类型提示是为顶级文档以及每个值编写的(如果它是一个复杂类型和声明的属性类型的子类型)。下面的例子(在结尾有一个 JSON 表示)展示了映射是如何工作的:
Java 代码:
(1)抽象类 Contact,什么也不做,代码如下:
package com.hxstrive.springdata.mongodb.entity; public abstract class Contact { //... }
(2)继承 Contact 抽象类的 Person 类,定义了用户姓名、年龄,代码如下:
package com.hxstrive.springdata.mongodb.entity; import lombok.Builder; import lombok.Data; import lombok.ToString; @Data @Builder @ToString public class Person extends Contact { /** 自动映射到 MongoDB 的 _id 字段 */ private String id; private String name; private int age; }
(3)编写一个 Sample 类,成员变量引用 Contact 类,代码如下:
package com.hxstrive.springdata.mongodb.entity; import lombok.Builder; import lombok.Data; @Data @Builder public class Sample { // 必须是抽象类,不能是具体类 Person // 否则,保存到 MongDB 中的 JSON 的 contact 中不会添加 _class 字段 private Contact Contact; private String title; }
(4)客户端代码,如下:
package com.hxstrive.springdata.mongodb; import com.hxstrive.springdata.mongodb.entity.Person; import com.hxstrive.springdata.mongodb.entity.Sample; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; @SpringBootTest class MongoTypeMapperTest { @Autowired private MongoTemplate mongoTemplate; @Test void contextLoads() { Person p = Person.builder().name("Joe").age(34).build(); Sample sample = Sample.builder().title("MongoTypeMapper Demo").person(p).build(); // 保存到 MongoDB mongoTemplate.save(sample); } }
MongoDB 内部 JSON 表示:
{ "_id" : ObjectId("63a925e2a19ac53e1d21f6b8"), "contact" : { "name" : "Joe", "age" : NumberInt(34), "_class" : "com.hxstrive.springdata.mongodb.entity.Person" }, "title" : "MongoTypeMapper Demo", "_class" : "com.hxstrive.springdata.mongodb.entity.Sample" }
Spring Data MongoDB 将类型信息存储为实际根类以及嵌套类型的最后一个字段(因为它是复杂的并且是 Contact 的子类型)。因此,如果您现在使用mongoTemplate.findAll(Object.class, "sample"),您可以发现存储的文档是一个 sample 实例。您还可以发现 value 属性实际上是一个 Person。
如果你想避免把整个 Java 类的完全限定名称写成类型信息(_class),而是想使用一个键(简短的名字),你可以在实体类上使用 @TypeAlias 注解。如果你需要进一步定制映射,可以看看 TypeInformationMapper 接口。该接口的实例可以在 DefaultMongoTypeMapper 上配置。反过来,也可以在 MappingMongoConverter 上配置。
// 定义类型别名 @TypeAlias("pers") class Person { //... }
注意:此时生成的文档中的 _class 字段值为 “pers”,而不在是 “com.hxstrive.springdata.mongodb.entity.Person”。
MongoDB 内部 JSON 表示示例:
{ "_id" : ObjectId("63a92670fda8272641e51459"), "contact" : { "name" : "Joe", "age" : NumberInt(34), "_class" : "pers" }, "title" : "MongoTypeMapper Demo", "_class" : "com.hxstrive.springdata.mongodb.entity.Sample" }
警告
类型别名只有在映射上下文知道实际类型的情况下才起作用。所需的实体元数据要么在第一次保存时确定,要么必须通过配置的初始实体集提供。默认情况下,配置类会扫描基础包以寻找潜在的候选者。
下面例子展示如何通过 @Configuration 配置类手动将 Sample 类添加到上下文,代码如下:
package com.hxstrive.springdata.mongodb.config; import com.hxstrive.springdata.mongodb.entity.Sample; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration; import java.util.Set; @Configuration public class AppConfig extends AbstractMongoClientConfiguration { @Override protected String getDatabaseName() { return "test"; } // 扫描映射基础包中标注了 @Document 的类。 // 默认情况下,它扫描 getMappingBasePackages() 返回的所有包中的实体。 @Override protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException { Set<Class<?>> superSet = super.getInitialEntitySet(); superSet.add(Sample.class); return superSet; } }
以下示例显示如何在 MappingMongoConverter 中配置自定义 MongoTypeMapper:
(1)自定义的 MongoTypeMapper
class CustomMongoTypeMapper extends DefaultMongoTypeMapper { // implement custom type mapping here }
(2)配置类
package com.hxstrive.springdata.mongodb.config; import com.hxstrive.springdata.mongodb.constom.CustomMongoTypeMapper; import com.hxstrive.springdata.mongodb.entity.Person; import com.hxstrive.springdata.mongodb.entity.Sample; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoCustomConversions; import org.springframework.data.mongodb.core.convert.MongoTypeMapper; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import java.util.Set; @Configuration public class AppConfig extends AbstractMongoClientConfiguration { @Override protected String getDatabaseName() { return "test"; } /** * 重写父类方法,重写了 MappingMongoConverter 的 bean 定义 * @param databaseFactory * @param customConversions * @param mappingContext * @return */ @Bean @Override public MappingMongoConverter mappingMongoConverter(MongoDatabaseFactory databaseFactory, MongoCustomConversions customConversions, MongoMappingContext mappingContext) { MappingMongoConverter mmc = super.mappingMongoConverter(databaseFactory, customConversions, mappingContext); // 指定自定义的 MongoTypeMapper mmc.setTypeMapper(customTypeMapper()); return mmc; } /** * 自定义的 MongoTypeMapper * @return */ @Bean public MongoTypeMapper customTypeMapper() { return new CustomMongoTypeMapper(); } }
当然,我们也可以通过 XML 文件的形式进行配置,例如:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mongo="http://www.springframework.org/schema/data/mongo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd"> <!-- 定义 MongoClient --> <mongo:mongo-client id="mongoClient" host="localhost" port="27017"/> <!-- 定义 MongoDatabaseFactory --> <mongo:db-factory id="mongoDbFactory" dbname="test" mongo-client-ref="mongoClient"/> <!-- 配置自定义的MongoTypeMapper --> <mongo:mapping-converter type-mapper-ref="customMongoTypeMapper"/> <!-- 定义自定义 MongoTypeMapper --> <bean name="customMongoTypeMapper" class="com.hxstrive.springdata.mongodb.constom.CustomMongoTypeMapper"/> <!-- 定义 MongoTemplate --> <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate"> <constructor-arg ref="mongoClient"/> <constructor-arg name="databaseName" value="test"/> </bean> </beans>
请注意,前面的例子扩展了 AbstractMongoClientConfiguration 类,并重写了 MappingMongoConverter 的 bean 定义,我们在这里配置了我们的自定义 MongoTypeMapper。