Using MongoDB

Aug 4, 2017
Using MongoDB
  • Using MongoDB

    How to use MongoDB in your plugins



    Note: Before editing this article, please ask in the discussion section for a second opinion – personal opinions and views should be kept out of wiki articles. Thank you!


    NoSQL Databases(top)


    NoSQL Databases don't follow a table-like structure of the SQL databases. Instead many use either a Document style format (see MongoDB, etc.) or a key->value structure (see Aerospike, Redis, etc)

    This article especially targets MongoDB, the database used by many Minecraft networks.

    The concept of MongoDB(top)


    Like in SQL, you have databases in MongoDB. In these databases there are things like tables, called collections. But these tables have no defined columns. Every collection is simply a list of little JSON objects. The JSON objects can contain whatever fields they want, they don't have to have one field in common. For faster searching you can define a field that can be found in every JSON object, for example the player UUID. This is called indexing. This way, the DB only keeps these values (the UUID) in memory and searches only them before loading the rest of the objects. You can still search for other values in the JSON object, but the search will not be as fast as a search for an index.

    Morphia(top)

    Besides just what is covered in this tutorial, there is another way to save and access data from MongoDB, and that is with an API called Morphia. It is completely optional, but it makes the process of creating new collections from Java Objects a very streamline process. The tutorial on Spigot can be found here: https://www.spigotmc.org/wiki/mongodb-with-morphia/

    Setup the database(top)


    When you are on Debian Linux you can simply run:
    Code (Java):
    apt-get install mongodb
    This will automatically install and setup your database.
    When not on Linux, you can get MongoDB from their homepage: Downloads

    Securing your database(top)


    By default, the database will bind on 127.0.0.1, which means that it can only be accessed from the root itself.
    If you want to change the bind ip to 0.0.0.0 (can be accessed from everywhere) and create a user and password for your db's, please follow these instructions: Enable Authentication
    0.0.0.0 is not "accessible" from anywhere. it's simply a binding to every local machine ip

    Begin using MongoDB(top)


    On your root simply enter "mongo" in console and you will enter the so called mongo shell. There you can simply dispatch commands. Our aim is to dispatch these commands with Java, so we continue downloading the Java Driver of MongoDB. Import the .jar as library to your project. Alternatively, more experienced developers can import it via. its maven artifact.

    I suggest using the old 2.x driver as it will be used here. If you want to try out the new one, feel free to do so. I didn't get the new driver to work because of missing references in it :/

    Connect to MongoDB using Java(top)


    Code (Java):
        private DBCollection players;
        private DB mcserverdb;
        private MongoClient client;
        public boolean connect(String ip, int port){
            //Connect to the specified ip and port
            //Default is localhost, 27017
            try {
                client = new MongoClient(ip, port);
            } catch (UnknownHostException e) {
                //When you end up here, the server the db is running on could not be found!
                System.out.println("Could not connect to database!");
                e.printStackTrace();
                return false;
            }
            //Get the database called "mcserver"
            //If it does not exist it will be created automatically
            //once you save something in it
            mcserverdb = client.getDB("mcserver");
            //Get the collection called "players" in the database "mcserver"
            //Equivalent to the table in MySQL, you can store objects in here
            players = mcserverdb.getCollection("players");
            return true;
        }

    Store player information(top)


    Code (Java):
        public void storePlayer(UUID uuid, String name, long tokens, String rank){
            //Lets store our first player!
            //This player has never played before and we just want to create a object for him
            DBObject obj = new BasicDBObject("uuid", uuid);
            obj.put("name", name);
            obj.put("tokens", tokens);
            obj.put("rank", rank);
            //Lets insert it in our collection:
            players.insert(obj);
        }

    Read player information(top)


    There are two ways here. You can either use a Cursor to loop through all objects, or use findOne() to only find one on the server side. For searching only one object, as we do now, findOne() is faster. For other operations, where you search multiple objects, Cursor is very useful.

    Option 1: Using findOne()
    Code (Java):
        public void readPlayer(UUID uuid){
            //Lets build a minimal object to get all objects in the
            //collection "players" containing the field
            //"uuid" with the value uuid (what we are
            //searching for)
            DBObject r = new BasicDBObject("uuid", uuid);
            //Use findOne to only get one object!
            DBObject found = players.findOne(r);
            if(found==null){
                //User not saved yet. Add him in the DB!
                return;
            }
            //The user was found! Lets get our values!
            //You can cast the objects to String/Long etc.
            //As they are being delivered as binary objects
            //not as String like in MySQL
            String name = (String) found.get("name");
            long tokens = (long) found.get("tokens");
            String rank = (String) found.get("rank");
        }
    Option 2: Using DBCursor
    Code (Java):
        public void readPlayer(UUID uuid){
            //Lets build a minimal object to get all objects in the
            //collection "players" containing the field
            //"uuid" with the value uuid (what we are
            //searching for)
            DBObject r = new BasicDBObject("uuid", uuid);
            //Create a cursor object to loop through all the results
            //Lets get all the objects and get the one object that we are searching for
            DBObject found = null;
            // try-with-resources will handle the closing of the resources
            try (DBCursor cursor = players.find(r)){
                   while(cursor.hasNext()) {
                       found = cursor.next();
                   }
            }

            if (found == null) {
                //User not saved yet. Add him in the DB!
                return;
            }
            //The user was found! Lets get our values!
            //You can cast the objects to String/Long etc.
            //As they are being delivered as binary objects
            //not as String like in MySQL
            String name = (String) found.get("name");
            long tokens = (long) found.get("tokens");
            String rank = (String) found.get("rank");
        }

    Updating player information in the DB(top)


    Updating works like reading. You search the object, modify it and send it back to the server.

    Option 1: Updating all the Objects.
    Code (Java):
        public void updatePlayer(UUID uuid, String name, long tokens, String rank) {
            //Lets build a minimal object to get all objects in the
            //collection "players" containing the field
            //"uuid" with the value uuid (what we are
            //searching for)
            DBObject r = new BasicDBObject("uuid", uuid);
            //Use findOne to only get one object!
            DBObject found = players.findOne(r);
            if (found == null){
                //User not saved yet. Add him in the DB!
                return;
            }
            //The user was found! Lets create a new replacement object!
            DBObject obj = new BasicDBObject("uuid", uuid);
            obj.put("name", name);
            obj.put("tokens", tokens);
            obj.put("rank", rank);
            //And update it! This simply replace our found object!
            players.update(found, obj);
        }
    Option 2: Updating only 1 Object.
    Code (Java):
        public void updatePlayer(UUID uuid, String rank) {
            //Lets build a minimal object to get all objects in the
            //collection "players" containing the field
            //"uuid" with the value uuid (what we are
            //searching for)
            DBObject r = new BasicDBObject("uuid", uuid);
            //Use findOne to only get one object!
            DBObject found = players.findOne(r);
            if (found == null){
                //User not saved yet. Add him in the DB!
                return;
            }
            //The user was found! Lets create a new replacement object!
            BasicDBObject set = new BasicDBObject("$set", r);
            set.append("$set", new BasicDBObject("rank", rank));
            //And update it! This simply replace our found object!
            players.update(found, set);
        }

    Using the MongoDB driver in your server(top)


    So, I'll assume that after reading this topic, you've made a plugin that successfully uses MongoDB as it's database system, yay!

    And now you put the plugin in the server, boot it up aaaaaaand... wait... what? an error?
    NoClassDefFound? NoClassFoundException: com.mongodb.MongoClient.?
    How is this possible?
    Everything should be working fi-
    Oooooh I forgot to put the driver into the plugins folder
    ~Places mongo-java-driver-{Version here}.jar into plugins folder~
    Now it should wor- Wait, it still doesn't works?
    ~Moves mongo-java-driver-{Version here}.jar to {Server folder}/lib~
    What is happening???

    Now, as you should know, the MongoDB Java Driver isn't loaded as a bukkit plugin, because it isn't one. So the question floating in the air is:
    There are some possible solutions, I've seen people around saying to shade the java driver into the plugin.

    - Does it work?
    - Yes
    - Should I do it?
    - No
    - Why?

    It's not actually like you can't do it, you can, but it's kind of bad.

    For example, the driver mongo-java-driver-3.2.0-rc0.jar is 1,41 MB in size, if you shade it in a plugin that is 500KB in size, the final compiled jar will be around 2MB, which means the java driver alone composes 75% of the plugin size, which also means you increase your plugin size by 300%. So everytime you and your users upload the plugin to your server, you are uploading 1,41 MB needlessly, which is not something we want.

    What I recommend you to do is to notify spigot that:
    But how do we do that?
    Classpath option!
    There is this cool command line option that allow us to solve this, which is -classpath, what it does is that it tells the JRE:
    The usage for the argument is:
    Code (Text):
    java –classpath ${CLASSPATH} {MAIN CLASS}
    So we should use something among the lines of:
    Code (Text):
    java -classpath "spigot.jar:lib/*" org.bukkit.craftbukkit.Main
    This will load the jar files in {spigot.jar folder}/lib as dependencies! Now we can use MongoDB and be eternally happy with our lives! :D

    Note: If using Windows as your OS, you will need to use a semi-colon rather than a colon in the above command.

    Please note that when using the classpath option, you don't use -jar spigot.jar, the classpath will execute the server without the need for the -jar argument :)

    The End(top)


    Everything above should be enough to use MongoDB in your plugins. If you need further information, you can ask in this Wiki Page's discussion thread or go to the official MongoDB Wiki.
  • Loading...
  • Loading...