Spring данных MongoDb: MappingMongoConverter удалить _class - PullRequest
31 голосов
/ 25 июля 2011

По умолчанию 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"));

результирующий объект в монго будет:

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

Вопросы:

  1. Каковы последствия перемещения класса Person в другое пространство имен?

  2. Можно ли не загрязнять объект клавишей "_class"; без написания уникального конвертера только для класса Person?

Ответы [ 9 ]

27 голосов
/ 25 июля 2011

Итак, вот история: мы добавляем тип по умолчанию как некоторый намек, какой класс на самом деле создавать. Поскольку для чтения документа по каналу MongoTemplate необходимо использовать конвейер, в любом случае возможны два варианта:

  1. Вы передаете тип, которому может быть присвоен фактический сохраненный тип. В этом случае мы рассматриваем хранимый тип, используем его для создания объекта. Классическим примером здесь является выполнение полиморфных запросов. Предположим, у вас есть абстрактный класс Contact и ваш Person. Затем вы можете запросить Contact s, и нам, по существу, придется определить тип для создания экземпляра.
  2. Если вы - с другой стороны - передадите совершенно другой тип, мы просто добавим этот тип, а не тот, который на самом деле хранится в документе. Это охватило бы ваш вопрос, что произойдет, если вы переместите тип.

Возможно, вам будет интересно посмотреть этот билет , который охватывает какую-то сменную стратегию отображения типов для преобразования информации о типе в фактический тип. Это может служить просто для экономии места, так как вы можете сократить длинное имя класса до нескольких букв. Это также позволит создавать более сложные сценарии миграции, в которых вы можете найти ключи совершенно произвольного типа, созданные другим клиентом хранилища данных, и связать их с типами Java.

17 голосов
/ 12 сентября 2012

Вот моя аннотация, и она работает.

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

    }

}
5 голосов
/ 21 сентября 2015

Если вы хотите отключить атрибут _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"), вы сохраните полиморфизм, сохранив псевдоним класса.

Я кратко объяснил это в своем блоге , но вот небольшой фрагмент кода: https://gist.github.com/athlan/6497c74cc515131e1336

5 голосов
/ 15 июня 2013
<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 голосов
/ 11 июня 2017

Хотя ответ 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);

    }

}

Надеюсь, это поможет людям, которые хотели бы иметь чистый класс без предупреждений об устаревании.

2 голосов
/ 15 июля 2016

Это мое однострочное решение:

@Bean 
public MongoTemplate mongoTemplateFraud() throws UnknownHostException {

  MongoTemplate mongoTemplate = new MongoTemplate(getMongoClient(), dbName);
  ((MappingMongoConverter)mongoTemplate.getConverter()).setTypeMapper(new DefaultMongoTypeMapper(null));//removes _class
  return mongoTemplate;
}
1 голос
/ 04 марта 2016

Я долго боролся с этой проблемой.Я следовал подходу из mkyong , но когда я ввел атрибут LocalDate (любой класс JSR310 из Java 8), я получил следующее исключение:

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 получить преобразователь из базового класса,это гарантирует, что классы преобразования типов будут зарегистрированы
0 голосов
/ 13 июля 2019
@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;

    }

}
0 голосов
/ 10 мая 2018

вам просто нужно добавить аннотацию @TypeAlias ​​к определению класса вместо изменения сопоставителя типов

...