Подтвердить что ты не робот

Spring data MongoDb: MappingMongoConverter удалить _class

По умолчанию MappingMongoConverter добавляет специальный тип ключа ( "_class" ) к каждому объекту в базе данных. Итак, если я создаю Person:

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"));

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

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

Вопросы:

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

  • Возможно ли загрязнение объекта ключом "_class"? без написания уникального конвертера только для класса Person?

4b9b3361

Ответ 1

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

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

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

Ответ 2

Здесь моя аннотация, и она работает.

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

    }

}

Ответ 3

<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

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

В то время как ответ 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);

    }

}

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

Ответ 6

Это однолинейное решение:

@Bean 
public MongoTemplate mongoTemplateFraud() throws UnknownHostException {

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

Ответ 7

Я долго боролся с этой проблемой. Я выполнил подход 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)
    }

Подводя итог:

  • extend AbstractMongoConfiguration
  • аннотировать с помощью EnableMongoRepositories
  • in mongoTemplate получить конвертер из базового класса, это гарантирует, что классы преобразования типов зарегистрированы

Ответ 8

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

Ответ 9

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

    }

}