【问题标题】:Spring data MongoDb: MappingMongoConverter remove _classSpring数据MongoDb:MappingMongoConverter删除_class
【发布时间】:2011-10-12 05:38:17
【问题描述】:

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

【问题讨论】:

  • 那么这有什么故事呢?有没有办法阻止“_class”字段存储在MongoDB中?

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


【解决方案1】:

我已经尝试了上面的解决方案,其中一些不能与审计结合使用,并且似乎没有一个正确设置 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;
    }
}

【讨论】:

    【解决方案2】:

    上面的正确答案似乎是使用了一些已弃用的依赖项。例如,如果您检查代码,它会提到在最新的 Spring 版本中已弃用的 MongoDbFactory。如果您碰巧在 2020 年将 MongoDB 与 Spring-Data 一起使用,则此解决方案似乎较旧。如需即时结果,请检查此 sn-p 代码。工作 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;
        }
    
    }
    

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:
        @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;
        
            }
        
        }
        

        【讨论】:

        • 欢迎来到 Stack Overflow!虽然这段代码 sn-p 可以解决问题,但它没有解释为什么或如何回答问题。请include an explanation for your code,因为这确实有助于提高您的帖子质量。请记住,您是在为将来的读者回答问题,而这些人可能不知道您提出代码建议的原因。
        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          我为这个问题苦苦挣扎了很长时间。我遵循 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】:

            虽然 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 现在是 deprecated
            【解决方案8】:

            这是我的单行解决方案:

            @Bean 
            public MongoTemplate mongoTemplateFraud() throws UnknownHostException {
            
              MongoTemplate mongoTemplate = new MongoTemplate(getMongoClient(), dbName);
              ((MappingMongoConverter)mongoTemplate.getConverter()).setTypeMapper(new DefaultMongoTypeMapper(null));//removes _class
              return mongoTemplate;
            }
            

            【讨论】:

              【解决方案9】:

              如果您想默认禁用_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") 注释您的文档,您将通过保持类的别名来保持多态性。

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

              【讨论】:

                【解决方案10】:
                <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>
                

                【讨论】:

                  【解决方案11】:

                  这是我的注释,它有效。

                  @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 属性。
                  • @mkyong,您的代码中很少有 sn-ps 已被弃用。添加了删除弃用警告的工作答案。您是否介意在此处以及您的博客here 中更新您的答案。谢谢
                  • 对此的重要增强:与其创建一个全新的 MongoMappingContext,不如将其注入,否则可能会导致问题,例如因为此映射上下文未使用应用程序上下文初始化。这是我在评估 SpEL 表达式时遇到问题的根源。
                  【解决方案12】:

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

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

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

                  【讨论】:

                  • 感谢您的回答。将类型提取到配置中是否有意义?而不是把它和物体放在一起?例如,在代码中提供映射:(converter.configure(Contact.class, Person.class))。
                  • Oliver,有没有在 1.0GA 中删除 _class 的简单方法? This 现在不起作用。看起来最简单的方法是:((MappingMongoConverter)template.getConverter()).setTypeMapper(new DefaultMongoTypeMapper(null));。但这是丑陋和错误的......
                  • “不起作用”是什么意思?如果您通过代码或 XML 配置预先正确配置 MappingMongoConverter,则无需执行转换工作。
                  猜你喜欢
                  • 1970-01-01
                  • 2014-06-24
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-08-21
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多