以正确的方式使用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; 
    } 

} 
+1

你存储' Connection'作为一个类的变量,你不应该这样做,因此,你可以打开3个连接,但只关闭最后一个,每次打开一个新的'Connection'时,你都覆盖前一个连接,从而泄漏它 –

+0

谢谢,我该如何正确地做到这一点?这完全有道理。 – kinx

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

+0

非常感谢你的解释!我试图让你的代码在我的代码中工作,但是我有一个issu e用右花括号。 https://gist.github.com/anonymous/a4900271e808a93bb60cce83e68f63ee 在关闭r.runTask上面的三个括号/大括号...我收到一些错误,这取决于我悬停的错误。缺少退货声明,预计)和;并期待; ..真奇怪的错误,我已经尝试了一切。 – kinx

+0

另外在执行方法中,我收到一个错误,说它缺少一个return语句。 – kinx

+0

我在答案中输入了它,所以可能是对齐关闭:)。 'execute'方法存在一个问题。应该首先将返回值分配给一个值,并在方法结束时返回或重新抛出异常。在其他代码中,应该有一个';',就像我在样本中那样。 –