Skip to content

4. Database System

Tebrox edited this page Sep 3, 2026 · 2 revisions

Database System

VertexCore provides a unified abstraction layer for persistent plugin data.

Plugins interact with a common API while the underlying storage backend is selected through configuration.

Supported backends:

  • JSON (flat file)
  • H2
  • MySQL / MariaDB

Data Model

VertexCore stores data objects as JSON.

Only fields annotated with @DbExpose are serialized.

Example Data Object

import de.tebrox.vertexCore.database.DataObject;
import de.tebrox.vertexCore.database.annotation.DbExpose;

import java.util.UUID;

public class PlayerProfile implements DataObject {

    private String uniqueId;

    @DbExpose
    public UUID playerUuid;

    @DbExpose
    public int coins;

    // Required by the JSON codec
    public PlayerProfile() {
    }

    public PlayerProfile(UUID playerUuid, int coins) {
        this.playerUuid = playerUuid;
        this.coins = coins;
    }

    @Override
    public String getUniqueId() {
        return uniqueId;
    }

    @Override
    public void setUniqueId(String uniqueId) {
        this.uniqueId = uniqueId;
    }
}

Config-driven Database Settings

The recommended approach is to combine ConfigObject and DatabaseSettings in a single class.

This keeps configuration and database behavior together without requiring additional mapping or wrapper classes.

Database Settings as ConfigObject

import de.tebrox.vertexCore.config.ConfigObject;
import de.tebrox.vertexCore.config.annotation.*;
import de.tebrox.vertexCore.database.DatabaseSettings;

@StoreAt("database.yml")
@ConfigComment("Database configuration for MyPlugin")
public final class MyDbSettings implements DatabaseSettings, ConfigObject {

    @ConfigKey("backend")
    @AllowedValues({"json", "h2", "mysql"})
    @ConfigComment("Storage backend used by VertexCore")
    public String backend = "json";

    @ConfigKey("table-prefix")
    @ConfigComment("Prefix for database tables")
    public String tablePrefix = "myplugin_";

    @ConfigKey("mysql.url")
    @ConfigComment("JDBC URL (only used if backend=mysql)")
    public String mysqlUrl =
            "jdbc:mysql://localhost:3306/test?useSSL=false";

    @ConfigKey("mysql.user")
    public String mysqlUser = "root";

    @ConfigKey("mysql.password")
    public String mysqlPassword = "password";

    @Override
    public String backend() {
        return backend;
    }

    @Override
    public String tablePrefix() {
        return tablePrefix;
    }

    @Override
    public String mysqlUrl() {
        return mysqlUrl;
    }

    @Override
    public String mysqlUser() {
        return mysqlUser;
    }

    @Override
    public String mysqlPassword() {
        return mysqlPassword;
    }
}

DatabaseSettings also provides optional defaults for:

  • Queue usage
  • Operation timeout
  • Connection pool size

These can be overridden when required.


Loading the Database Config

import de.tebrox.vertexCore.config.Config;
import de.tebrox.vertexCore.database.Database;
import org.bukkit.plugin.java.JavaPlugin;

public final class MyPlugin extends JavaPlugin {

    private Config<MyDbSettings> dbConfig;
    private MyDbSettings dbSettings;
    private Database<PlayerProfile> database;

    @Override
    public void onEnable() {
        this.dbConfig = new Config<>(this, MyDbSettings.class);
        this.dbSettings = this.dbConfig.loadConfigObject();

        this.database = new Database<>(
            this,
            dbSettings,
            PlayerProfile.class
        );
    }
}

The database configuration file is created automatically on first startup.


Basic Usage

Save an Object

PlayerProfile profile =
        new PlayerProfile(playerUuid, 100);

profile.setUniqueId(playerUuid.toString());

database.saveObject(profile);

Load an Object

PlayerProfile loaded =
        database.loadObject(playerUuid.toString());

if (loaded != null) {
    int coins = loaded.coins;
}

Check Whether an Object Exists

boolean exists =
        database.objectExists(playerUuid.toString());

Delete an Object

database.deleteObject(playerUuid.toString());

Load All Objects

List<PlayerProfile> profiles =
        database.loadObjects();

Async Usage

VertexCore provides asynchronous helpers based on CompletableFuture.

Load an Object Asynchronously

database.loadObjectAsync(playerUuid.toString())
    .thenAccept(profile -> {
        if (profile != null) {
            getLogger().info("Coins: " + profile.coins);
        }
    })
    .exceptionally(err -> {
        err.printStackTrace();
        return null;
    });

Main-thread Callbacks

If the result needs to be processed on the server's main thread:

database.loadObjectAsyncMain(
    playerUuid.toString(),
    profile -> {
        if (profile != null) {
            // Executed on the main thread
        }
    },
    Throwable::printStackTrace
);

VertexCore also provides corresponding helpers for saving objects and loading collections.


Tracked Writes

VertexCore 1.1.0 provides tracked database writes for cases where a plugin needs more information about the actual outcome of a write operation.

DatabaseWriteOperation operation =
        database.saveObjectTrackedAsync(profile);

Tracked writes expose the underlying write operation and its result instead of only providing a caller-facing completion state.

The normal APIs remain available:

database.saveObject(profile);

and:

database.saveObjectAsync(profile);

For most plugins, these standard methods are sufficient.

Tracked writes are primarily useful when stronger write-state handling or recovery logic is required.


Reconciliation

VertexCore can reconcile the state of a previously tracked write for a specific object key.

database.reconcileObjectAsync(playerUuid.toString())
    .thenAccept(result -> {
        // Inspect reconciliation result
    });

This is useful for handling situations where the caller could not immediately determine whether a database write was committed.


Migration Support

To make a plugin available to VertexCore's migration commands, register its database settings and data classes.

import de.tebrox.vertexCore.VertexCoreApi;

VertexCoreApi.get().registry().register(
    this,
    () -> dbSettings,
    PlayerProfile.class
);

After registration, the plugin becomes selectable by VertexCore's migration system.


Closing Database Resources

Database implements AutoCloseable.

A database instance can therefore be closed when the owning plugin no longer needs it:

database.close();

VertexCore then closes resources associated with the owning plugin.


Next Steps