Resource [UPDATED] RealmDrive a simple and easy to use database adapter

Discussion in 'Spigot Plugin Development' started by Gerolmed, Aug 30, 2019.

  1. Ive done several updates. Now at 1.2.0
    I hope I updated all information here correctly.

    About
    Currently only supports MongoDB. MySQL support is work in progress. Check this to see the current MySQL support progress. Feel free to implement other databases.

    Features:
    • Easy setup and usage
    • Easy query building that translates into all supported databases
    • Automatic java object <-> database entity conversion
    • Support to define custom serializers for classes. (new) Automatic storage of enums.
    • (new) Supports auto child class conversion (if saved with this automatically converts to child type instead of parent, even in lists)
    • (new/improved) Serialization/Deserialization control using annotations
    • Also features async requests (like #writeAsync(Object, Query, OnSuccess, OnFailure)
    • (new) Annotation to inject parent instance into child check out the docs for examples
    Usage
    It is recommended to use the Releases on Maven Central here

    Include into project

    Include with Maven

    Add the following dependency to your pom.xml
    Code (Text):
    <dependency>
        <groupId>net.endrealm</groupId>
        <artifactId>realm-drive</artifactId>
        <version>1.2.0-RELEASE</version>
        <scope>compile</scope>
    </dependency>
    Choose any version you like. On top of that you will probably want to depend on the driver of the backend you want to use. To keep the RealmDrive light weighted the drivers are not shaded along. Check here to find supported drivers.

    Getting started
    Latest docs are here https://github.com/endrealm/RealmDrive/blob/development/docs/usage.md
    Converting the markdown to BBCode is a pain, so I didn't do that again :D


    Basics

    To get started using RealmDrive you will probably want to know how it works. Here is a simple explanation:
    You can comunicate with the backend through the reader and writer held in a driver service. You additionally have direct access to the backend implementation, but do keep in mind that direct interactions might require backend specific code and will prevent switching the backend later on. Java classes can directly be converted into database entities and saved into the database. For this to work classes must be registered into the conversion handler of the used service. Fields to save must be annotated with @SaveVar. Retrieving data out of the backend is simple as well. Just build a query with the QueryBuilder, do note that some complex query setups might be incompatible with some backends. Retrieved values can be automatically converted into Java classes. Either by handing over a mapped class or by letting the conversion service try to automatically find the right class. Unmapped objects can be interacted with as DriveObject instances.

    Setup

    To get started we will first need a DriveService. The DriveServiceFactory will help out here. First thing we need is a DriveSettings object. The following code shows an example on how the settings for a MongoDB database is setup.
    Code (Java):

    DriveSettings settings = DriveSettings.builder()
                    .type(DriveSettings.BackendType.MONGO_DB)
                    .hostURL("mongodb://localhost:27017")
                    .database("yourDatabase")
                    .table("defaultTable")
                    .build()
     
    The table and database defined here are used as a default/fallback, if no other is specified in the used query.

    Now there is little left to get a simple DriveService implementation
    Code (Java):

    DriveService service = new DriveServiceFactory().getDriveService(settings);
     
    Marking fields

    Now that you have your service you will probably want to start mapping your first classes. As an example we will map the class GreatEntity here.
    Code (Java):

    public class GreatEntity {

    //No-Args constructor is required, when reading values
      public GreatEntity() {
        this.feet = 2;
      }

    public GreatEntity(String entityName, int age, String[] addresses, int feet) {
        this.entityName = entityName;
        this.age = age;
        this.addresses = addresses;
        this.feet = feet;
      }

    @SaveVar
      private String entityName;

    @SaveVar
      private int age;

    @SaveVar
      private String[] addresses;

    private int feet;
    }
     
    Note All variables, but feet are annotated with @SaveVar. Only those annotated will be saved and retrieved.

    Register classes

    Now we will want to go ahead and tell the conversion handler that this class can be saved. This is required to automatically transform database entries to classes. While you will not be able to store them directly into the database you can define serializers that will allow you using them as fields.
    Code (Java):

    //service is the implementation of DriveService
    ConversionHandler conversion = service.getConversionHandler();
    conversion.registerClasses(GreatEntity.class);
     
    Adding custom serializers

    At some point you will probably want to store some of java's various classes (e.g. UUID, Date,...), those of other libraries or just want to change how a specific class is saved in general. The classes Date and UUID are supported by default. Serializers are used according to LIFO so it is possible to overwrite them, by just adding serializers supporting those classes.

    First of all you have to implement Custom Serializer
    Code (Java):

    public class DateSerializer implements CustomSerializer<Date> {

    [code=code]@Override
    public DriveElement toDriveEndpoint(Date element) {
        try {
            return new SimplePrimitiveDriveElement(element.getTime());
        } catch (NotAPrimitiveTypeException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public Date fromEndpoint(DriveElement endpoint) {
        try {
            return new Date(endpoint.getAsLong());
        } catch (NotAPrimitiveTypeException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public boolean supportsClass(Class clazz) {
        return Date.class.equals(clazz);
    }
    }
    [/code]To register it you do almost the same, as you would when registering new classes.
    Code (Java):

    //service is the implementation of DriveService
    ConversionHandler conversion = service.getConversionHandler();
    conversion.registerSerializers(new DateSerializer());
     
    Classes supported by default (current version):
    - UUID
    - Date

    Writing

    Now we are going to write objects to our database. We are going to keep using our example class GreatEntity, our DriveService instance service and the retrieved instance of the ConversionHandler named
    Code (Text):
    conversion
    . From here it is pretty straight forward.

    Simple Write

    This will add an entry to the database. It will try to add the entry, this can cause duplicate entries.
    Code (Java):

    service.getWriter().write(new GreatEntity("Josh", 19, new String[] {"nowhere lol"}, 3));
     
    ##### Overwrite
    When choosing overwrite all entities matching the query will be removed and the data will be added to the database.
    Code (Java):

    Query query = new Query().addEq().setField("entityName").setValue("Josh").close().build();
    service.getWriter().write(new GreatEntity("Josh", 19, new String[] {"nowhere lol"}, 3), true, query);
     
    ##### Deleting
    Delete will delete values matching the query. It will delete the specified amount of entities.
    Code (Java):

    Query query = new Query().addEq().setField("entityName").setValue("Josh").close().build();
    service.getWriter().delete(query, 12); //Deletes twelve entities where entityname == Josh
     
    #### Reading

    Now we are going to read objects from our database. We are going to keep using our example class GreatEntity, our DriveService instance service and the retrieved instance of the ConversionHandler named conversion. From here it is pretty straight forward as well.

    Read One
    Code (Java):


    Query query = new Query().addEq().setField("entityName").setValue("Josh").close().build();
    DriveObject object = service.getReader().readObject(query); //Reads an object as a DriveObject. This can later be converted into a class object via a DriveObject method
    GreatEntity entity = service.getReader().readObject(query, GreatEntity.class);
     
    Read All

    Code (Java):


    Query query = new Query().addEq().setField("entityName").setValue("Josh").close().build();
    Iterable<DriveObject> objects = service.getReader().readAllObjects(query);
    Iterable<GreatEntity> entities = service.getReader().readAllObjects(query, GreatEntity.class);
     
    Writing Queries

    When interacting with the reader and writer you will have to hand over Query objects. Some operations are database specific e.g. MongoDB features embedded documents that can be easily be searched for with setField("child1.name"), while other databases e.g. MySQL don't. Those issues will have to be resolved manually when switching backend.
    Writing queries is pretty easy. Just create a new Query object and append requirements e.g. addAnd(). Each requirement has to be closed with .close(). Although right now not required, be sure to call .build() at the end later versions might require this.
    Example Query:
    Code (Java):

    Query query = new Query()
            .addAnd()
                .addEq()
                    .setField("name")
                    .setValue("James")
                .close()
                .addIn()
                    .setField("age")
                    .addValue(1)
                    .addValue(2)
                    .addValue(40)
                .close()
            .close()
            .addOr()
                .addGt()
                    .setField("height")
                    .setValue(10)
                .close()
            .close()
        .build();
     
    Note:
     
    #1 Gerolmed, Aug 30, 2019
    Last edited: Mar 12, 2020
    • Like Like x 1
  2. One request:
    Create a class based annotation where you can pass a enumeration. This enumeration allows it to have a negative lookahead. So all fields a serialized by default. A field annotation allows it to ignore this field.
     
  3. Good idea. I already planned something similar. I think this issue contains everything you wanted. Not with an enum. Does your idea add something the planned feature does not support?
     
  4. Are relations supported?
     
    • Like Like x 1
  5. No not yet. That was a thing I wanted to add in when the MySQL extensions is implemented.
     
  6. Updated thread to latest version 1.2.0-RELEASE
    Exact details can be found in the first post.