From 818ffe28cf12e5d9dfe67e5c22860038f430d465 Mon Sep 17 00:00:00 2001 From: xHyroM Date: Mon, 28 Aug 2023 22:17:58 +0200 Subject: [PATCH] feat: mysql [wip] --- .../common/dependencies/Dependency.java | 27 ++++-- .../dependencies/DependencyRegistry.java | 2 + .../common/storage/StorageFactory.java | 12 +++ .../storage/provider/sql/SqlStatements.java | 2 +- .../DriverBasedHikariConnectionFactory.java | 46 +++++++++ .../hikari/HikariConnectionFactory.java | 94 ++++++++++++++++--- .../hikari/MariaDBConnectionFactory.java | 32 +++++++ .../hikari/MySQLConnectionFactory.java | 54 +++++++++++ common/src/main/resources/schema/mariadb.sql | 13 +++ 9 files changed, 262 insertions(+), 20 deletions(-) create mode 100644 common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/DriverBasedHikariConnectionFactory.java create mode 100644 common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MariaDBConnectionFactory.java create mode 100644 common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MySQLConnectionFactory.java create mode 100644 common/src/main/resources/schema/mariadb.sql diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/dependencies/Dependency.java b/common/src/main/java/dev/xhyrom/lighteco/common/dependencies/Dependency.java index f7a4158..d9acc51 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/dependencies/Dependency.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/dependencies/Dependency.java @@ -4,14 +4,29 @@ import lombok.Getter; public enum Dependency { H2_DRIVER( - "com.h2database", - "h2", - "2.1.214" + "com.h2database", + "h2", + "2.1.214" ), SQLITE_DRIVER( - "org.xerial", - "sqlite-jdbc", - "3.28.0" + "org.xerial", + "sqlite-jdbc", + "3.28.0" + ), + MARIADB_DRIVER( + "org.mariadb.jdbc", + "mariadb-java-client", + "3.1.3" + ), + MYSQL_DRIVER( + "mysql", + "mysql-connector-java", + "8.0.23" + ), + POSTGRESQL_DRIVER( + "org.postgresql", + "postgresql", + "42.6.0" ); @Getter diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/dependencies/DependencyRegistry.java b/common/src/main/java/dev/xhyrom/lighteco/common/dependencies/DependencyRegistry.java index 5f5b30d..08d2d27 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/dependencies/DependencyRegistry.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/dependencies/DependencyRegistry.java @@ -11,6 +11,8 @@ public class DependencyRegistry { private static final SetMultimap STORAGE_DEPENDENCIES = ImmutableSetMultimap.builder() .putAll(StorageType.SQLITE, Dependency.SQLITE_DRIVER) .putAll(StorageType.H2, Dependency.H2_DRIVER) + .putAll(StorageType.MYSQL, Dependency.MYSQL_DRIVER) + .putAll(StorageType.MARIADB, Dependency.MARIADB_DRIVER) .build(); public Set resolveStorageDependencies(Set types) { diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/StorageFactory.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/StorageFactory.java index 299ae78..efd0f41 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/storage/StorageFactory.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/StorageFactory.java @@ -7,6 +7,8 @@ import dev.xhyrom.lighteco.common.storage.provider.memory.MemoryStorageProvider; import dev.xhyrom.lighteco.common.storage.provider.sql.SqlStorageProvider; import dev.xhyrom.lighteco.common.storage.provider.sql.connection.file.H2ConnectionFactory; import dev.xhyrom.lighteco.common.storage.provider.sql.connection.file.SqliteConnectionFactory; +import dev.xhyrom.lighteco.common.storage.provider.sql.connection.hikari.MariaDBConnectionFactory; +import dev.xhyrom.lighteco.common.storage.provider.sql.connection.hikari.MySQLConnectionFactory; import java.util.Set; @@ -45,6 +47,16 @@ public class StorageFactory { this.plugin, new SqliteConnectionFactory(this.plugin.getBootstrap().getDataDirectory().resolve("lighteco-sqlite.db")) ); + case "mysql": + return new SqlStorageProvider( + this.plugin, + new MySQLConnectionFactory(this.plugin.getConfig().storage.data) + ); + case "mariadb": + return new SqlStorageProvider( + this.plugin, + new MariaDBConnectionFactory(this.plugin.getConfig().storage.data) + ); default: throw new IllegalArgumentException("Unknown storage provider: " + provider); } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SqlStatements.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SqlStatements.java index 1100855..8c56a0a 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SqlStatements.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/SqlStatements.java @@ -25,7 +25,7 @@ public enum SqlStatements { case SQLITE -> { return this.sqlite; } - case H2, MYSQL -> { + case H2, MYSQL, MARIADB -> { return this.mysql; } } diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/DriverBasedHikariConnectionFactory.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/DriverBasedHikariConnectionFactory.java new file mode 100644 index 0000000..45bb553 --- /dev/null +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/DriverBasedHikariConnectionFactory.java @@ -0,0 +1,46 @@ +package dev.xhyrom.lighteco.common.storage.provider.sql.connection.hikari; + +import com.zaxxer.hikari.HikariConfig; +import dev.xhyrom.lighteco.common.config.storage.StorageDataConfig; + +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Enumeration; + +public abstract class DriverBasedHikariConnectionFactory extends HikariConnectionFactory { + protected DriverBasedHikariConnectionFactory(StorageDataConfig configuration) { + super(configuration); + } + + protected abstract String driverClassName(); + + protected abstract String driverJdbcIdentifier(); + + @Override + protected void configureDatabase(HikariConfig config, String address, String port, String databaseName, String username, String password) { + config.setDriverClassName(driverClassName()); + config.setJdbcUrl(String.format("jdbc:%s://%s:%s/%s", driverJdbcIdentifier(), address, port, databaseName)); + config.setUsername(username); + config.setPassword(password); + } + + @Override + protected void postInitialize() { + super.postInitialize(); + + deregisterDriver(driverClassName()); + } + + private static void deregisterDriver(String driverClassName) { + Enumeration drivers = DriverManager.getDrivers(); + while (drivers.hasMoreElements()) { + Driver driver = drivers.nextElement(); + if (driver.getClass().getName().equals(driverClassName)) { + try { + DriverManager.deregisterDriver(driver); + } catch (SQLException ignored) {} + } + } + } +} diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/HikariConnectionFactory.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/HikariConnectionFactory.java index 1e58d20..d480431 100644 --- a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/HikariConnectionFactory.java +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/HikariConnectionFactory.java @@ -1,36 +1,104 @@ package dev.xhyrom.lighteco.common.storage.provider.sql.connection.hikari; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import dev.xhyrom.lighteco.common.config.storage.StorageDataConfig; import dev.xhyrom.lighteco.common.plugin.LightEcoPlugin; -import dev.xhyrom.lighteco.common.storage.StorageType; import dev.xhyrom.lighteco.common.storage.provider.sql.connection.ConnectionFactory; import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.function.Function; -public class HikariConnectionFactory implements ConnectionFactory { +public abstract class HikariConnectionFactory implements ConnectionFactory { + private final StorageDataConfig configuration; + private HikariDataSource hikari; + + public HikariConnectionFactory(StorageDataConfig configuration) { + this.configuration = configuration; + } + + protected abstract String defaultPort(); + + protected abstract void configureDatabase(HikariConfig config, String address, String port, String databaseName, String username, String password); + + protected void overrideProperties(Map properties) { + // https://github.com/brettwooldridge/HikariCP/wiki/Rapid-Recovery + properties.putIfAbsent("socketTimeout", String.valueOf(TimeUnit.SECONDS.toMillis(30))); + } + + protected void setProperties(HikariConfig config, Map properties) { + for (Map.Entry property : properties.entrySet()) { + config.addDataSourceProperty(property.getKey(), property.getValue()); + } + } + + /** + * Called after the Hikari pool has been initialised + */ + protected void postInitialize() { - @Override - public StorageType getImplementationName() { - return null; } @Override public void init(LightEcoPlugin plugin) { + HikariConfig config = new HikariConfig(); + // set pool name so the logging output can be linked back to us + config.setPoolName("lighteco-hikari"); + + // get the database info/credentials from the config file + String[] addressSplit = this.configuration.address.split(":"); + String address = addressSplit[0]; + String port = addressSplit.length > 1 ? addressSplit[1] : defaultPort(); + + // allow the implementation to configure the HikariConfig appropriately with these values + configureDatabase(config, address, port, this.configuration.database, this.configuration.username, this.configuration.password); + + Map properties = new HashMap<>(); + + this.overrideProperties(properties); + this.setProperties(config, properties); + + // configure the connection pool +// config.setMaximumPoolSize(1); +// config.setMinimumIdle(10); +// config.setMaxLifetime(1800000); +// config.setKeepaliveTime(0); +// config.setConnectionTimeout(5000); +// config.setInitializationFailTimeout(-1); + + this.hikari = new HikariDataSource(config); + + postInitialize(); } @Override - public void shutdown() throws Exception { + public void shutdown() { + if (this.hikari != null) { + this.hikari.close(); + } + } + @Override + public Connection getConnection() throws SQLException { + if (this.hikari == null) { + throw new SQLException("Unable to get a connection from the pool. (hikari is null)"); + } + + Connection connection = this.hikari.getConnection(); + if (connection == null) { + throw new SQLException("Unable to get a connection from the pool. (getConnection returned null)"); + } + + return connection; } @Override public Function getStatementProcessor() { - return null; + return s -> s.replace('\'', '`'); } - - @Override - public Connection getConnection() throws Exception { - return null; - } -} +} \ No newline at end of file diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MariaDBConnectionFactory.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MariaDBConnectionFactory.java new file mode 100644 index 0000000..37b8eaa --- /dev/null +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MariaDBConnectionFactory.java @@ -0,0 +1,32 @@ +package dev.xhyrom.lighteco.common.storage.provider.sql.connection.hikari; + +import dev.xhyrom.lighteco.common.config.storage.StorageDataConfig; +import dev.xhyrom.lighteco.common.storage.StorageType; + +import java.util.Map; + +public class MariaDBConnectionFactory extends DriverBasedHikariConnectionFactory { + public MariaDBConnectionFactory(StorageDataConfig configuration) { + super(configuration); + } + + @Override + public StorageType getImplementationName() { + return StorageType.MARIADB; + } + + @Override + protected String defaultPort() { + return "3306"; + } + + @Override + protected String driverClassName() { + return "org.mariadb.jdbc.Driver"; + } + + @Override + protected String driverJdbcIdentifier() { + return "mariadb"; + } +} diff --git a/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MySQLConnectionFactory.java b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MySQLConnectionFactory.java new file mode 100644 index 0000000..d5f8d0b --- /dev/null +++ b/common/src/main/java/dev/xhyrom/lighteco/common/storage/provider/sql/connection/hikari/MySQLConnectionFactory.java @@ -0,0 +1,54 @@ +package dev.xhyrom.lighteco.common.storage.provider.sql.connection.hikari; + +import dev.xhyrom.lighteco.common.config.storage.StorageDataConfig; +import dev.xhyrom.lighteco.common.storage.StorageType; + +import java.util.Map; + +public class MySQLConnectionFactory extends DriverBasedHikariConnectionFactory { + public MySQLConnectionFactory(StorageDataConfig configuration) { + super(configuration); + } + + @Override + public StorageType getImplementationName() { + return StorageType.MYSQL; + } + + @Override + protected String defaultPort() { + return "3306"; + } + + @Override + protected String driverClassName() { + return "com.mysql.cj.jdbc.Driver"; + } + + @Override + protected String driverJdbcIdentifier() { + return "mysql"; + } + + @Override + protected void overrideProperties(Map properties) { + // https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration + properties.putIfAbsent("cachePrepStmts", "true"); + properties.putIfAbsent("prepStmtCacheSize", "250"); + properties.putIfAbsent("prepStmtCacheSqlLimit", "2048"); + properties.putIfAbsent("useServerPrepStmts", "true"); + properties.putIfAbsent("useLocalSessionState", "true"); + properties.putIfAbsent("rewriteBatchedStatements", "true"); + properties.putIfAbsent("cacheResultSetMetadata", "true"); + properties.putIfAbsent("cacheServerConfiguration", "true"); + properties.putIfAbsent("elideSetAutoCommits", "true"); + properties.putIfAbsent("maintainTimeStats", "false"); + properties.putIfAbsent("alwaysSendSetIsolation", "false"); + properties.putIfAbsent("cacheCallableStmts", "true"); + + // https://stackoverflow.com/a/54256150 + // It's not super important which timezone we pick, because we don't use time-based + // data types in any of our schemas/queries. + properties.putIfAbsent("serverTimezone", "UTC"); + } +} diff --git a/common/src/main/resources/schema/mariadb.sql b/common/src/main/resources/schema/mariadb.sql new file mode 100644 index 0000000..8e45fcf --- /dev/null +++ b/common/src/main/resources/schema/mariadb.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS `{prefix}_users` ( + `uuid` VARCHAR(36) NOT NULL, + `currency_identifier` VARCHAR(255) NOT NULL, + `balance` DECIMAL(10, 2) NOT NULL, + PRIMARY KEY (`uuid`, `currency_identifier`) +); + +CREATE TABLE IF NOT EXISTS `{prefix}_{context}_users` ( + `uuid` VARCHAR(36) NOT NULL, + `currency_identifier` VARCHAR(255) NOT NULL, + `balance` DECIMAL(10, 2) NOT NULL, + PRIMARY KEY (`uuid`, `currency_identifier`) +); \ No newline at end of file