40

默认MappingMongoConverter为数据库中的每个对象添加一个自定义类型键(“_class”)。所以,如果我创建一个人:

package my.dto;
public class Person {
    String name;
    public Person(String name) {
        this.name = name; 
    }
}

并将其保存到数据库:

MongoOperations ops = new MongoTemplate(new Mongo(), "users");
ops.insert(new Person("Joe"));

mongo 中的结果对象将是:

{ "_id" : ObjectId("4e2ca049744e664eba9d1e11"), "_class" : "my.dto.Person", "name" : "Joe" }

问题:

  1. 将 Person 类移动到不同的命名空间有什么影响?

  2. 是否可以不使用“_class”键污染对象;不为 Person 类编写唯一的转换器?

4

12 回答 12

32

所以这就是故事:我们默认添加类型作为某种暗示实际实例化的类。由于您必须通过管道输入一种类型来读取文档MongoTemplate,因此有两种可能的选择:

  1. 您提交可以分配实际存储类型的类型。在这种情况下,我们考虑存储类型,将其用于对象创建。这里的经典示例是进行多态查询。假设你有一个抽象类Contact和你的Person. 然后您可以查询Contacts,我们基本上必须确定要实例化的类型。
  2. 另一方面,如果您传入一个完全不同的类型,我们只需编组为该给定类型,而不是实际存储在文档中的类型。这将涵盖您的问题,如果您移动类型会发生什么。

您可能有兴趣观看此票证,该票证涵盖了某种可插入的类型映射策略,以将类型信息转换为实际类型。这可以简单地用于节省空间的目的,因为您可能希望将长限定类名减少为几个字母的散列。它还允许更复杂的迁移场景,您可能会找到由另一个数据存储客户端生成的完全任意类型键并将它们绑定到 Java 类型。

于 2011-07-25T19:08:39.263 回答
17

这是我的注释,它有效。

@Configuration
public class AppMongoConfig {

    public @Bean
    MongoDbFactory mongoDbFactory() throws Exception {
        return new SimpleMongoDbFactory(new Mongo(), "databasename");
    }

    public @Bean
    MongoTemplate mongoTemplate() throws Exception {

        //remove _class
        MappingMongoConverter converter = new MappingMongoConverter(mongoDbFactory(), new MongoMappingContext());
        converter.setTypeMapper(new DefaultMongoTypeMapper(null));

        MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory(), converter);

        return mongoTemplate;

    }

}
于 2012-09-12T13:54:53.993 回答
7

如果您想_class默认禁用属性,但保留指定类的多态性,您可以通过配置显式定义_class(可选)字段的类型:

@Bean
public MongoTemplate mongoTemplate() throws Exception {
    Map<Class<?>, String> typeMapperMap = new HashMap<>();
    typeMapperMap.put(com.acme.domain.SomeDocument.class, "role");

    TypeInformationMapper typeMapper1 = new ConfigurableTypeInformationMapper(typeMapperMap);

    MongoTypeMapper typeMapper = new DefaultMongoTypeMapper(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, Arrays.asList(typeMapper1));
    MappingMongoConverter converter = new MappingMongoConverter(mongoDbFactory(), new MongoMappingContext());
    converter.setTypeMapper(typeMapper);

    MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory(), converter);
    return mongoTemplate;
}

这将只为指定的实体保留_class字段(或您想在构造器中命名的任何内容)。

您还可以TypeInformationMapper根据注释编写自己的示例。如果你注释你的文档,@DocumentType("aliasName")你将通过保持类的别名来保持多态性。

我已经在我的博客上简要解释过,但这里有一些快速代码: https ://gist.github.com/athlan/6497c74cc515131e1336

