数据库连接池
# 数据库连接池 HikariCP
要开启数据库支持,先到构建脚本添加依赖
// ...
dependencies {
// ...
implementation("com.zaxxer:HikariCP:4.0.3")
}
// 通过 shadow 打进 jar 里
// 看个人喜好,在 plugin.yml 声明下载依赖也行
tasks {
shadowJar {
// ...
mapOf(
// ...
"com.zaxxer.hikari" to "hikari",
).forEach { (original, target) ->
relocate(original, "$shadowGroup.$target")
}
}
}
// ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MySQL 和 SQLite 的 JDBC Driver 已经打到 spigot 核心里面了,所以不需要在插件里带上 JDBC Driver。
提示
部分服务端,例如 1.8,自带的 SQLite JDBC 驱动是非常旧的,一些语句可能不支持。
以及一些旧版本服务端自带的 MySQL JDBC 驱动是 5.x 的,不能连接到 MySQL 8.x 的数据库。
做多版本兼容时需要注意这一点。MySQL JDBC 可以通过添加外部依赖库解决,但 SQLite JDBC 不行。
然后将 database.yml (opens new window) 放到 /src/main/resources
,再到主类启用数据库支持
public class PluginMain extends BukkitPlugin {
// 1. 在插件主类构造函数启用数据库支持
public PluginMain() {
super(options() // ...
.database(true)
// 是否在 reloadConfig 时重连数据库
// 避免频繁断连重连,更推荐分离 重载配置文件 和 重连数据库
.reconnectDatabaseWhenReloadConfig(false)
// ...
);
}
// 2. 在 beforeEnable 或之前注册数据库调用器实例
// 如何创建数据库调用器 (IDatabase) 将会在下方说明
//
// 像这样放在主类比较容易调用,无论在哪里都有 plugin 实例可以使用
// 想把它注册成模块也行,看个人喜好
public final ExampleDatabase exampleDatabase;
@Override
protected void beforeEnable() {
options.registerDatabase(
exampleDatabase = new ExampleDatabase(this)
);
// 3. 如需重新连接数据库,可用 options.database().reconnect();
// 4. 如需从线程池拉取数据库连接,可用 this.getConnection();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 创建数据库调用器
package top.mrxiaom.pluginbase.database;
// 1. 实现 IDatabase,其它随意
public class ExampleDatabase extends AbstractPluginHolder implements IDatabase {
String tableName;
// 2. 确保能取到主类示例
public ExampleDatabase(PluginMain plugin) {
super(plugin);
}
// 3. 在 reload 时建表
@Override
public void reload(Connection conn, String tablePrefix) {
tableName = (tablePrefix + "example").toUpperCase();
try (PreparedStatement statement = conn.prepareStatement(
"CREATE TABLE " + tableName + " IF NOT EXISTS example(`foo` int, `bar` int);"
)) {
statement.execute();
} catch (SQLException e) {
warn(e);
}
}
// 4. 执行你想要的其它操作
// plugin.getConnection() 可以从连接池申请一个连接
// 用 try-with-resource 用法,用完连接后自动 close 归还到连接池
public void addEntry(int foo, int bar) {
try (Connection conn = plugin.getConnection()) {
try (PreparedStatement statement = conn.prepareStatement(
"INSERT INTO " + tableName + " (`foo`, `bar`) VALUES (?, ?);"
)) {
statement.setInt(1, foo);
statement.setInt(2, bar);
statement.execute();
}
// 注意! 注意! 注意!
// 不要在 try-catch 块里面再出现 plugin.getConnection() 操作!
// SQLite 只支持一个连接,这么做会导致死锁,即如下情况
//
// 新执行的 plugin.getConnection() 因为连接数量不够,在阻塞等待
// 在上面旧执行的连接因为刚刚在阻塞等待,try 里面的代码没有执行完,导致没有及时将连接归还到连接池
// 一边在等待新连接,一边在等待关闭,锁死在这里了
//
// 最后会因为新执行的 plugin.getConnection() 连接超时而报错
} catch (SQLException e) {
warn(e);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
上次更新: 2025/08/20, 17:50:57