https://github.com/exlll/databaselib
Bukkit and BungeeCord library for asynchronous execution of SQL queries
https://github.com/exlll/databaselib
async asynchronous bukkit bungeecord bungeecord-plugin library minecraft spigot spigot-plugin sql
Last synced: 7 months ago
JSON representation
Bukkit and BungeeCord library for asynchronous execution of SQL queries
- Host: GitHub
- URL: https://github.com/exlll/databaselib
- Owner: Exlll
- License: mit
- Created: 2017-03-27T04:26:15.000Z (about 9 years ago)
- Default Branch: master
- Last Pushed: 2021-10-11T16:21:28.000Z (over 4 years ago)
- Last Synced: 2025-05-07T00:33:11.290Z (11 months ago)
- Topics: async, asynchronous, bukkit, bungeecord, bungeecord-plugin, library, minecraft, spigot, spigot-plugin, sql
- Language: Java
- Homepage:
- Size: 247 KB
- Stars: 12
- Watchers: 5
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# DatabaseLib v3
## How-to
#### Installation
1. Put the .jar in the plugin folder
2. Start the server
3. Stop the server
4. Configure the config.yml and sql_pool.yml
5. Start the server again
#### Usage
1. Create a new class (or use a preexisting one) and `extend PluginSqlTaskSubmitter`.
2. Create a new instance of your class by either passing a `JavaPlugin` (Bukkit) or a
`Plugin` instance (BungeeCord).
Take a look at the examples below for a complete example of a Bukkit plugin.
## General information
#### Callbacks
When you create or submit tasks, you have to pass a non-null callback.
The type of the callback depends on whether the asynchronously executed method
returns something or not. If the method doesn't return anything (i.e. there is no
`return` statement), the type of the callback is `Consumer`.
Otherwise, it is `BiConsumer`.
Some examples:
- if there is no `return` statement, pass a `Consumer`
- if you return an `Integer`, pass a `BiConsumer`
- if you return a `String`, pass a `BiConsumer`
- in general: if you return something of type `R`, pass a `BiConsumer super R, Throwable>`
Usage example
```java
public static final class UserRepo extends PluginSqlTaskSubmitter {
public UserRepo(JavaPlugin plugin) { super(plugin); }
public void deleteUser(UUID uuid, Consumer callback) {
String query = "DELETE FROM `users` WHERE `uuid` = ?";
submitSqlPreparedStatementTask(query, preparedStatement -> {
preparedStatement.setString(1, uuid.toString());
preparedStatement.execute();
}, callback);
}
public void getUser(UUID uuid, BiConsumer callback) {
String query = "SELECT * FROM `users` WHERE `uuid` = ?";
submitSqlPreparedStatementTask(query, preparedStatement -> {
preparedStatement.setString(1, uuid.toString());
ResultSet rs = preparedStatement.executeQuery();
return rs.next()
? new User(uuid.toString(), rs.getString("email"))
: null;
}, callback);
}
}
```
#### Task submission without callbacks (new in 3.1.0)
You can submit tasks which don't require a callback method and which instead return
a `CompletionStage`. The result of the `CompletionStage` is the result of the
function that is given when the task is submitted.
Usage example
```java
public static final class UserRepo extends PluginSqlTaskSubmitter {
public UserRepo(JavaPlugin plugin) { super(plugin); }
public CompletionStage> getUsers() {
String sql = "SELECT * FROM `users`";
return submitSqlPreparedStatementTask(sql, preparedStatement -> {
List users = new ArrayList<>();
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
String uuid = resultSet.getString("uuid");
String name = resultSet.getString("name");
users.add(new User(uuid, name));
}
return users;
});
}
}
```
#### Asynchronous execution of tasks
All tasks that are submitted through one of the different `submit...` methods
are executed asynchronously in whichever thread the library chooses. After
a task completes (either normally or by throwing an exception), its
callback method is executed in the server thread (Bukkit plugins) or in one
of the plugin threads (Bungee plugins). If the task completes
exceptionally, the `Throwable` passed to the callback is non-null.
#### Closing Connections and Statements
All library supplied `Connection`s and `Statement`s are closed automatically,
so you don't have to call `close()` on them. However, you still need to close
all `Statement`s you created yourself (e.g. when using an `SqlConnectionTask`).
The only exception to this rule is when you use `getConnection()` (see next section).
#### Synchronous execution of queries
Sometimes you don't want to execute queries asynchronously. In these cases
you can get a `Connection` directly from the pool by calling `getConnection()`.
**You are responsible for closing the connection after usage.** If you forget
to do so, the pool will run out of `Connection`s, so it's best to use a try-with-resources
block wherever possible when acquiring them this way.
**Be aware that a call to `getConnection()` is blocking.** If no `Connection` is available
(e.g. because of the pool being empty), the thread in which this method
is called will be blocked.
**(new in 3.4.0)** Instead of using a call to `getConnection()` you can also use any of
`apply...` methods. These methods call `getConnection()` for you but try to reduce
boilerplate by catching any occurring `SQLExceptions` and rethrowing them wrapped in
`RuntimeExceptions`.
Usage example
```java
String query = "CREATE TABLE IF NOT EXISTS users(" +
" uuid VARCHAR(36) NOT NULL PRIMARY KEY," +
" email VARCHAR(36)" +
")";
boolean result = applyStatement(statement -> statement.execute(query));
```
#### Creating your own connection pools
If your application submits many long-running tasks, or you have some reason that
makes it necessary for you to manage your own set of `Connection`s, you can instantiate a
connection pool by using the static factory methods of the `SqlConnectionPool` class.
To use these methods you have to pass a `SqlPoolConfig` instance which can be created by
using a `SqlPoolConfig.Builder`. If you want your users to be able to manually configure
your pool from a file, you can use a `SqlPoolConfiguration` to store its options.
#### ScriptRunner
A `ScriptRunner` is a utility class that lets you execute SQL scripts. An SQL script is a
file that contains SQL queries delimited by ';' (semicolon). You can create a `ScriptRunner`
by passing an instance of a `Reader` and a `Connection` to its constructor. A `ScriptRunner`
has the ability to replace any part of a query prior to executing it.
Usage example
```java
try (Reader reader = new FileReader("my_script.sql");
Connection connection = getConnection()) {
ScriptRunner runner = new ScriptRunner(reader, connection);
runner.setReplacements(Map.of("%USER%", "user1"));
runner.runScript();
} catch (IOException | SQLException e) {
e.printStackTrace();
}
```
## Examples
#### Complete Bukkit plugin example
```java
import de.exlll.databaselib.sql.submit.PluginSqlTaskSubmitter;
import org.bukkit.plugin.java.JavaPlugin;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.UUID;
import java.util.concurrent.CompletionStage;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class ExamplePlugin extends JavaPlugin {
private final Logger log = Logger.getLogger(ExamplePlugin.class.getName());
private final Consumer exceptionLogger = throwable -> {
if (throwable != null) {
log.log(Level.SEVERE, throwable.getMessage(), throwable);
}
};
private UserRepository userRepository;
public void deleteUser(UUID uuid) {
userRepository.deleteUser(uuid, exceptionLogger);
}
public void updateUser(UUID uuid, String email) {
userRepository.updateUserMail(uuid, email, exceptionLogger);
}
public void getUser(UUID uuid) {
userRepository.getUser(uuid, (user, throwable) -> {
if (throwable != null) {
exceptionLogger.accept(throwable);
} else {
log.info("User: " + user);
}
});
}
public void getUserWithCompletionStage(UUID uuid) {
userRepository.getUserWithCompletionStage(uuid)
.whenComplete((user, throwable) -> {
if (throwable != null) {
exceptionLogger.accept(throwable);
} else {
log.info("User: " + user);
}
});
}
@Override
public void onEnable() {
userRepository = new UserRepository(this);
userRepository.createTable();
}
}
final class UserRepository extends PluginSqlTaskSubmitter {
public UserRepository(JavaPlugin plugin) {
super(plugin);
}
public void deleteUser(UUID uuid, Consumer callback) {
String query = "DELETE FROM `users` WHERE `uuid` = ?";
submitSqlPreparedStatementTask(query, preparedStatement -> {
preparedStatement.setString(1, uuid.toString());
preparedStatement.execute();
}, callback);
}
public void updateUserMail(UUID uuid, String email, Consumer callback) {
String query = "UPDATE `users` SET `email` = ? WHERE `uuid` = ?";
submitSqlPreparedStatementTask(query, preparedStatement -> {
preparedStatement.setString(1, email);
preparedStatement.setString(2, uuid.toString());
preparedStatement.execute();
}, callback);
}
public void getUser(UUID uuid, BiConsumer callback) {
String query = "SELECT * FROM `users` WHERE `uuid` = ?";
submitSqlPreparedStatementTask(query, preparedStatement -> {
preparedStatement.setString(1, uuid.toString());
ResultSet rs = preparedStatement.executeQuery();
return rs.next()
? new User(uuid, rs.getString("email"))
: null;
}, callback);
}
public CompletionStage getUserWithCompletionStage(UUID uuid) {
String query = "SELECT * FROM `users` WHERE `uuid` = ?";
return submitSqlPreparedStatementTask(query, preparedStatement -> {
preparedStatement.setString(1, uuid.toString());
ResultSet rs = preparedStatement.executeQuery();
return rs.next()
? new User(uuid, rs.getString("email"))
: null;
});
}
public void createTable() {
// get Connection synchronously, blocks if no Connection available
try (Connection connection = getConnection();
Statement stmt = connection.createStatement()) {
stmt.execute("CREATE TABLE IF NOT EXISTS `users` (" +
"`uuid` VARCHAR(36) PRIMARY KEY," +
"`email` VARCHAR(36))");
} catch (SQLException e) {
e.printStackTrace();
}
}
static class User {
private final UUID uuid;
private final String email;
User(UUID uuid, String email) {
this.uuid = uuid;
this.email = email;
}
}
}
```
#### Ways to submit tasks
```java
final class ExampleSubmitter extends PluginSqlTaskSubmitter {
private final Consumer exceptionLogger = throwable -> {
if (throwable != null) {
plugin.getLogger().log(Level.SEVERE, throwable.getMessage(), throwable);
}
};
private final BiConsumer resultExceptionLogger =
(result, throwable) -> {
if (throwable != null) {
plugin.getLogger().log(Level.SEVERE, throwable.getMessage(), throwable);
} else {
plugin.getLogger().info("result: " + result);
}
};
public ExampleSubmitter(JavaPlugin plugin) {
super(plugin);
}
/* You can provide your own SqlConnectionPool */
public ExampleSubmitter(JavaPlugin plugin, SqlConnectionPool connectionPool) {
super(plugin, connectionPool);
}
public void submittingTasksWhichDoNotReturnResults() {
submitSqlStatementTask(connection -> {
// ...do something with the connection
}, exceptionLogger);
submitSqlStatementTask(statement -> {
// ...do something with the statement
}, exceptionLogger);
String query = "UPDATE ...";
submitSqlPreparedStatementTask(query, preparedStatement -> {
// ...do something with the preparedStatement
}, exceptionLogger);
String call = "{call ... }";
submitSqlCallableStatementTask(call, callableStatement -> {
// ...do something with the callableStatement
}, exceptionLogger);
}
public void submittingTasksWhichReturnResults() {
submitSqlConnectionTask(connection -> {
// ...do something with the connection
return "1";
}, resultExceptionLogger);
submitSqlStatementTask(statement -> {
// ...do something with the statement
return "2";
}, resultExceptionLogger);
String query = "SELECT * FROM ...";
submitSqlPreparedStatementTask(query, preparedStatement -> {
// ...do something with the preparedStatement
return "3";
}, resultExceptionLogger);
String call = "{call ... }";
submitSqlCallableStatementTask(call, callableStatement -> {
// ...do something with the callableStatement
return "4";
}, resultExceptionLogger);
}
public void submittingTasksWhichReturnCompletionStages() {
submitSqlConnectionTask(connection -> {
// ...do something with the connection
return "1";
});
submitSqlStatementTask(statement -> {
// ...do something with the statement
return "2";
});
String query = "SELECT * FROM ...";
submitSqlPreparedStatementTask(query, preparedStatement -> {
// ...do something with the preparedStatement
return "3";
});
String call = "{call ... }";
submitSqlCallableStatementTask(call, callableStatement -> {
// ...do something with the callableStatement
return "4";
});
}
}
```
## Import
#### Maven
```xml
de.exlll
https://maven.pkg.github.com/Exlll/DatabaseLib
de.exlll
databaselib-bukkit
3.4.0
de.exlll
databaselib-bungee
3.4.0
```
#### Gradle
```groovy
repositories { maven { url 'https://maven.pkg.github.com/Exlll/DatabaseLib' } }
dependencies {
// for Bukkit plugins
implementation group: 'de.exlll', name: 'databaselib-bukkit', version: '3.4.0'
// for Bungee plugins
implementation group: 'de.exlll', name: 'databaselib-bungee', version: '3.4.0'
}
```
```kotlin
repositories { maven { url = uri("https://maven.pkg.github.com/Exlll/DatabaseLib") } }
dependencies {
// for Bukkit plugins
implementation("de.exlll:databaselib-bukkit:3.4.0")
// for Bungee plugins
implementation("de.exlll:databaselib-bungee:3.4.0")
}
```
Additionally, you either have to import the Bukkit or BungeeCord API
or disable transitive lookups. This project uses both of these APIs, so if you
need an example of how to import them using Gradle, take a look at the `build.gradle.kts`.