以正确的方式使用HikariCP的连接池
我一直在试图开发一个Minecraft服务器插件,其中玩家用某些数据输入命令,将数据发送到数据库,或者从数据库请求某些数据的命令。以正确的方式使用HikariCP的连接池
它正在工作,直到用户开始使用它多次。我得到一个leakdetection错误:
[HikariPool-2 housekeeper] WARN com.zaxxer.hikari.pool.ProxyLeakTask - Connection leak detection triggered for [email protected], stack trace follows
[23:36:11 WARN]: java.lang.Exception: Apparent connection leak detected
或者我得到一个错误,告诉我,我有太多的连接。 (对不起,我目前没有这个错误)
这是我的代码的要点。我在做什么不当?
public class MochaModel {
private Latte instance = Latte.getInstance();
private Connection connection;
public MochaModel() {
}
public void createTable() {
BukkitRunnable r = new BukkitRunnable() {
@Override
public void run() {
try {
connection = Database.getConnection();
if (connection != null) {
String sql = "CREATE TABLE IF NOT EXISTS `mocha` (" +
" `id` INT NOT NULL AUTO_INCREMENT ," +
"`uuid` VARCHAR(255) NOT NULL ," +
" `join_message` VARCHAR(255) NOT NULL ," +
" `quit_message` VARCHAR(255) NOT NULL ," +
" `change_points` INT NOT NULL," +
" `last_modified` TIMESTAMP NOT NULL," +
" PRIMARY KEY (`id`)" +
")";
PreparedStatement q = connection.prepareStatement(sql);
q.executeUpdate();
}
} catch(SQLException e) {
e.printStackTrace();
} finally {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
};
r.runTaskAsynchronously(instance);
}
public void setJoinMessage(String uuid, String message) {
ResultSet rs = getDataWithUUID(uuid);
String[] sqlValues = new String[2];
try {
if (!rs.isBeforeFirst()) {
String insertSql = "INSERT INTO `mocha` (`uuid`, `join_message`,`quit_message`, `change_points`, `last_modified`) VALUES (?, ?, '', 0, CURRENT_TIMESTAMP)";
sqlValues[0] = uuid;
sqlValues[1] = message;
insertData(insertSql, sqlValues);
} else {
while (rs.next()) {
String updateSql = "UPDATE `mocha` SET `join_message`=? WHERE `uuid`=?";
sqlValues[0] = message;
sqlValues[1] = uuid;
updateData(updateSql, sqlValues);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public void setQuitMessage(String uuid, String message) {
ResultSet rs = getDataWithUUID(uuid);
String[] sqlValues = new String[2];
try {
if (!rs.isBeforeFirst()) {
String insertSql = "INSERT INTO `mocha` (`uuid`, `join_message`,`quit_message`, `change_points`, `last_modified`) VALUES (?, '', ?, 0, CURRENT_TIMESTAMP)";
sqlValues[0] = uuid;
sqlValues[1] = message;
insertData(insertSql, sqlValues);
} else {
while (rs.next()) {
String updateSql = "UPDATE `mocha` SET `quit_message`=? WHERE `uuid`=?";
sqlValues[0] = message;
sqlValues[1] = uuid;
updateData(updateSql, sqlValues);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
private void updateData(String sql, String[] sqlValues) {
BukkitRunnable r = new BukkitRunnable() {
@Override
public void run() {
try {
connection = Database.getConnection();
if (connection != null) {
PreparedStatement q = connection.prepareStatement(sql);
q.setString(1, sqlValues[0]);
q.setString(2, sqlValues[1]);
System.out.println(q);
q.executeUpdate();
}
} catch(SQLException e) {
e.printStackTrace();
} finally {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
};
r.runTaskAsynchronously(instance);
}
private void updateChangePointsData(String sql, String[] sqlValues) {
BukkitRunnable r = new BukkitRunnable() {
@Override
public void run() {
try {
connection = Database.getConnection();
if (connection != null) {
PreparedStatement q = connection.prepareStatement(sql);
q.setInt(1, Integer.parseInt(sqlValues[0]));
q.setString(2, sqlValues[1]);
System.out.println(q);
q.executeUpdate();
}
} catch(SQLException e) {
e.printStackTrace();
} finally {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
};
r.runTaskAsynchronously(instance);
}
private void insertData(String sql, String[] sqlValues) {
BukkitRunnable r = new BukkitRunnable() {
@Override
public void run() {
try {
connection = Database.getConnection();
if (connection != null) {
PreparedStatement q = connection.prepareStatement(sql);
q.setString(1, sqlValues[0]);
q.setString(2, sqlValues[1]);
System.out.println(q);
q.executeUpdate();
}
} catch(SQLException e) {
e.printStackTrace();
} finally {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
};
r.runTaskAsynchronously(instance);
}
private ResultSet getDataWithUUID(String uuid) {
ResultSet result = null;
String sqlPlayer = "SELECT * FROM `mocha` WHERE `uuid` = ?";
try {
connection = Database.getConnection();
if (connection != null) {
PreparedStatement q = connection.prepareStatement(sqlPlayer);
q.setString(1, uuid);
result = q.executeQuery();
}
} catch(SQLException e) {
e.printStackTrace();
}
return result;
}
public String getMessage(String uuid, String messageType) {
ResultSet rs = getDataWithUUID(uuid);
String message = null;
try {
if (!rs.isBeforeFirst()) {
message = null;
} else {
while (rs.next()) {
if (messageType.equalsIgnoreCase("getjoin")) {
message = rs.getString("join_message");
} else if (messageType.equalsIgnoreCase("getquit")) {
message = rs.getString("quit_message");
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return message;
}
public int getChangePoints(String uuid) {
ResultSet rs = getDataWithUUID(uuid);
int changePoints = 0;
try {
if (!rs.isBeforeFirst()) {
changePoints = 0;
} else {
while (rs.next()) {
changePoints = rs.getInt("change_points");
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return changePoints;
}
public void removeChangePoints(String uuid, int amount) {
int changePoints = getChangePoints(uuid);
String[] sqlValues = new String[2];
if (changePoints >= amount) {
String updateSql = "UPDATE `mocha` SET `change_points`=? WHERE `uuid`=?";
sqlValues[0] = String.valueOf((changePoints-amount));
sqlValues[1] = uuid;
updateData(updateSql, sqlValues);
}
}
public void addChangePoints(String uuid, int amount) {
int changePoints = getChangePoints(uuid);
String[] sqlValues = new String[2];
String updateSql = "UPDATE `mocha` SET `change_points`=? WHERE `uuid`=?";
sqlValues[0] = String.valueOf((changePoints+amount));
sqlValues[1] = uuid;
updateChangePointsData(updateSql, sqlValues);
}
}
我的DB类:
public class Database {
private static Latte instance = Latte.getInstance();
private static Config config = new Config();
private static HikariConfig dbConfig;
static {
dbConfig = new HikariConfig();
dbConfig.setJdbcUrl("jdbc:mysql://localhost:3306/" + config.get("database.database"));
dbConfig.setUsername(config.get("database.username"));
dbConfig.setPassword(config.get("database.password"));
dbConfig.setDriverClassName("com.mysql.jdbc.Driver");
dbConfig.addDataSourceProperty("cachePrepStmts", "true");
dbConfig.addDataSourceProperty("prepStmtCacheSize", "250");
dbConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
}
private static HikariDataSource ds = new HikariDataSource(dbConfig);
public static Connection getConnection() {
try {
ds.setIdleTimeout(60000);
ds.setConnectionTimeout(60000);
ds.setValidationTimeout(3000);
ds.setLoginTimeout(5);
ds.setMaxLifetime(60000);
ds.setMaximumPoolSize(20);
ds.setLeakDetectionThreshold(5000);
return ds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
当打开一个Connection
你还需要将其关闭。但是,您将Connection
存储在实例变量中。其中,对于代码中的某些路径,可能导致使用多个Connection
实例。由于在实例变量中的存储,只有最后一个使用的将被关闭,所有其他的都被泄露。
取而代之,您希望使其成为本地或隐藏部分复杂性。你可以将你的Database
类改写成这样的东西。
注意:假设Java 8在这里!
public class Database {
private static Latte instance = Latte.getInstance();
private static Config config = new Config();
private static HikariConfig dbConfig;
static {
dbConfig = new HikariConfig();
dbConfig.setJdbcUrl("jdbc:mysql://localhost:3306/" + config.get("database.database"));
dbConfig.setUsername(config.get("database.username"));
dbConfig.setPassword(config.get("database.password"));
dbConfig.setDriverClassName("com.mysql.jdbc.Driver");
dbConfig.addDataSourceProperty("cachePrepStmts", "true");
dbConfig.addDataSourceProperty("prepStmtCacheSize", "250");
dbConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
}
private static HikariDataSource ds = new HikariDataSource(dbConfig);
public static <T> T execute(ConnectionCallback<T> callback) {
try (Connection conn = ds.getConnection()) {
return callback.doInConnection(conn);
} catch (SQLException e) {
throw new IllegalStateException("Error during execution.", e);
}
}
public static interface ConnectionCallback<T> {
public T doInConnection(Connection conn) throws SQLException;
}
}
通知没有更多的getConnection
并且由于尝试与 - 资源的连接将得到自动关闭。
您现在可以调用此方法的实例为ConnectionCallback
,而不是获取Connection
并自行管理它。
现在使用Connection
的代码可以被重构,就像这样。 (注意:没有更多的渔获物,关闭等,所有这一切都是在Database.execute
方法处理。
private void updateData(String sql, String[] sqlValues) {
BukkitRunnable r = new BukkitRunnable() {
@Override
public void run() {
Database.execute((conn) -> {
PreparedStatement q = conn.prepareStatement(sql);
q.setString(1, sqlValues[0]);
q.setString(2, sqlValues[1]);
System.out.println(q);
q.executeUpdate();
}});
};
r.runTaskAsynchronously(instance);
}
每次使用后,该代码将关闭Connection
(你不能忘记关闭它)。
非常感谢你的解释!我试图让你的代码在我的代码中工作,但是我有一个issu e用右花括号。 https://gist.github.com/anonymous/a4900271e808a93bb60cce83e68f63ee 在关闭r.runTask上面的三个括号/大括号...我收到一些错误,这取决于我悬停的错误。缺少退货声明,预计)和;并期待; ..真奇怪的错误,我已经尝试了一切。 – kinx
另外在执行方法中,我收到一个错误,说它缺少一个return语句。 – kinx
我在答案中输入了它,所以可能是对齐关闭:)。 'execute'方法存在一个问题。应该首先将返回值分配给一个值,并在方法结束时返回或重新抛出异常。在其他代码中,应该有一个';',就像我在样本中那样。 –
你存储' Connection'作为一个类的变量,你不应该这样做,因此,你可以打开3个连接,但只关闭最后一个,每次打开一个新的'Connection'时,你都覆盖前一个连接,从而泄漏它 –
谢谢,我该如何正确地做到这一点?这完全有道理。 – kinx