Resource Implementing multiple database types with minimal code changes.

Discussion in 'Spigot Plugin Development' started by Zastrix, Mar 18, 2020.

  1. So I saw a few resources where people hardcoded if/else clauses for a DB type.

    Example would be an enormous amounts of:

    Code (Java):
    if(type == Enum#MySQL)
      mysqlDb.write();
    else
      sqlite.write();
    And such, I would like to help people use almost every data type for a database with minimal changes in the code.

    So where to I start?

    To do this we will be using a thing called an interface. It's basically a set of functions which the target class MUST override and implement. Imagine it as a required behavior.

    Now let's create our mock interface, it won't be that complicated:
    Code (Java):
    public interface DatabaseImplementation {

        User readUserFromDatabase(UUID uuid);

        Set<Mounts> readMountsFromUser(User user);

    }
    (an interface does not hold any work logic (except in some occasions) and generally just defines the method and the args you need to feed in it)

    With this we have 2 methods, readUserFromDatabase(UUID uuid) and readMountsFromUser(User user). You can add as more as you like but I'm just showing 2 because what holds true with 2, holds with an infinite amount of them.

    Now, you can't directly get an interface instance. So using new DatabaseImplementation is a no-go (we want this either way).

    Because of this we need to implement this interface (behavior). Let's say you want to have the end-user decide whether they will be using MySQL, SQLite or JSON for the database types.

    For this we need to define 3 new classes, each one for it's own work logic and data type.

    JSON:
    Code (Java):
    public class JsonImplementation implements DatabaseImplementation {

        @Override
        public User readUserFromDatabase(UUID uuid) {
            // Iplementation which corresponds to JSON
        }

        @Override
        public Set<Mounts> readMountsFromUser(User user) {
            // Iplementation which corresponds to JSON
        }

    }
    MySQL:
    Code (Java):
    public class MySQLImplementation implements DatabaseImplementation {

        @Override
        public User readUserFromDatabase(UUID uuid) {
            // Iplementation which corresponds to MySQL
        }

        @Override
        public Set<Mounts> readMountsFromUser(User user) {
            // Iplementation which corresponds to MySQL
        }

    }
    SQLite:
    Code (Java):
    public class SQLiteImplementation implements DatabaseImplementation {

        @Override
        public User readUserFromDatabase(UUID uuid) {
            // Iplementation which corresponds to SQLite
        }

        @Override
        public Set<Mounts> readMountsFromUser(User user) {
            // Iplementation which corresponds to SQLite
        }

    }
    Now we have 3 classes which correspond to the database types. I put the comment for the work logic it would have. JSON would use Gson for serialization and whatnot and SQLite and MySQL have queries of their own.

    Now how do I use this?

    Well, now because this is an implementation of an interface we will be using a powerful tool of OOP which is called polymorphism.

    We know that these classes are implementations of the previous interface, meaning these classes are basically made to only behave as that interface, or as that interface + something more. Either way these new classes can be seen as an instance of the interface (now you get why we should instance the interface itself). You can say that the interface is the parent class and the child class is the one that implemented the interface.

    Now to use this, let's say we have the main class:
    Code (Java):
    public class MainClass {

        private static DatabaseImplementation implementation = null;

        public void main(String[] args) {
            String type = args.length < 1 ? "" : args[0];

            if (type.equalsIgnoreCase("MySQL"))
                implementation = new MySQLImplementation();
            else if (type.equalsIgnoreCase("SQLite"))
                implementation = new SQLiteImplementation();
            else
                implementation = new JsonImplementation();

            User user = implementation.readUserFromDatabase("My Custom UUID");
            Set<Mounts> mounts = implementation.readMountsFromUser(user);
        }

        public DatabaseImplementation getDatabase() {
            return implementation;
        }

    }
    I hadn't made the main class as a plugin only because it was easier for me to make it as a standard java app but everything holds true.

    Now you can see how this works. First when the main method is called, I get a String where the value is the type of the database I want to use. I can use this to select which type of database I want. When that is selected the new object is stored as a DatabaseImplementation which is a private static variable so I can later access it indirectly from any other class without an instance.

    As you can see at the end of the main class I call implementation.readUserFromDatabase and implementation.readMountsFromUser. This part of the code is agnostic of the type of database I use in my application.

    As you can see I only to an if/else check at the beginning of the code to select which behavior I want but the rest of the code doesn't care.

    I can just call MainClass.getDatabase().readUserFromDatabase(UUID) from any other remote class and won't need to define any checks for the database types.
     
    #1 Zastrix, Mar 18, 2020
    Last edited: Mar 18, 2020
    • Winner Winner x 3
  2. Useful resource, especially for beginners!