Skip to main content
Foundation’s SimpleDatabase class provides a unified interface for working with MySQL, MariaDB, and SQLite databases with automatic connection management and data serialization.

Creating a database

Extend SimpleDatabase and implement onConnected():
public class PlayerDatabase extends SimpleDatabase {

    @Override
    protected void onConnected() {
        createTable(TableCreator.of("Players")
            .add("UUID", "VARCHAR(36)")
            .addNotNull("Name", "VARCHAR(16)")
            .add("Coins", "INTEGER")
            .add("LastSeen", "BIGINT")
            .setPrimaryColumn("UUID"));
    }
}

Connecting

MySQL

@Override
protected void onPluginStart() {
    database = new PlayerDatabase();
    database.connect(
        "localhost",    // host
        3306,           // port
        "minecraft",    // database
        "root",         // user
        "password",     // password
        "Players"       // table
    );
}

MariaDB

database.connect(
    "jdbc:mariadb://localhost:3306/minecraft?useSSL=false",
    "root",
    "password",
    "Players"
);

SQLite

database.connect(
    "jdbc:sqlite://database.db"
);

Creating tables

Use TableCreator for type-safe table creation:
createTable(TableCreator.of("Warps")
    .addAutoIncrement("ID", "INTEGER")
    .addNotNull("Name", "VARCHAR(32)")
    .addNotNull("World", "VARCHAR(32)")
    .add("X", "DOUBLE")
    .add("Y", "DOUBLE")
    .add("Z", "DOUBLE")
    .addDefault("Enabled", "BOOLEAN", "true")
    .setPrimaryColumn("ID"));

Column types

add("ColumnName", "VARCHAR(255)")

Inserting data

Single insert

insert("Players", SerializedMap.ofArray(
    "UUID", player.getUniqueId().toString(),
    "Name", player.getName(),
    "Coins", 0,
    "LastSeen", System.currentTimeMillis()
));

Batch insert

List<SerializedMap> batch = new ArrayList<>();

for (Player player : Bukkit.getOnlinePlayers()) {
    batch.add(SerializedMap.ofArray(
        "UUID", player.getUniqueId().toString(),
        "Name", player.getName(),
        "Coins", 0
    ));
}

insertBatch("Players", batch);

Serializable objects

public class PlayerData implements ConfigSerializable {
    private UUID uuid;
    private String name;
    private int coins;
    
    @Override
    public SerializedMap serialize() {
        return SerializedMap.ofArray(
            "UUID", uuid.toString(),
            "Name", name,
            "Coins", coins
        );
    }
}

// Insert directly
insert("Players", playerData);

Querying data

Select all

Common.runAsync(() -> {
    selectAll("Players", resultSet -> {
        UUID uuid = resultSet.getUniqueId("UUID");
        String name = resultSet.getString("Name");
        int coins = resultSet.getInt("Coins");
        
        System.out.println(name + " has " + coins + " coins");
    });
});

With conditions

select("Players", "Coins > 100", resultSet -> {
    String name = resultSet.getString("Name");
    System.out.println("Rich player: " + name);
});

Map conditions

Map<String, Object> conditions = new HashMap<>();
conditions.put("Name", "Notch");
conditions.put("Coins", 9999);

select("Players", conditions, resultSet -> {
    // Results matching both conditions
});

Result set helpers

Basic types

String name = resultSet.getString("Name");
int coins = resultSet.getInt("Coins");
long lastSeen = resultSet.getLong("LastSeen");
boolean active = resultSet.getBoolean("Active");
double balance = resultSet.getDouble("Balance");

Strict getters

Throw exception if value is null:
String name = resultSet.getStringStrict("Name");
UUID uuid = resultSet.getUniqueIdStrict("UUID");
int coins = resultSet.getIntStrict("Coins");

Special types

UUID uuid = resultSet.getUniqueId("UUID");
UUID strict = resultSet.getUniqueIdStrict("UUID");

Updating data

update("UPDATE Players SET Coins = Coins + 100 WHERE UUID = '" 
    + player.getUniqueId() + "'");

Using variables

addVariable("table", "Players");

update("UPDATE {table} SET Coins = 500 WHERE Name = 'Notch'");

Counting rows

int count = count("Players", "Coins", 1000);
System.out.println(count + " players have 1000 coins");

// With multiple conditions
int richPlayers = count("Players", SerializedMap.ofArray(
    "Coins >", 1000,
    "Active", true
));

Raw queries

ResultSet results = query("SELECT * FROM Players WHERE Coins > 500");

while (results.next()) {
    String name = results.getString("Name");
    int coins = results.getInt("Coins");
}

close(results);

Prepared statements

PreparedStatement statement = prepareStatement(
    "SELECT * FROM Players WHERE UUID = ?"
);

statement.setString(1, player.getUniqueId().toString());
ResultSet results = statement.executeQuery();

while (results.next()) {
    // Process results
}

results.close();
statement.close();

Connection management

Check connection

if (database.isConnected()) {
    // Database is ready
}

if (database.isLoaded()) {
    // Connection was established at least once
}

Reconnect

if (!database.isConnected()) {
    database.connectUsingLastCredentials();
}

Close connection

@Override
protected void onPluginStop() {
    database.close();
}

Variables

Define reusable variables for queries:
public PlayerDatabase() {
    addVariable("table", "Players");
    addVariable("prefix", "mc_");
}

// Use in queries
update("UPDATE {table} SET Coins = 100");
select("{prefix}stats", null, resultSet -> {
    // ...
});

Driver configuration

Use HikariCP

SimpleDatabase.setConnectUsingHikari(true); // Default: true

Serialization mode

@Override
protected Mode getTableMode() {
    return SerializeUtil.Mode.JSON; // or Mode.YAML
}

SQLite compatibility

if (isSQLite()) {
    // Use SQLite-specific syntax
} else {
    // Use MySQL/MariaDB syntax
}

Best practices

1

Always run async

Common.runAsync(() -> {
    database.select("Players", null, resultSet -> {
        // Database operation
    });
});
2

Close connections

@Override
protected void onPluginStop() {
    if (database != null && database.isConnected()) {
        database.close();
    }
}
3

Use batch operations

// Instead of multiple inserts
for (Player player : players) {
    insert("Players", data); // Bad
}

// Use batch insert
List<SerializedMap> batch = new ArrayList<>();
for (Player player : players) {
    batch.add(data);
}
insertBatch("Players", batch); // Good
4

Handle errors gracefully

Common.runAsync(() -> {
    try {
        select("Players", null, resultSet -> {
            // Process data
        });
    } catch (Exception e) {
        Common.error(e, "Failed to load player data");
    }
});
All database operations should be run asynchronously to avoid blocking the main thread.
Never use string concatenation for user input in queries - always use prepared statements to prevent SQL injection!

Build docs developers (and LLMs) love