Why is Spring Data MongoDB unable to instantiate this nested type structure?
Asked Answered
S

2

15

My document structure is like:

{
    _id: "A",
    groups:[{
        groupId: "someId",
        groupName: "someName",
        params: {
            type1: ["a", "b"],
            type2: ["c", d]
        }
    }],
    config: {
        person: {}
        dataDetails: {
            dataTypeDetails: {},
            dataList: ["dt1", "dt2"]
        }
    }
}

My Spring Data MongoDB model types look like this:

// Imports etc.
@Document
public class Entity {

    @Id
    private String _id;

    private List<Group> groups;
    private Config config;
    // setters and getters

    public class Group {
        private String groupId;
        private String groupName;
        private ParamData params;

        // setter and getters
    }

    public class ParamData {
        private List<String> type1;
        private List<String> type2;
    }

    public class Config {
        private Map person;
        private DataConfig dataDetails;
    }

    public class DataConfig {
        private Map dataTypeDetails;
        private List<String> dataList;
    }
}

Stacktrace:

org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate com.****.common.models.Entity$ParamData using constructor public com.****.common.models.Entity$ParamData(com.****.common.models.Entity) with arguments com.****.common.models.Entity$Group@2eb61a7b
    at org.springframework.data.convert.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:78)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:257)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:237)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readValue(MappingMongoConverter.java:1136)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.access$100(MappingMongoConverter.java:78)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1085)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getValueInternal(MappingMongoConverter.java:816)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter$1.doWithPersistentProperty(MappingMongoConverter.java:270)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter$1.doWithPersistentProperty(MappingMongoConverter.java:263)
    at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:261)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:263)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:237)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readCollectionOrArray(MappingMongoConverter.java:861)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readValue(MappingMongoConverter.java:1134)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.access$100(MappingMongoConverter.java:78)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1085)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getValueInternal(MappingMongoConverter.java:816)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter$1.doWithPersistentProperty(MappingMongoConverter.java:270)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter$1.doWithPersistentProperty(MappingMongoConverter.java:263)
    at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:261)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:263)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:237)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:201)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:197)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:78)
    at org.springframework.data.mongodb.core.MongoTemplate$ReadDbObjectCallback.doWith(MongoTemplate.java:2016)
    at org.springframework.data.mongodb.core.MongoTemplate.executeFindMultiInternal(MongoTemplate.java:1700)
    at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:1523)
    at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:1507)
    at org.springframework.data.mongodb.core.MongoTemplate.find(MongoTemplate.java:532)
    at org.springframework.data.mongodb.core.MongoTemplate.findOne(MongoTemplate.java:497)
    at org.springframework.data.mongodb.core.MongoTemplate.findOne(MongoTemplate.java:489)

My in DAO I am trying to fetch the document by identifier, but it is failing only for the values at dataDetails and params. If I comment out the ParamData param, I get error for DataConfig. The data was added using command line/node scripts and was not added using this code. But it is getting retrieved properly by node/mongoose as well as from command line.

Subchaser answered 14/10, 2014 at 12:50 Comment(0)
D
7

This seems to be an issue with doubly nested inner classes and the synthetically generated constructors created by the compiler. I could reproduce that issue locally and see if we can provide a fix. In the meantime you have two options:

  1. Turn the inner class into static ones as this will remove the synthetic constructors and instantiation will work correctly.
  2. Nest the type declarations in the same way you nest the properties. I.e. move the ParamData class into the Group class, DataConfig into Config as that will cause the synthetic constructors created in a way they match instantiation order Spring Data currently relies on.

I'd suggest the former approach as it doesn't artificially bind the classes to instances of the outer class.

Divisionism answered 14/10, 2014 at 18:42 Comment(1)
Thank you by moving ParamData inside Group and DataConfig in Config class, it is working now. But have stumbled upon another problem, its not letting me declare the List<Group>, it needs ArrayList<Group> or Group[], but works fine for List<String>! But for now, its working at least.Subchaser
U
3
Failed to instantiate ... using constructor public ... ReflectionEntityInstantiator

says it cannot create the objects using reflection.

Do you have getters and setters for all the fields in all your classes? Your code above does not have them for ParamData, Config and DataConfig.

Also, if you happen to have non-default constructors in any of your classes make sure you have an empty argument constructor, else reflection will fail.

Uxoricide answered 14/10, 2014 at 15:28 Comment(3)
Sorry, it seems I didn't add the comments, but yes all classes have getters and setters and none has a nondefault constructors.Subchaser
I am not sure if for the sake of saving space here you kept all your classes in Entity class itself, or if they are in their own class files. You need to have each class in a separate file annotated with @Document. For example, see here: #11255600Uxoricide
@Uxoricide - That's not required. If done correctly, inner classes work.Divisionism

© 2022 - 2024 — McMap. All rights reserved.