Spring Data Mongo - How to map inherited POJO enti

2019-07-28 00:31发布

问题:

I'm fairly new to Spring but I want to give it a try on this project. I have a MongoDB database populated with quite complexe documents. I want to use Spring data Mongo to query (no other CRUD operations) the database.

I already described my document entity using POJOs but some of them are abstract (see GeometryGeoJSON is used to accept all type of GeoJson geometry, or Contact that can be a Person or an Organisation. The link to the GitHub repo is provided below).

Having a test with that entity definition, a java.lang.InstantiationError is thrown which fair since no Contructor are defined in those abstract classes.

Here is GitHub repository in case you need to have a look.

I feel a bit lost with all this but I'll have a more careful look at the documentation.

How would you face this issue ?

回答1:

I'll answer my own question. As mentioned in the comments, the solution is to use Converter.

Here is an example of what I intended to achieve with my class model :

A Contact can be either a Person or an Organisation.

If you are using spring-data-mongodb MongoRepository to write data in your database according to your entity model, a _class field will be added to document roots and to complex property types (see this section). This fields store the fully qualified name of the Java class and it allows disambiguation when mapping from MongoDb Document to Spring data model.

If your app just read document from the database (no _class fields), you need to tell Spring data which class to instantiate when mapping a Contact. Spring-data allows you to customize default type mapping behaviour using Converter. Using explicit Converter override default mapping for the class. you need to explicitly map your entire class. Here is an example of my ContactReadConverter:

@ReadingConverter
public class ContactReadConverter implements Converter<Document, Contact> {

    @Override
    public Contact convert(Document source) {
        if (source.get("firstName") == null) {
            Organisation organisation = new Organisation();
            I18n name = new I18n();
            name.setEn(source.get("name", Document.class).get("en", String.class));
            name.setFr(source.get("name", Document.class).get("fr", String.class));
            organisation.setName(name);
            organisation.setAcronym(source.get("acronym", String.class));
            organisation.setRole(source.get("role", String.class));
            return organisation;
        }
        Person person = new Person();
        person.setFirstName(source.get("firstName", String.class));
        person.setLastName(source.get("lastName", String.class));
        person.setRole(source.get("role", String.class));
        person.setEmail(source.get("email", String.class));
        person.setOrcId(source.get("orcId", String.class));
        if (source.get("organisation") != null) {
            Document sourceOrg = source.get("organisation", Document.class);
            Organisation organisation = new Organisation();
            organisation.setAcronym(sourceOrg.get("acronym", String.class));
            organisation.setRole(sourceOrg.get("role", String.class));
            if (sourceOrg.get("name") != null) {
                I18n name = new I18n();
                name.setFr(sourceOrg.get("name", Document.class).get("fr", String.class));
                name.setEn(sourceOrg.get("name", Document.class).get("en", String.class));
                organisation.setName(name);
            }
            person.setOrganisation(organisation);
        }
        return person;
    }
}

Then, newly defined converters need to be registered:

@Configuration
public class DataportalApplicationConfig extends AbstractMongoConfiguration {
    @Value("${spring.data.mongodb.uri}")
    private String uri;
    @Value("${spring.data.mongodb.database}")
    private String database;
    @Override
    public MongoClient mongoClient() {
        return new MongoClient(new MongoClientURI(uri));
    }
    @Override
    protected String getDatabaseName() {
        return database;
    }    
    @Bean
    @Override
    public MongoCustomConversions customConversions() {
        List<Converter<?, ?>> converterList = new ArrayList<>();
        converterList.add(new ContactReadConverter());
        return new MongoCustomConversions(converterList);
    }
}

Hope it helps.