Spring数据MongoDb:MappingMongoConverter删除_class

时间:2011-07-24 23:39:22

标签: mongodb spring-data spring-data-mongodb

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

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

并将其保存到db:

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类编写唯一的转换器?

12 个答案:

答案 0 :(得分:25)

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

  1. 您可以输入可以分配实际存储类型的类型。在这种情况下,我们考虑存储的类型,用于创建对象。这里的经典示例是进行多态查询。假设您有一个抽象类ContactPerson。然后,您可以查询Contact,我们基本上必须确定要实例化的类型。
  2. 如果你 - 另一方面 - 传递一个完全不同的类型,我们只是编组到那个给定的类型,而不是实际存储在文档中的类型。这将涵盖您的问题,如果您移动类型会发生什么。
  3. 您可能有兴趣观看this ticket,其中涵盖某种可插入类型映射策略,以将类型信息转换为实际类型。这可以简单地节省空间,因为您可能希望将长限定类名减少为几个字母的哈希值。它还允许更复杂的迁移方案,您可能会在其中找到由另一个数据存储客户端生成的完全任意类型的密钥,并将这些密钥绑定到Java类型。

答案 1 :(得分: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;

    }

}

答案 2 :(得分: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>

答案 3 :(得分:5)

如果要默认禁用_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字段(或任何您想要在construtor中命名的字段)。

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

I have explained briefly it on my blog,但这里有一些快速代码: https://gist.github.com/athlan/6497c74cc515131e1336

答案 4 :(得分:3)

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

    }

}

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

答案 5 :(得分:2)

这是我的一线解决方案:

@Bean 
public MongoTemplate mongoTemplateFraud() throws UnknownHostException {

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

答案 6 :(得分:1)

我在这个问题上挣扎了很长时间。我遵循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中从基类获取转换器,这可以确保注册类型转换类

答案 7 :(得分:1)

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

    // .....

}

答案 8 :(得分:0)

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

答案 9 :(得分:0)

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

    }

}

答案 10 :(得分: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;
    }

}

答案 11 :(得分: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);
    }
}

作为替代方案,您可以使用 BeanPostProcessor 来检测 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;
    }
}