Spring данные MongoDb: MappingMongoConverter удалить _class

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


person Yuriy Nemtsov    schedule 24.07.2011    source источник
comment
Так что за история с этим? Нет ли способа предотвратить сохранение поля _class в MongoDB?   -  person hodgesz    schedule 29.10.2011


Ответы (11)


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

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

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

person Oliver Drotbohm    schedule 25.07.2011
comment
спасибо за ответ. Имеет ли смысл извлекать тип в конфигурацию; вместо того, чтобы держать его с объектом? Например, чтобы обеспечить сопоставление в коде: (converter.configure(Contact.class, Person.class)). - person Yuriy Nemtsov; 27.07.2011
comment
Оливер, есть ли простой способ удалить _class в 1.0GA? Это сейчас не работает . Кажется, самый простой способ: ((MappingMongoConverter)template.getConverter()).setTypeMapper(new DefaultMongoTypeMapper(null));. Но это некрасиво и неправильно... - person Shcheklein; 27.01.2012
comment
Что вы подразумеваете под "не работает"? Нет необходимости выполнять приведение, если вы заранее правильно настроите MappingMongoConverter с помощью кода или конфигурации XML. - person Oliver Drotbohm; 27.01.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;

    }

}
person mkyong    schedule 12.09.2012
comment
Здесь есть предостережение: этот код удаляет все коды преобразования типов. Например, Spring Data больше не может преобразовывать (и хранить) атрибуты LocalDate. - person ChrLipp; 04.03.2016
comment
@mkyong, несколько фрагментов вашего кода устарели. Добавили рабочий ответ, удалив предупреждения об устаревании. Не могли бы вы обновить свой ответ здесь, а также в своем блоге здесь. Спасибо - person harshavmb; 11.06.2017
comment
Важное улучшение для этого: вместо создания нового MongoMappingContext лучше внедрить его, иначе это может вызвать проблемы, например, потому что этот контекст сопоставления не инициализирован контекстом приложения. Это было источником проблем, которые у меня были с оценкой выражений SpEL. - person Constantino Cronemberger; 25.05.2018

Если вы хотите отключить атрибут _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

person Athlan    schedule 20.09.2015

Хотя ответ Мкионга все еще работает, я хотел бы добавить свою версию решения, так как некоторые биты устарели и могут находиться на грани очистки.

Например: 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);

    }

}

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

person harshavmb    schedule 11.06.2017
comment
Инъекция в поле не рекомендуется. SimpleMongoDbFactory теперь deprecated. - person Kamil Witkowski; 10.02.2020

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

@Bean 
public MongoTemplate mongoTemplateFraud() throws UnknownHostException {

  MongoTemplate mongoTemplate = new MongoTemplate(getMongoClient(), dbName);
  ((MappingMongoConverter)mongoTemplate.getConverter()).setTypeMapper(new DefaultMongoTypeMapper(null));//removes _class
  return mongoTemplate;
}
person kozla13    schedule 15.07.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 получить преобразователь из базового класса, это гарантирует, что классы преобразования типов зарегистрированы
person ChrLipp    schedule 04.03.2016

Для 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);
    }

    // .....

}
person Maxim Savenko    schedule 31.07.2020

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

person Matt    schedule 10.05.2018

Правильный ответ выше, похоже, использует ряд устаревших зависимостей. Например, если вы проверите код, в нем упоминается MongoDbFactory, который устарел в последней версии Spring. Если вам довелось использовать MongoDB с Spring-Data в 2020 году, это решение кажется более старым. Для мгновенных результатов проверьте этот фрагмент кода. Работает 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;
    }

}
person Kaivalya Kate    schedule 26.01.2021

Я пробовал приведенные выше решения, некоторые из них не работают в сочетании с аудитом, и ни одно из них, похоже, не устанавливает правильно 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;
    }
}
person Michele Da Ros    schedule 06.05.2021

person    schedule
comment
Добро пожаловать в Stack Overflow! Хотя этот фрагмент кода может решить проблему, он не объясняет, почему и как отвечает на вопрос. Пожалуйста, включите объяснение кода, так как это действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос для будущих читателей, и эти люди могут не знать причин вашего предложения кода. - person Samuel Philipp; 13.07.2019