于 2015-09-20T21:33:50.413 回答
5
<mongo:mongo host="hostname" port="27017">
<mongo:options
...options...
</mongo:mongo>
<mongo:db-factory dbname="databasename" username="user" password="pass"                     mongo-ref="mongo"/>
<bean id="mongoTypeMapper"     class="org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper">
<constructor-arg name="typeKey"><null/></constructor-arg>
</bean>
<bean id="mongoMappingContext"      class="org.springframework.data.mongodb.core.mapping.MongoMappingContext" />
<bean id="mongoConverter"     class="org.springframework.data.mongodb.core.convert.MappingMongoConverter">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
<constructor-arg name="mappingContext" ref="mongoMappingContext" />
<property name="typeMapper" ref="mongoTypeMapper"></property>
</bean>
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
<constructor-arg name="mongoConverter" ref="mongoConverter" />
<property name="writeResultChecking" value="EXCEPTION" /> 
</bean>
于 2013-06-15T06:07:09.847 回答
4

虽然 Mkyong 的回答仍然有效,但我想添加我的解决方案版本,因为不推荐使用一些位并且可能处于清理的边缘。

例如:MappingMongoConverter(mongoDbFactory(), new MongoMappingContext())被弃用,赞成new MappingMongoConverter(dbRefResolver, new MongoMappingContext());SimpleMongoDbFactory(new Mongo(), "databasename");赞成new SimpleMongoDbFactory(new MongoClient(), database);

因此,我没有弃用警告的最终工作答案是:

@Configuration
public class SpringMongoConfig {

    @Value("${spring.data.mongodb.database}")
    private String database;

    @Autowired
    private MongoDbFactory mongoDbFactory;

    public @Bean MongoDbFactory mongoDBFactory() throws Exception {
        return new SimpleMongoDbFactory(new MongoClient(), database);
    }

    public @Bean MongoTemplate mongoTemplate() throws Exception {

        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);

        // Remove _class
        MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext());
        converter.setTypeMapper(new DefaultMongoTypeMapper(null));

        return new MongoTemplate(mongoDBFactory(), converter);

    }

}

希望这可以帮助那些希望有一个没有弃用警告的干净课程的人。

于 2017-06-11T05:05:29.080 回答
3

这是我的单行解决方案:

@Bean 
public MongoTemplate mongoTemplateFraud() throws UnknownHostException {

  MongoTemplate mongoTemplate = new MongoTemplate(getMongoClient(), dbName);
  ((MappingMongoConverter)mongoTemplate.getConverter()).setTypeMapper(new DefaultMongoTypeMapper(null));//removes _class
  return mongoTemplate;
}
于 2016-07-15T08:09:24.210 回答
2

我在这个问题上挣扎了很长时间。我遵循了mkyong的方法,但是当我引入一个LocalDate属性(Java 8 中的任何 JSR310 类)时,我收到了以下异常:

org.springframework.core.convert.ConverterNotFoundException:
No converter found capable of converting from type [java.time.LocalDate] to type [java.util.Date]

相应的转换器org.springframework.format.datetime.standard.DateTimeConverters是 Spring 4.1 的一部分,在 Spring Data MongoDB 1.7 中被引用。即使我使用较新的版本,转换器也没有加入。

解决方案是使用现有的MappingMongoConverter并且只提供一个新DefaultMongoTypeMapper的(来自 mkyong 的代码正在评论中):

@Configuration
@EnableMongoRepositories
class BatchInfrastructureConfig extends AbstractMongoConfiguration
{
    @Override
    protected String getDatabaseName() {
        return "yourdb"
    }

    @Override
    Mongo mongo() throws Exception {
        new Mongo()
    }

    @Bean MongoTemplate mongoTemplate()
    {
        // overwrite type mapper to get rid of the _class column
//      get the converter from the base class instead of creating it
//      def converter = new MappingMongoConverter(mongoDbFactory(), new MongoMappingContext())
        def converter = mappingMongoConverter()
        converter.typeMapper = new DefaultMongoTypeMapper(null)

        // create & return template
        new MongoTemplate(mongoDbFactory(), converter)
    }

总结一下:

  • 延长AbstractMongoConfiguration
  • 注释EnableMongoRepositories
  • mongoTemplate从基类获取转换器中,这确保了类型转换类已注册
于 2016-03-04T15:41:59.440 回答
2

对于 Spring Boot 2.3.0.RELEASE,它更容易,只需覆盖方法mongoTemplate,它已经拥有设置类型映射器所需的所有东西。请参见以下示例:

@Configuration
@EnableMongoRepositories(
// your package ...
)
public class MongoConfig extends AbstractMongoClientConfiguration {

