diff --git a/src/de/steamwar/bungeecore/BungeeCore.java b/src/de/steamwar/bungeecore/BungeeCore.java index 7b9d428a..1a4ea14c 100644 --- a/src/de/steamwar/bungeecore/BungeeCore.java +++ b/src/de/steamwar/bungeecore/BungeeCore.java @@ -272,12 +272,6 @@ public class BungeeCore extends Plugin { Persistent.setChatPrefix(CHAT_PREFIX); Persistent.setLobbyServer(LOBBY_SERVER); - Statement.connect( - config.getString("db.url"), - config.getString("db.username"), - config.getString("db.password") - ); - if (config.contains("discord")) { SteamwarDiscordBotConfig.loadConfig(config.getSection("discord")); } diff --git a/src/de/steamwar/bungeecore/ErrorLogger.java b/src/de/steamwar/bungeecore/ErrorLogger.java index 5e4ffdfb..1f38d82b 100644 --- a/src/de/steamwar/bungeecore/ErrorLogger.java +++ b/src/de/steamwar/bungeecore/ErrorLogger.java @@ -20,7 +20,6 @@ package de.steamwar.bungeecore; import de.steamwar.bungeecore.sql.SWException; -import de.steamwar.bungeecore.sql.Statement; import java.io.ByteArrayOutputStream; import java.io.PrintStream; @@ -63,7 +62,7 @@ public class ErrorLogger extends Handler { SWException.log("Bungee", "DDOS", ddosRate + ""); } return; - } else if (!Statement.connectionStable()) { + } else if (stacktrace.contains("ErrorLogger")) { return; } diff --git a/src/de/steamwar/bungeecore/sql/Statement.java b/src/de/steamwar/bungeecore/sql/Statement.java index 1bd849f1..7530c421 100644 --- a/src/de/steamwar/bungeecore/sql/Statement.java +++ b/src/de/steamwar/bungeecore/sql/Statement.java @@ -21,128 +21,167 @@ package de.steamwar.bungeecore.sql; import de.steamwar.bungeecore.BungeeCore; import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.config.Configuration; +import net.md_5.bungee.config.ConfigurationProvider; +import net.md_5.bungee.config.YamlConfiguration; +import java.io.File; +import java.io.IOException; import java.sql.*; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import java.util.logging.Level; +import java.util.logging.Logger; public class Statement implements AutoCloseable { + + private static final Logger logger = BungeeCore.get().getLogger(); + private static final List statements = new ArrayList<>(); + private static final Deque connections = new ArrayDeque<>(); - private static Connection con; - private static String url; - private static String user; - private static String password; + private static final String URL; + private static final String USER; + private static final String PASSWORD; - public static void connect(String url, String user, String password) { - Statement.url = url; - Statement.user = user; - Statement.password = password; + static { + File file = new File(BungeeCore.get().getDataFolder(), "MySQL.yml"); + Configuration config; try { - con = DriverManager.getConnection(url + "?autoReconnect=true&useServerPrepStmts=true", user, password); - } catch (SQLException e) { + config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(file); + } catch (IOException e) { ProxyServer.getInstance().stop(); - throw new SecurityException("Could not start SQL-Connection", e); + throw new SecurityException("", e); } - } - private static void reset() { - closeAll(); - connect(url, user, password); - try { - for (Statement statement : statements) { - statement.init(); - } - } catch (SQLException e) { - throw new SecurityException("Could not reprepare SQL statements", e); - } - } - - @Override - public void close() { - try { - st.close(); - } catch (SQLException e) { - BungeeCore.get().getLogger().log(Level.INFO, "Could not close statement", e); - } - statements.remove(this); + URL = "jdbc:mysql://" + config.getString("HOST") + ":" + config.getString("PORT") + "/" + config.getString("DATABASE"); + USER = config.getString("USER"); + PASSWORD = config.getString("PASSWORD"); } public static void closeAll() { - synchronized (statements) { - for (Statement statement : statements) { + synchronized (connections) { + for(Connection connection : connections) { try { - statement.st.close(); + synchronized (statements) { + statements.forEach(statement -> statement.close(connection)); + } + connection.close(); } catch (SQLException e) { - BungeeCore.get().getLogger().log(Level.INFO, "Could not close statement", e); + logger.log(Level.INFO, "Could not close SQL connection", e); } } - - try { - con.close(); - } catch (SQLException e) { - BungeeCore.get().getLogger().log(Level.INFO, "Could not close SQL connection", e); - } - } - } - - public static boolean connectionStable() { - try { - return !con.isClosed(); - } catch (SQLException e) { - return false; + connections.clear(); } } private final String sql; - private PreparedStatement st; + private final Map cachedStatements = new HashMap<>(); public Statement(String sql) { this.sql = sql; - statements.add(this); - try { - init(); - } catch (SQLException e) { - throw new SecurityException("Could not init SQL statement", e); + synchronized (statements) { + statements.add(this); } } - private void init() throws SQLException { - st = con.prepareStatement(sql); - } - public T select(ResultSetUser user, Object... objects) { - synchronized (statements) { - return prepare(() -> { - ResultSet rs = st.executeQuery(); - T result = user.use(rs); - rs.close(); - return result; - }, objects); - } + return withConnection(st -> { + ResultSet rs = st.executeQuery(); + T result = user.use(rs); + rs.close(); + return result; + }, objects); } public void update(Object... objects) { - synchronized (statements) { - prepare(st::executeUpdate, objects); - } + withConnection(PreparedStatement::executeUpdate, objects); } - private T prepare(SQLRunnable runnable, Object... objects) { + private T withConnection(SQLRunnable runnable, Object... objects) { + Connection connection = aquireConnection(); + + T result; try { - setObjects(objects); - return runnable.run(); + result = tryWithConnection(connection, runnable, objects); } catch (SQLException e) { - reset(); - throw new SecurityException("Could not execute SQL statement", e); + closeConnection(connection); + connection = aquireConnection(); + try { + result = tryWithConnection(connection, runnable, objects); + } catch (SQLException ex) { + closeConnection(connection); + throw new SecurityException("Could not execute statement", ex); + } } + + synchronized (connections) { + connections.push(connection); + } + + return result; } - private void setObjects(Object... objects) throws SQLException { + private T tryWithConnection(Connection connection, SQLRunnable runnable, Object... objects) throws SQLException { + PreparedStatement st = cachedStatements.get(connection); + if(st == null) { + st = connection.prepareStatement(sql); + cachedStatements.put(connection, st); + } + for (int i = 0; i < objects.length; i++) { st.setObject(i + 1, objects[i]); } + + return runnable.run(st); + } + + @Override + public void close() { + cachedStatements.values().forEach(st -> closeStatement(st, false)); + cachedStatements.clear(); + synchronized (statements) { + statements.remove(this); + } + } + + private void close(Connection connection) { + PreparedStatement st = cachedStatements.remove(connection); + if(st != null) + closeStatement(st, true); + } + + private static Connection aquireConnection() { + synchronized (connections) { + if(!connections.isEmpty()) + return connections.pop(); + } + try { + return DriverManager.getConnection(URL + "?autoReconnect=true&useServerPrepStmts=true", USER, PASSWORD); + } catch (SQLException e) { + throw new SecurityException("Could not open connection", e); + } + } + + private static void closeConnection(Connection connection) { + synchronized (statements) { + for (Statement statement : statements) { + statement.close(connection); + } + } + try { + connection.close(); + } catch (SQLException e) { + logger.log(Level.INFO, "Could not close connection", e); + } + } + + private static void closeStatement(PreparedStatement st, boolean silent) { + try { + st.close(); + } catch (SQLException e) { + if(!silent) + logger.log(Level.INFO, "Could not close statement", e); + } } public interface ResultSetUser { @@ -150,6 +189,6 @@ public class Statement implements AutoCloseable { } private interface SQLRunnable { - T run() throws SQLException; + T run(PreparedStatement st) throws SQLException; } }