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。