web-dev-qa-db-fra.com

Données de printemps MongoDb: MappingMongoConverter remove _class

La valeur par défaut MappingMongoConverter ajoute une clé de type personnalisée ("_class") à chaque objet de la base de données. Donc, si je crée une personne:

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

et enregistrez-le sur la base de données:

MongoOperations ops = new MongoTemplate(new Mongo(), "users");
ops.insert(new Person("Joe"));

l'objet résultant dans le mongo sera:

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

Des questions:

  1. Quelles sont les implications du déplacement de la classe Person dans un autre espace de noms?

  2. Est-il possible de ne pas polluer l'objet avec la clé "_class"; sans écrire un convertisseur unique juste pour la classe Personne?

29
Yuriy Nemtsov

Alors voici l'histoire: nous ajoutons le type par défaut comme une sorte d'indice sur la classe à instancier réellement. Comme vous devez diriger un type dans lequel lire le document via MongoTemplate, deux options sont possibles:

  1. Vous remettez un type auquel le type enregistré peut être affecté. Dans ce cas, nous considérons le type stocké, utilisez-le pour la création d'objet. Exemple classique ici fait des requêtes polymorphes. Supposons que vous ayez une classe abstraite Contact et votre Person. Vous pouvez alors interroger Contacts et nous avons essentiellement devons déterminer le type à instancier.
  2. Si vous - en revanche - passez dans un type complètement différent, nous nous contenterons de marshaler dans ce type donné, pas dans celui stocké dans le document. Cela couvrirait votre question ce qui se passe si vous déplacez le type.

Vous voudrez peut-être regarder ce ticket qui couvre une sorte de stratégie de mappage de types enfichable pour transformer les informations de type en un type réel. Cela peut servir simplement à économiser de l'espace car vous pouvez réduire un nom de classe qualifié long à un hachage de quelques lettres. Cela permettrait également des scénarios de migration plus complexes dans lesquels vous pourriez trouver des clés de type complètement arbitraire produites par un autre client de banque de données et les lier à des types Java.

24
Oliver Drotbohm

Voici mon annotation, et ça marche.

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

    }

}
17
mkyong
<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>
5
AKKI

Si vous souhaitez désactiver l'attribut _class par défaut tout en préservant le polymorphisme pour les classes spécifiées, vous pouvez définir explicitement le type de champ _class (facultatif) en configurant:

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

Cela préservera le champ _class (ou ce que vous voulez nommer dans construtor) uniquement pour les entités spécifiées.

Vous pouvez également écrire votre propre variable TypeInformationMapper, par exemple, en fonction d’annotations. Si vous annotez votre document avec @DocumentType("aliasName"), vous conserverez le polymorphisme en conservant un alias de classe.

Je l'ai expliqué brièvement sur mon blog , mais voici un code rapide: https://Gist.github.com/athlan/6497c74cc515131e1336

4
Athlan

Bien que la réponse de Mkyong fonctionne toujours, j'aimerais ajouter ma version de la solution car quelques bits sont obsolètes et risquent d'être sur le point d'être nettoyés. 

Par exemple: MappingMongoConverter(mongoDbFactory(), new MongoMappingContext()) est déconseillé en faveur de new MappingMongoConverter(dbRefResolver, new MongoMappingContext()); et SimpleMongoDbFactory(new Mongo(), "databasename"); en faveur de new SimpleMongoDbFactory(new MongoClient(), database);

Donc, ma dernière réponse de travail sans avertissements de dépréciation est la suivante:

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

    }

}

J'espère que cela aidera les gens qui voudraient avoir une classe propre sans avertissements de dépréciation.

3
harshavmb

Ceci est ma solution en une seule ligne:

@Bean 
public MongoTemplate mongoTemplateFraud() throws UnknownHostException {

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

J'ai longtemps lutté avec ce problème. J'ai suivi l'approche de mkyong mais lorsque j'ai introduit un attribut LocalDate (toute classe JSR310 de Java 8), j'ai reçu l'exception suivante:

org.springframework.core.convert.ConverterNotFoundException:
No converter found capable of converting from type [Java.time.LocalDate] to type [Java.util.Date]

Le convertisseur correspondant org.springframework.format.datetime.standard.DateTimeConverters fait partie de Spring 4.1 et est référencé dans Spring Data MongoDB 1.7. Même si j’utilisais des versions plus récentes, le convertisseur n’est pas intervenu. 

La solution consistait à utiliser la MappingMongoConverter existante et à ne fournir qu'une nouvelle DefaultMongoTypeMapper (le code de mkyong est en commentaire):

@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)
    }

Résumer:

  • étendre AbstractMongoConfiguration
  • annoter avec EnableMongoRepositories
  • dans mongoTemplate obtenir un convertisseur de la classe de base, cela garantit que les classes de conversion de type sont enregistrées
1
ChrLipp

il vous suffit d'ajouter l'annotation @TypeAlias ​​à la définition de la classe en changeant le mappeur de type

0
Matt