Spring Data MongoDb: MappingMongoConverter删除_class

44

默认的 MappingMongoConverter 会向数据库中的每个对象添加一个自定义类型键 ("_class")。所以,如果我创建了一个 Person:

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. 是否可能在不为Person类编写独特的转换器的情况下,使对象不会被"_class"键污染?


1
那么这是怎么回事呢?有没有办法防止“_class”字段被存储在MongoDB中? - hodgesz
13个回答

33

故事是这样的:我们默认添加类型,作为某种提示哪个类实际上要实例化。由于您无论如何都必须通过 MongoTemplate 输入一个类型来将文档读入,因此有两种可能性:

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

您可能会对观看此票感兴趣,其中涵盖了一些可插拔的类型映射策略,将类型信息转换为实际类型。这可以简单地用于节省空间,因为您可能希望将长限定类名减少为几个字母的哈希值。它还可以允许更复杂的迁移方案,其中您可能会发现完全任意的类型键由其他数据存储客户端生成,并将这些绑定到Java类型。


1
谢谢您的回答。将类型提取到配置中,而不是与对象一起保留,这样做是否有意义?例如,在代码中提供映射:(converter.configure(Contact.class, Person.class))。 - Yuriy Nemtsov
2
Oliver,有没有简单的方法可以在1.0GA中删除_class?这个现在不起作用了。最简单的方法似乎是:((MappingMongoConverter)template.getConverter()).setTypeMapper(new DefaultMongoTypeMapper(null));。但这很丑陋而且不正确... - Shcheklein
1
“doesn't work”是什么意思?如果您通过代码或XML配置正确地预先配置了MappingMongoConverter,则无需执行转换作业。 - Oliver Drotbohm

18

这是我的注解,它有效。

@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;

    }

}

这里有一个警告:此代码会删除所有类型转换代码。例如,Spring Data 将不再能够转换(和存储)LocalDate 属性。 - ChrLipp
1
@mkyong,你的代码中有一些过时的片段。我已经添加了一个可以消除过时警告的可行答案。你介意在这里和你的博客这里更新你的答案吗?谢谢。 - harshavmb
重要的增强功能是:不要创建全新的MongoMappingContext,最好是将其注入,否则可能会出现问题,例如因为该映射上下文未使用应用程序上下文进行初始化。这就是我在评估SpEL表达式时遇到问题的根源。 - Constantino Cronemberger

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


5

虽然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);

    }

}

希望这能帮助那些想要一个没有弃用警告的干净类的人。

字段注入不被推荐。SimpleMongoDbFactory现在已经过时。 - Kamil Witkowski

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>

4

对于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);
    }

    // .....

}

3

我曾经为这个问题苦苦挣扎。我按照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中获取来自基类的转换器,这可以确保类型转换类已经注册

3
这是我的一行解决方案:
@Bean 
public MongoTemplate mongoTemplateFraud() throws UnknownHostException {

  MongoTemplate mongoTemplate = new MongoTemplate(getMongoClient(), dbName);
  ((MappingMongoConverter)mongoTemplate.getConverter()).setTypeMapper(new DefaultMongoTypeMapper(null));//removes _class
  return mongoTemplate;
}

2
上面的正确答案似乎使用了许多已弃用的依赖项。例如,如果您检查代码,它会提到MongoDbFactory,在最新的Spring版本中已被弃用。如果您在2020年使用Spring-Data和MongoDB,则此解决方案似乎过时。要获得即时结果,请检查此代码片段。可以100%正常工作。 只需创建一个新的AppConfig.java文件并粘贴此代码块。您将看到MongoDB文档中的“_class”属性消失。
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;
    }

}

2

我正在使用:

package YOUR_PACKAGE;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;

@Configuration
public class MongoConfiguration {


  @Autowired
  private MappingMongoConverter mongoConverter;

  @PostConstruct
  public void setUpMongoEscapeCharacterAndTypeMapperConversion() {
      mongoConverter.setMapKeyDotReplacement("_");
      
      // This will remove _class: key
      mongoConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
  }

}

顺便提一下:它还将“.”替换为“_”。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接