    // .....

    @Override
    public MongoTemplate mongoTemplate(MongoDatabaseFactory databaseFactory, MappingMongoConverter converter) {
        // remove __class field from mongo
        converter.setTypeMapper(new DefaultMongoTypeMapper(null));
        return super.mongoTemplate(databaseFactory, converter);
    }

    // .....

}
于 2020-07-31T08:37:18.047 回答
1
@Configuration
public class MongoConfig {

    @Value("${spring.data.mongodb.database}")
    private String database;

    @Value("${spring.data.mongodb.host}")
    private String host;

    public @Bean MongoDbFactory mongoDbFactory() throws Exception {
        return new SimpleMongoDbFactory(new MongoClient(host), database);
    }

    public @Bean MongoTemplate mongoTemplate() throws Exception {

        MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory()),
                new MongoMappingContext());
        converter.setTypeMapper(new DefaultMongoTypeMapper(null));

        MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory(), converter);

        return mongoTemplate;

    }

}
于 2019-07-13T10:50:59.530 回答
0

您只需要在更改类型映射器时将 @TypeAlias 注释添加到类定义中

于 2018-05-10T11:22:08.340 回答
0

上面的正确答案似乎是使用了许多已弃用的依赖项。例如,如果您检查代码,它会提到在最新的 Spring 版本中已弃用的 MongoDbFactory。如果你碰巧在 2020 年将 MongoDB 与 Spring-Data 一起使用,那么这个解决方案似乎更老了。要获得即时结果,请检查此代码片段。工作 100%。只需创建一个新的 AppConfig.java 文件并粘贴此代码块。您将看到“_class”属性从 MongoDB 文档中消失。

package "Your Package Name";

import org.apache.naming.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;  
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;

@Configuration
public class AppConfig {

@Autowired
MongoDatabaseFactory mongoDbFactory;
@Autowired
MongoMappingContext mongoMappingContext;

@Bean
public MappingMongoConverter mappingMongoConverter() {

    DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
    MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
    converter.setTypeMapper(new DefaultMongoTypeMapper(null));

    return converter;
    }

}
于 2021-01-26T10:40:36.277 回答
0

我已经尝试了上面的解决方案,其中一些不能与审计结合使用,而且似乎没有一个正确设置MongoCustomConversions

对我有用的解决方案如下

@Configuration
public class MongoConfig {

    @Bean
    public MappingMongoConverter mappingMongoConverterWithCustomTypeMapper(
            MongoDatabaseFactory factory,
            MongoMappingContext context,
            MongoCustomConversions conversions) {
        DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
        MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context);
        mappingConverter.setCustomConversions(conversions);

        /**
         * replicate the way that Spring
         * instantiates a {@link DefaultMongoTypeMapper}
         * in {@link MappingMongoConverter#MappingMongoConverter(DbRefResolver, MappingContext)}
         */
        CustomMongoTypeMapper customTypeMapper = new CustomMongoTypeMapper(
                context,
                mappingConverter::getWriteTarget);
        mappingConverter.setTypeMapper(customTypeMapper);
        return mappingConverter;
    }
}

public class CustomMongoTypeMapper extends DefaultMongoTypeMapper {

    public CustomMongoTypeMapper(
            MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext,
            UnaryOperator<Class<?>> writeTarget) {
        super(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, mappingContext, writeTarget);
    } 

    @Override
    public TypeInformation<?> readType(Bson source) {

    /**
     * do your conversion here, and eventually return
     */
    return super.readType(source);
    }
}

作为替代方案,您可以使用 aBeanPostProcessor来检测 a 的创建mappingMongoConverter,并在那里添加您的转换器。

就像是

public class MappingMongoConverterHook implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if ("mappingMongoConverter" == beanName) {
            ((MappingMongoConverter) bean).setTypeMapper(new CustomMongoTypeMapper());
        }
        return bean;
    }
}
于 2021-05-06T21:35:00.920 回答