Skip to content

Centralized database and Redis connection pooling for Minecraft plugins

Notifications You must be signed in to change notification settings

bedge117/SharedDB

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 

Repository files navigation

SharedDB

Version: 1.0.0 Compatibility: 1.21 - 1.21.11 (Paper) Java: 21 Author: cd3daddy

Centralized database and Redis connection pooling for Minecraft plugins.

What is SharedDB?

SharedDB provides shared MariaDB (HikariCP) and Redis (Lettuce) connection pools that multiple plugins can use. Instead of each plugin managing its own database and Redis connections, they all share one pool — reducing resource usage, simplifying configuration, and enabling read/write split architectures.

Features

  • Database connection pooling via HikariCP with read/write split (separate read replica and write primary pools)
  • Redis connection pooling via Lettuce with read/write split
  • Simple static API — plugins call SharedDB.redisGet(), SharedDB.getWriteConnection(), etc.
  • Simple Redis API covering strings, hashes, sets, sorted sets, and pub/sub — no need to interact with Lettuce directly
  • Binary Redis API for storing compressed or serialized data
  • Caller tracking and debug logging — see which plugins are making which calls
  • Health monitoring via /shareddb status and /shareddb test
  • All dependencies shaded and relocated — no conflicts with other plugins

Configuration

# config.yml
database:
  # Read connection - point to your local replica for fast reads
  # For single-server setups, use the same host for both read and write
  read:
    host: 127.0.0.1
    port: 3306

  # Write connection - point to your primary database
  write:
    host: 127.0.0.1
    port: 3306

  username: minecraft
  password: "yourpassword"
  parameters: "?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8"
  pool:
    maximum-size: 20
    minimum-idle: 5
    connection-timeout: 10000
    max-lifetime: 1800000
    idle-timeout: 300000
    keepalive-time: 30000

redis:
  password: ""
  timeout: 10000

  # Read connection - point to your local replica for fast reads
  # For single-server setups, use the same host for both read and write
  read:
    host: 127.0.0.1
    port: 6379

  # Write connection - point to your primary Redis
  write:
    host: 127.0.0.1
    port: 6379

  threads:
    io: 4
    computation: 4

For single-server setups, set both read and write hosts to the same address. The read/write split is designed for multi-server or multi-region deployments where you have a primary database/Redis with local read replicas.

Using SharedDB in Your Plugin

Dependency Setup

Maven (pom.xml):

<dependency>
    <groupId>com.c3smp</groupId>
    <artifactId>SharedDB</artifactId>
    <version>1.0.0</version>
    <scope>provided</scope>
</dependency>

Since SharedDB is not published to a Maven repository, install the jar to your local Maven repo:

mvn install:install-file -Dfile=SharedDB-1.0.0.jar -DgroupId=com.c3smp -DartifactId=SharedDB -Dversion=1.0.0 -Dpackaging=jar

Or reference it as a system dependency:

<dependency>
    <groupId>com.c3smp</groupId>
    <artifactId>SharedDB</artifactId>
    <version>1.0.0</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/libs/SharedDB-1.0.0.jar</systemPath>
</dependency>

Gradle (build.gradle.kts):

compileOnly(files("libs/SharedDB-1.0.0.jar"))

plugin.yml:

# Required dependency
depend: [SharedDB]

# Or optional dependency
softdepend: [SharedDB]

API

SharedDB exposes static methods that hide the underlying Lettuce/JDBC complexity.

Database

// Read connection — use for SELECT queries (fast, local replica)
Connection conn = SharedDB.getReadConnection();
Connection conn = SharedDB.getReadConnection("database_name");

// Write connection — use for INSERT/UPDATE/DELETE (primary)
Connection conn = SharedDB.getWriteConnection();
Connection conn = SharedDB.getWriteConnection("database_name");

// IMPORTANT: Always close connections when done (use try-with-resources)
try (Connection conn = SharedDB.getWriteConnection("mydb")) {
    PreparedStatement ps = conn.prepareStatement("INSERT INTO ...");
    ps.executeUpdate();
}

// Check availability
boolean ready = SharedDB.isReady();
boolean dbRead = SharedDB.isDatabaseReadAvailable();
boolean dbWrite = SharedDB.isDatabaseWriteAvailable();
boolean redisRead = SharedDB.isRedisReadAvailable();
boolean redisWrite = SharedDB.isRedisWriteAvailable();

Redis - String Operations

String value = SharedDB.redisGet(key);
SharedDB.redisSet(key, value);
SharedDB.redisSetex(key, seconds, value);
long deleted = SharedDB.redisDel(key1, key2, ...);
boolean exists = SharedDB.redisExists(key);
List<String> keys = SharedDB.redisKeys(pattern);
SharedDB.redisExpire(key, seconds);

