commonDb #21

Zusammengeführt
Lixfel hat 21 Commits von commonDb nach master 2022-11-22 11:34:05 +01:00 zusammengeführt
4 geänderte Dateien mit 296 neuen und 0 gelöschten Zeilen
Nur Änderungen aus Commit 6d29507973 werden angezeigt - Alle Commits anzeigen

Datei anzeigen

@ -81,6 +81,8 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.hamcrest:hamcrest:2.2'
implementation 'org.xerial:sqlite-jdbc:3.36.0'
}
task buildProject {

Datei anzeigen

@ -0,0 +1,34 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2022 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar;
import java.lang.reflect.InvocationTargetException;
public class ImplementationProvider {
private ImplementationProvider() {}
public static <T> T getImpl(String className) {
try {
return (T) Class.forName(className).getDeclaredConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) {
throw new SecurityException("Could not load SQLConfigProviderImpl", e);
}
}
}

Datei anzeigen

@ -0,0 +1,34 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2022 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.sql;
import de.steamwar.ImplementationProvider;
import java.util.logging.Logger;
public interface SQLConfig {
SQLConfig impl = ImplementationProvider.getImpl("de.steamwar.sql.SQLConfigImpl");
Logger getLogger();
int maxConnections();
}

Datei anzeigen

@ -0,0 +1,226 @@
/*
This file is a part of the SteamWar software.
Copyright (C) 2022 SteamWar.de-Serverteam
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.sql;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.sql.*;
import java.util.*;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Statement implements AutoCloseable {
private static final Logger logger = SQLConfig.impl.getLogger();
private static final List<Statement> statements = new ArrayList<>();
private static final Deque<Connection> connections = new ArrayDeque<>();
private static final int MAX_CONNECTIONS;
private static final Supplier<Connection> conProvider;
static {
File file = new File(new File("plugins", "SpigotCore"), "mysql.properties");
if(file.exists()) {
Properties properties = new Properties();
try {
properties.load(new FileReader(file));
} catch (IOException e) {
throw new SecurityException("Could not load SQL connection", e);
}
String url = "jdbc:mysql://" + properties.getProperty("host") + ":" + properties.getProperty("port") + "/" + properties.getProperty("database") + "?autoReconnect=true&useServerPrepStmts=true";
String user = properties.getProperty("user");
String password = properties.getProperty("password");
MAX_CONNECTIONS = SQLConfig.impl.maxConnections();
conProvider = () -> {
try {
return DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
throw new SecurityException("Could not create MySQL connection", e);
}
};
} else {
MAX_CONNECTIONS = 1;
Connection connection;
try {
connection = DriverManager.getConnection("jdbc:sqlite:standalone.db");
} catch (SQLException e) {
throw new SecurityException("Could not create sqlite connection", e);
}
//TODO ensure schema
conProvider = () -> connection;
}
}
private static int connectionBudget = MAX_CONNECTIONS;
public static void closeAll() {
synchronized (connections) {
while(connectionBudget < MAX_CONNECTIONS) {
if(connections.isEmpty())
waitOnConnections();
else
closeConnection(aquireConnection());
}
}
}
private final String sql;
private final Map<Connection, PreparedStatement> cachedStatements = new HashMap<>();
public Statement(String sql) {
this.sql = sql;
synchronized (statements) {
statements.add(this);
}
}
public <T> T select(ResultSetUser<T> user, Object... objects) {
return withConnection(st -> {
ResultSet rs = st.executeQuery();
T result = user.use(rs);
rs.close();
return result;
}, objects);
}
public void update(Object... objects) {
withConnection(PreparedStatement::executeUpdate, objects);
}
private <T> T withConnection(SQLRunnable<T> runnable, Object... objects) {
Connection connection = aquireConnection();
T result;
try {
result = tryWithConnection(connection, runnable, objects);
} catch (SQLException 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);
connections.notify();
}
return result;
}
private <T> T tryWithConnection(Connection connection, SQLRunnable<T> 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() && connectionBudget == 0)
waitOnConnections();
if(!connections.isEmpty()) {
return connections.pop();
} else {
Connection connection = conProvider.get();
connectionBudget--;
return connection;
}
}
}
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);
}
synchronized (connections) {
connectionBudget++;
connections.notify();
}
}
private static void waitOnConnections() {
synchronized (connections) {
try {
connections.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
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<T> {
T use(ResultSet rs) throws SQLException;
}
private interface SQLRunnable<T> {
T run(PreparedStatement st) throws SQLException;
}
}