Search code examples
springmongodbspring-datamongotemplatemongorepository

Spring Data MongoRepository Saving Objects with Differing Numbers of Fields


I am storing game states in a MongoDB database and am using Spring Data to manage my database interactions. I am new to Spring Data and am unsure how to deal with the following scenario.

I have a document of type "Game" with a bunch of properties such as id, timestamp, etc... One of these properties is a list of actions taken by users. These actions are of the form:

{ type: 2 }, {type: 3, value: 4}, {type: 5, id: 1234}, {type 6}, {type: 5, value: 6, id: 56}

In other words, an action can have three properties: type, value, and id. However, not every action requires all three values to be stored. I want to avoid having a bunch of null values in my database, and would like my database to just not include and id or a value if they are not specified.

Using Spring Data's MongoRepository model, I am not sure how to achieve this. I can create a CRUD Game class and have one of its properties be a list of Action (where Action itself is a CRUD class with properties type, value, and id), but won't that end up storing null values in the database if I do not specify the value or id?

In short, how can I use Spring Data's MongoRepository but still maintain the flexibility of being able to store lists of objects with varying parameters or object types in general.


Solution

  • I will explain how varying fields are handled with an example. The following Game.java POJO class represents the object mapping to the game collection document.

    public class Game {
    
        String name;
        List<Actions> actions;
    
        public Game(String name, List<Actions> actions) {
            this.name = name;
            this.actions = actions;
        }
    
        public String getName() {
            return name;
        }
    
        public List<Actions> getActions() {
            return actions;
        }
    
        // other get/set methods, override, etc..
    
    
        public static class Actions {
    
            Integer id;
            String type;
    
            public Actions() {
            }
    
            public Actions(Integer id) {
                this.id = id;
            }
    
            public Actions(Integer id, String type) {
                this.id = id;
                this.type = type;
            }
    
            public Integer getId() {
                return id;
            }
    
            public String getType() {
                return type;
            }
    
            // other methods
        }
    }
    

    For the Actions class you need to provide constructors with the possible combinations. Use the appropriate constructor with id, type, etc. For example, create a Game object and save to the database:

    Game.Actions actions= new Game.Actions(new Integer(1000));
    Game g1 = new Game("G-1", Arrays.asList(actions));
    repo.save(g1);
    

    This is stored in the database collection game as follows (queried from mongo shell):

    {
            "_id" : ObjectId("5eeafe2043f875621d1e447b"),
            "name" : "G-1",
            "actions" : [
                    {
                            "_id" : 1000
                    }
            ],
            "_class" : "com.example.demo.Game"
    }
    

    Note the actions array. As we had stored only the id field in the Game.Actions object, only that field is stored. Even though you specify all the fields in the class, only those provided with values are persisted.

    These are two more documents with Game.Actions created with type only and id + type using the appropriate constructors:

    {
            "_id" : ObjectId("5eeb02fe5b86147de7dd7484"),
            "name" : "G-9",
            "actions" : [
                    {
                            "type" : "type-x"
                    }
            ],
            "_class" : "com.example.demo.Game"
    }
    {
            "_id" : ObjectId("5eeb034d70a4b6360d5398cc"),
            "name" : "G-11",
            "actions" : [
                    {
                            "_id" : 2,
                            "type" : "type-y"
                    }
            ],
            "_class" : "com.example.demo.Game"
    }