Skip to content

A lightweight async API for Minecraft plugins that removes JDBC boilerplate and provides a clean, fluent multi-database query layer.

License

Notifications You must be signed in to change notification settings

devspexx/CentralDatabase

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

66 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Java CI with Maven Latest Version CodeFactor

A lightweight, fully asynchronous multi-database API for Minecraft plugins.
CentralDatabase removes JDBC boilerplate, provides a safe async query layer,
and exposes a clean fluent API used across your entire plugin ecosystem.

Features

  • Async query execution using CompletableFuture
  • Unified database access layer shared across all plugins
  • HikariCP connection pooling with full config.yml control
  • Fluent query builder (api.query().sql(...).params(...).firstIntegerAsync())
  • Immutable credentials loader + clean API surface
  • Consistent result + error handling via ResponseCode & ResponseData

Architecture overview

YourPlugin → CentralDatabase API → HikariCP → JDBC Driver → Database

Getting Started

CentralDatabase is distributed through JitPack, making it easy to include in any plugin.

Add JitPack to your pom.xml

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

Add the CentralDatabase dependency

<dependency>
    <groupId>com.github.devspexx</groupId>
    <artifactId>CentralDatabase</artifactId>
    <version>2.0</version>
</dependency>

Hooking Into CentralDatabase

Plugins should never store the DatabaseManager permanently. Always access it through the public API:

private CentralDatabaseAPI db;

@Override
public void onEnable() {
    CentralDatabase plugin = (CentralDatabase) getServer()
        .getPluginManager()
        .getPlugin("CentralDatabase");

    if (plugin == null || !plugin.isEnabled()) {
        getLogger().severe("CentralDatabase not available!");
        getServer().getPluginManager().disablePlugin(this);
        return;
    }

    db = plugin.api();
}

Threading Notes

  • Never call .sync() or .get() on the main thread.
  • All DB operations run on a dedicated executor created by CentralDatabase.
  • The plugin ensures safe shutdown of the connection pool.

Fluent API vs Low-Level API

Feature Fluent Query API (db.query()) DatabaseManager API
Convenience ⭐⭐⭐⭐⭐ ⭐⭐⭐
Custom SQL Full Full
Method Chaining Yes No
Typed Shortcuts Yes (firstIntegerAsync()) No
Manual SELECT/UPDATE handling No Yes (via getAsync, updateAsync)

Recommendation:
Use Fluent API for 95% of queries.
Use DatabaseManager only for highly specialized operations.

API Overview

CentralDatabase exposes:

  • CentralDatabaseAPI - Entry point for all plugin queries
  • QueryExecutor - Fluent builder for SQL operations
  • DatabaseManager - Lower-level async SELECT/UPDATE runner
  • ResponseData - Encapsulated SELECT result
  • ResponseCode - Enum describing operation outcome

Using the Fluent Query API

SELECT Example:

db.query()
    .sql("SELECT coins FROM bank WHERE uuid=?")
    .param(playerUuid)
    .firstIntegerAsync()
    .thenAccept(coins -> {
        if (coins == null) {
            getLogger().info("No data found.");
            return;
        }
        getLogger().info("Player coins: " + coins);
    });

UPDATE Example

db.query()
    .sql("UPDATE bank SET coins = coins + ? WHERE uuid=?")
    .params(50, playerUuid)
    .updateAsync()
    .thenAccept(code -> {
        switch (code) {
            case SUCCESS -> getLogger().info("Coins updated.");
            case NO_RESULTS -> getLogger().warning("Player not found.");
            case ERROR -> getLogger().severe("SQL error!");
            case FAILED -> getLogger().severe("Unexpected failure.");
        }
    });

INSERT Example