Redis - Hash Operations

String value = SharedDB.redisHget(key, field);
boolean isNew = SharedDB.redisHset(key, field, value);
Map<String, String> all = SharedDB.redisHgetall(key);
SharedDB.redisHmset(key, map);
long deleted = SharedDB.redisHdel(key, field1, field2, ...);

Redis - Set Operations

long added = SharedDB.redisSadd(key, member1, member2, ...);
long removed = SharedDB.redisSrem(key, member1, member2, ...);
Set<String> members = SharedDB.redisSmembers(key);

Redis - Sorted Set Operations

long added = SharedDB.redisZadd(key, score, member);
List<String> range = SharedDB.redisZrangeByScore(key, minScore, maxScore);
long removed = SharedDB.redisZremrangeByScore(key, minScore, maxScore);

Redis - Pub/Sub

long receivers = SharedDB.redisPublish(channel, message);
String pong = SharedDB.redisPing();

Redis - Binary Operations

For storing compressed or serialized binary data:

SharedDB.redisSetBytes("mykey", byteArray);
SharedDB.redisSetexBytes("mykey", 3600, byteArray);
byte[] data = SharedDB.redisGetBytes("mykey");
SharedDB.redisDelBytes("mykey");

Redis - Direct Lettuce Access

For advanced use cases like setting up pub/sub listeners:

RedisCommands<String, String> sync = SharedDB.redis();           // read replica
RedisCommands<String, String> writeSync = SharedDB.writeRedis(); // primary
RedisAsyncCommands<String, String> async = SharedDB.redisAsync();       // async read
RedisAsyncCommands<String, String> writeAsync = SharedDB.writeRedisAsync(); // async write

// Pub/sub
StatefulRedisPubSubConnection<String, String> pubsub = SharedDB.pubsub();
// ... set up listeners ...
SharedDB.closePubSub(pubsub); // close when done

Reflection-Based Integration (Optional)

If your plugin shades its own copy of Lettuce or other libraries that SharedDB also shades, you may hit classloader conflicts. In that case, use reflection:

private Class<?> sharedDBClass;

private boolean initSharedDB() {
    try {
        Plugin sharedDBPlugin = Bukkit.getPluginManager().getPlugin("SharedDB");
        if (sharedDBPlugin == null || !sharedDBPlugin.isEnabled()) return false;

        sharedDBClass = Class.forName("com.c3smp.shareddb.SharedDB", true,
            sharedDBPlugin.getClass().getClassLoader());

        String pong = (String) sharedDBClass.getMethod("redisPing").invoke(null);
        getLogger().info("SharedDB connected: " + pong);
        return true;
    } catch (Exception e) {
        getLogger().warning("SharedDB init failed: " + e.getMessage());
        return false;
    }
}

private String redisGet(String key) {
    try {
        return (String) sharedDBClass.getMethod("redisGet", String.class).invoke(null, key);
    } catch (Exception e) { return null; }
}

This is only necessary if you have classloader conflicts. Most plugins can use the direct static API without issues.

Commands

/shareddb status Permission: shareddb.admin Show connection status for all pools.

/shareddb test db Permission: shareddb.admin Test database connection.

/shareddb test redis Permission: shareddb.admin Test Redis connections.

/shareddb test all Permission: shareddb.admin Test all connections.

/shareddb debug on|off Permission: shareddb.admin Toggle debug logging (shows which plugins make which calls).

/shareddb stats Permission: shareddb.admin Show per-plugin usage statistics.

/shareddb clearstats Permission: shareddb.admin Clear usage statistics.

Permissions

shareddb.admin (default: op) — Access to all /shareddb commands.

Technical Notes

MariaDB Driver Version: SharedDB uses MariaDB JDBC 2.7.12 (not 3.x) for compatibility with libraries that use PreparedStatement.setObject() with Long and Date types. MariaDB 3.x is stricter about these type conversions.

Why Reflection? Each Bukkit plugin has its own classloader. When plugins shade libraries (like Lettuce), their shaded classes are incompatible with SharedDB's shaded classes even though they're functionally identical. Using reflection with SharedDB's classloader avoids ClassCastException errors. Most plugins that don't shade Lettuce themselves can use the direct static API.

Pub/Sub Listeners: Use SharedDB.pubsub() to create pub/sub connections. The connection is tracked and will be closed on shutdown, but you should close it yourself when done via SharedDB.closePubSub().

Build

mvn clean package

Output: target/SharedDB-1.0.0.jar

About

Centralized database and Redis connection pooling for Minecraft plugins

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages