Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Persisting and retrieving a Map of Maps with Morphia and Mongodb

I would like to be able to persist and retrieve, amongst other things, a map of maps in a MongoDB collection. I am using Java to access the MongoDB via Morphia.

The example I am using below is a collection that contains documents detailing the owners of various cars. In this example the number of vehicles of a specific make and model are stored in a map of maps

The majority of the properties are working with no problems experienced, but for the case where a property is a map of a map defined in the following way:

@Property("vehicles")
private Map<String, Map<String, Integer> vehicles = new HashMap<String, HashMap<String, Integer>>();

The object is created (some values inserted into the map) and persisted to the Mongo database as one would expect it to be:

"vehicles" : {
    "FORD" : {
        "FIESTA" : 1
    },
    "TOYOTA" : {
        "COROLLA" : 1,                  
        "PRIUS": 1
    },
    "BMW" : {
        "SLK" : 1
    }
}

However when the object is retrieved via java code (a query on the MongoDB console works as expected)) in the following way...

Query<Owner> q = ds.find(Owner.class);    
System.out.println(q.countAll());
Iterable<Owner> i = q.fetch();
for (Owner o : i) {
    System.out.println(o);
}

...the code dies in a horrible way on the q.fetch() line.

Please help :)

like image 237
Ron Tuffin Avatar asked Sep 10 '12 11:09

Ron Tuffin


2 Answers

The issue stems from the fact that a Map (being an interface) does not have a default constructor, and while Morphia was correctly assigning the constructor for the concrete HashMap on the outer Map it was failing to resolve a constructor for the inner Map. This was resulting in the NullPointerException.

After a lot of debugging and trying this and that, eventually I stumbled (with the help of a colleague) on to the solution.

  • Instead of using the @Property annotation use @Embedded. and
  • Declare the maps using the concrete HashMap and not use the Map interface

    @Embedded("vehicles")
    private HashMap<String, HashMap<String, Integer>> vehicles = new HashMap<String, HashMap<String, Integer>>();
    

For those of you who are wondering... specifying the concrete class in either the @Property or @Embedded annotation did nothing to help resolve the constructor for the inner HashMap.

like image 142
Ron Tuffin Avatar answered Sep 19 '22 14:09

Ron Tuffin


Since we were using our own datatype this way

private HashMap<String, HashMap<String, OwnDataType>> vehicles = new HashMap<String, HashMap<String, OwnDataType>>();

all previous recommendations did not work; the only thing making morphia reading the data properly was to transform the OwnDataType to plural form, that is keeping a map inside the OwnDataType itself and not using maps inside of maps:

private HashMap<String, OwnDataTypes> vehicles = new HashMap<String, OwnDataTypes>();

Now everything is working fine.

like image 22
Andreas Avatar answered Sep 19 '22 14:09

Andreas