db.query()
  .sql("INSERT INTO bank (uuid, coins) VALUES (?, ?)")
  .params(playerUuid, 100)
  .setAsync()
  .thenAccept(code -> {
      switch (code) {
          case SUCCESS -> getLogger().info("Account created.");
          case NO_RESULTS -> getLogger().warning("Insert failed — no rows affected.");
          case ERROR -> getLogger().severe("SQL error!");
          case FAILED -> getLogger().severe("Unexpected failure.");
      }
  })
  .exceptionally(ex -> {
      getLogger().severe("Async insert failed: " + ex.getMessage());
      return null;
  });

DELETE Example

db.query()
  .sql("DELETE FROM bank WHERE uuid=?")
  .param(playerUuid)
  .setAsync()
  .thenAccept(code -> {
      switch (code) {
          case SUCCESS -> getLogger().info("Player account removed.");
          case NO_RESULTS -> getLogger().warning("No account found.");
          case ERROR -> getLogger().severe("SQL error!");
          case FAILED -> getLogger().severe("Unexpected failure.");
      }
  });

ERROR HANDLING EXAMPLE

db.query()
  .sql("SELECT * FROM logs WHERE id=?")
  .param(id)
  .async()
  .thenAccept(result -> {
      if (result.isError()) {
          getLogger().severe("SQL error: " + result.status());
          return;
      }
      // handle result
  })
  .exceptionally(ex -> {
      getLogger().severe("Async execution failed: " + ex.getMessage());
      return null;
  });

Response System

Every query uses two high-level response types:

ResponseData: Used for SELECT queries.

Contains:

  • ResponseCode status() List<Map<String,Object>> rows()
  • Typed getters (getFirstString, getFirstInteger, etc.)

ResponseCode: Enum describing the result of any query:

  • SUCCESS - Query ran successfully & returned/updated rows
  • NO_RESULTS - Query succeeded but returned/updated nothing
  • ERROR - SQL or driver-level error
  • FAILED - Unexpected internal failure
  • NOT_INITIALIZED - Database not ready or manager shut down

Example:

db.query()
  .sql("SELECT * FROM players WHERE uuid=?")
  .param(uuid)
  .async()
  .thenAccept(result -> {
      switch (result.status()) {
          case SUCCESS -> { /* handle rows */ }
          case NO_RESULTS -> getLogger().info("Player not found.");
          case ERROR -> getLogger().severe("SQL error.");
          case FAILED -> getLogger().severe("Unexpected failure.");
      }
  });

Configuration (config.yml)

hostname: "localhost"
port: 3306
username: "root"
password: ""
database: "minecraft"

thread-pool-size: 4

# HikariCP
maximum-pool-size: 8
minimum-idle: 2
connection-timeout: 3000
idle-timeout: 300000
max-lifetime: 1800000
leak-detection-threshold: 0
keepalive-time: 300000

# JDBC URL template:
jdbc-url: "jdbc:mysql://{hostname}:{port}/{database}?user={username}&useSSL=false"

HikariCP configurable settings

Key Description
maximum-pool-size Max concurrent DB connections
minimum-idle Min idle connections kept alive
connection-timeout Max wait time before a connection request fails
idle-timeout How long idle connections live
max-lifetime Max lifetime before a connection is recycled
leak-detection-threshold Logs slow connections (debugging)
keepalive-time Periodic keepalive queries

Supported Databases:

You can freely change the jdbc-url template to match any JDBC-compatible driver:

  • MySQL
  • MariaDB
  • PostgreSQL (jdbc:postgresql://{hostname}:{port}/{database}?user={username})
  • SQLite (jdbc:sqlite:{database}.db)
  • SQLServer (jdbc:sqlserver://{hostname}:{port};databaseName={database})
  • H2 (jdbc:h2:./{database})

Feedback & Support

If you have questions or ideas for improvements, feel free to open an issue or reach out to me. Your feedback helps make CentralDatabase better for everyone.

License

MIT — free to use, modify, and distribute.

Thanks for checking out CentralDatabase!
If you have suggestions, find a bug, or want to contribute, feel free to open an issue or pull request.

About

A lightweight async API for Minecraft plugins that removes JDBC boilerplate and provides a clean, fluent multi-database query layer.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages