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
Basic
Not null
Auto increment
Default value
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
Enum
ItemStack
Timestamp
UUID uuid = resultSet . getUniqueId ( "UUID" );
UUID strict = resultSet . getUniqueIdStrict ( "UUID" );
GameMode mode = resultSet . getEnum ( "GameMode" , GameMode . class );
GameMode strict = resultSet . getEnumStrict ( "GameMode" , GameMode . class );
ItemStack item = resultSet . getItem ( "Item" );
ItemStack strict = resultSet . getItemStrict ( "Item" );
ItemStack [] items = resultSet . getItemArray ( "Items" );
long timestamp = resultSet . getTimestamp ( "Created" );
long strict = resultSet . getTimestampStrict ( "Created" );
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
Always run async
Common . runAsync (() -> {
database . select ( "Players" , null , resultSet -> {
// Database operation
});
});
Close connections
@ Override
protected void onPluginStop () {
if (database != null && database . isConnected ()) {
database . close ();
}
}
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
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!