Spring Data Mongo - 如何映射继承的 POJO 实体?

我对 Spring 还很陌生,但我想尝试一下这个项目.我有一个 MongoDB 数据库,其中填充了非常复杂的文档.我想使用 Spring data Mongo 来查询(没有其他 CRUD 操作)数据库.

我已经使用 POJO 描述了我的文档实体,但其中一些是抽象的(请参阅 GeometryGeoJSON 用于接受所有类型的 GeoJson 几何,或 Contact 可以是PersonOrganisation.下面提供了 GitHub 存储库的链接.

使用该实体定义进行测试,抛出一个 java.lang.InstantiationError,这是公平的,因为在这些抽象类中没有定义构造函数.

这里是

Contact 可以是 PersonOrganisation.

如果您使用的是 spring-data-mongodb MongoRepository 根据你的实体模型在你的数据库中写入数据,一个 _class 字段将被添加到文档根和复杂的属性类型(查看本节).此字段存储 Java 类的完全限定名称,并且在从 MongoDb 文档映射到 Spring 数据模型时允许消除歧义.

如果您的应用程序只是从数据库中读取文档(没有 _class 字段),您需要告诉 Spring 数据在映射 Contact 时要实例化哪个类.Spring-data 允许您使用 Converter 自定义默认类型映射行为.使用显式 Converter 覆盖类的默认映射.你需要明确地映射你的整个班级.这是我的 ContactReadConverter 的示例:

@ReadingConverter公共类ContactReadConverter实现Converter<Document, Contact>{@覆盖公共联系人转换(文档来源){if (source.get("firstName") == null) {组织组织=新组织();I18n 名称 = 新 I18n();name.setEn(source.get("name", Document.class).get("en", String.class));name.setFr(source.get("name", Document.class).get("fr", String.class));组织.setName(名称);organization.setAcronym(source.get("acronym", String.class));组织.setRole(source.get("角色", String.class));返回组织;}人人 = 新人();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) {文档 sourceOrg = source.get("组织", Document.class);组织组织=新组织();organization.setAcronym(sourceOrg.get("acronym", String.class));组织.setRole(sourceOrg.get("角色", String.class));if (sourceOrg.get("name") != null) {I18n 名称 = 新 I18n();name.setFr(sourceOrg.get("name", Document.class).get("fr", String.class));name.setEn(sourceOrg.get("name", Document.class).get("en", String.class));组织.setName(名称);}person.setOrganisation(组织);}返回人;}}

然后,需要注册新定义的转换器:

@Configuration公共类 DataportalApplicationConfig 扩展 AbstractMongoConfiguration {@Value("${spring.data.mongodb.uri}")私有字符串 uri;@Value("${spring.data.mongodb.database}")私有字符串数据库;@覆盖公共 MongoClient mongoClient() {返回新的 MongoClient(new MongoClientURI(uri));}@覆盖受保护的字符串 getDatabaseName() {返回数据库;}@豆角,扁豆@覆盖公共 MongoCustomConversions customConversions() {列表<转换器<?, ?>>converterList = new ArrayList<>();converterList.add(new ContactReadConverter());返回新的 MongoCustomConversions(converterList);}}

希望对你有帮助.

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 ?

解决方案

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.

相关文章