使用HikariCP的连接池是正确的方法

时间:2017-06-16 03:50:51

标签: java connection-pooling hikaricp

我一直在尝试开发一个Minecraft服务器插件,其中玩家输入带有一些数据的命令,数据被发送到数据库,或者从数据库请求一些数据的命令。

它正常工作,直到用户开始使用它多次。我得到了泄漏检测错误:

[HikariPool-2 housekeeper] WARN com.zaxxer.hikari.pool.ProxyLeakTask - Connection leak detection triggered for com.mysql.jdbc.JDBC4Connection@abc6eb, 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);
    }
}

我的数据库类:

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;
    }

}

1 个答案:

答案 0 :(得分:7)

打开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(您不能忘记关闭它)。