diff --git a/Emulator/pom.xml b/Emulator/pom.xml index e5e2d4f7..005fdfa2 100644 --- a/Emulator/pom.xml +++ b/Emulator/pom.xml @@ -6,7 +6,7 @@ com.eu.habbo Habbo - 4.1.0 + 4.0.5 UTF-8 @@ -86,11 +86,11 @@ 2.11.0 - + - com.mysql - mysql-connector-j - 9.1.0 + org.mariadb.jdbc + mariadb-java-client + 3.5.1 runtime diff --git a/Emulator/src/main/java/com/eu/habbo/database/Database.java b/Emulator/src/main/java/com/eu/habbo/database/Database.java index 00be0fee..9a7c2a4e 100644 --- a/Emulator/src/main/java/com/eu/habbo/database/Database.java +++ b/Emulator/src/main/java/com/eu/habbo/database/Database.java @@ -48,11 +48,11 @@ public class Database { } public void dispose() { - if (this.databasePool != null) { - this.databasePool.getDatabase().close(); + // dataSource is the same instance returned by databasePool.getDatabase(), + // so close it exactly once. + if (this.dataSource != null && !this.dataSource.isClosed()) { + this.dataSource.close(); } - - this.dataSource.close(); } public HikariDataSource getDataSource() { diff --git a/Emulator/src/main/java/com/eu/habbo/database/DatabasePool.java b/Emulator/src/main/java/com/eu/habbo/database/DatabasePool.java index 68a3e86c..acd50764 100644 --- a/Emulator/src/main/java/com/eu/habbo/database/DatabasePool.java +++ b/Emulator/src/main/java/com/eu/habbo/database/DatabasePool.java @@ -8,41 +8,85 @@ import org.slf4j.LoggerFactory; class DatabasePool { private final Logger log = LoggerFactory.getLogger(DatabasePool.class); + + // Connection settings + private static final String DB_HOSTNAME_KEY = "db.hostname"; + private static final String DB_PORT_KEY = "db.port"; + private static final String DB_PASSWORD_KEY = "db.password"; + private static final String DB_NAME_KEY = "db.database"; + private static final String DB_USER_KEY = "db.username"; + private static final String DB_PARAMS_KEY = "db.params"; + + // Pool sizing private static final String DB_POOL_MAX_SIZE = "db.pool.maxsize"; private static final String DB_POOL_MIN_SIZE = "db.pool.minsize"; - private static final String DB_HOSTNAME_KEY = "db.hostname"; - private static final String DB_PORT_KEY = "db.port"; - private static final String DB_PASSWORD_KEY = "db.password"; - private static final String DB_NAME_KEY = "db.database"; - private static final String DB_USER_KEY = "db.username"; - private static final String DB_PARAMS_KEY = "db.params"; + + // Pool tuning (all overridable via config.ini; sensible MariaDB defaults apply otherwise) + private static final String DB_POOL_CONNECTION_TIMEOUT = "db.pool.connection_timeout_ms"; + private static final String DB_POOL_IDLE_TIMEOUT = "db.pool.idle_timeout_ms"; + private static final String DB_POOL_MAX_LIFETIME = "db.pool.max_lifetime_ms"; + private static final String DB_POOL_LEAK_THRESHOLD = "db.pool.leak_detection_ms"; + private static final String DB_POOL_VALIDATION_TIMEOUT = "db.pool.validation_timeout_ms"; + private HikariDataSource database; - private static DatabasePool instance; DatabasePool() { } - public static synchronized DatabasePool getInstance() { - if (instance == null) { - instance = new DatabasePool(); - } - return instance; - } - public boolean getStoragePooling(ConfigurationManager config) { try { HikariConfig databaseConfiguration = new HikariConfig(); + + // Pool sizing databaseConfiguration.setMaximumPoolSize(config.getInt(DB_POOL_MAX_SIZE, 50)); databaseConfiguration.setMinimumIdle(config.getInt(DB_POOL_MIN_SIZE, 10)); - databaseConfiguration.setJdbcUrl("jdbc:mysql://" + config.getValue(DB_HOSTNAME_KEY, "localhost") + ":" + config.getValue(DB_PORT_KEY, "3306") + "/" + config.getValue(DB_NAME_KEY) + config.getValue(DB_PARAMS_KEY)); - databaseConfiguration.addDataSourceProperty("serverName", config.getValue(DB_HOSTNAME_KEY, "localhost")); - databaseConfiguration.addDataSourceProperty("port", config.getValue(DB_PORT_KEY, "3306")); - databaseConfiguration.addDataSourceProperty("databaseName", config.getValue(DB_NAME_KEY)); - databaseConfiguration.addDataSourceProperty("user", config.getValue(DB_USER_KEY)); - databaseConfiguration.addDataSourceProperty("password", config.getValue(DB_PASSWORD_KEY)); + + // Pool timeouts (milliseconds) + databaseConfiguration.setConnectionTimeout(config.getInt(DB_POOL_CONNECTION_TIMEOUT, 10_000)); + databaseConfiguration.setIdleTimeout(config.getInt(DB_POOL_IDLE_TIMEOUT, 600_000)); + databaseConfiguration.setMaxLifetime(config.getInt(DB_POOL_MAX_LIFETIME, 1_800_000)); + databaseConfiguration.setValidationTimeout(config.getInt(DB_POOL_VALIDATION_TIMEOUT, 5_000)); + + // Leak detection: 0 disables it. Default 20s helps locate connections + // that weren't closed in a try-with-resources block. + int leakThreshold = config.getInt(DB_POOL_LEAK_THRESHOLD, 20_000); + if (leakThreshold > 0) { + databaseConfiguration.setLeakDetectionThreshold(leakThreshold); + } + + // Use the MariaDB Connector/J native protocol instead of the Oracle MySQL driver. + databaseConfiguration.setJdbcUrl("jdbc:mariadb://" + + config.getValue(DB_HOSTNAME_KEY, "localhost") + ":" + + config.getValue(DB_PORT_KEY, "3306") + "/" + + config.getValue(DB_NAME_KEY) + + config.getValue(DB_PARAMS_KEY)); + databaseConfiguration.setUsername(config.getValue(DB_USER_KEY)); + databaseConfiguration.setPassword(config.getValue(DB_PASSWORD_KEY)); + + // Prepared-statement caching. Without these, Hikari's cache is off entirely + // and every prepareStatement() call re-parses on the server side. + databaseConfiguration.addDataSourceProperty("cachePrepStmts", "true"); + databaseConfiguration.addDataSourceProperty("prepStmtCacheSize", "500"); + databaseConfiguration.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); + databaseConfiguration.addDataSourceProperty("useServerPrepStmts", "true"); + + // Bulk write throughput: rewrites batched INSERTs into a single multi-value + // INSERT statement. Huge win for item/room/inventory persistence paths. + databaseConfiguration.addDataSourceProperty("rewriteBatchedStatements", "true"); + + // Cut per-connection round-trips. + databaseConfiguration.addDataSourceProperty("cacheServerConfiguration", "true"); + databaseConfiguration.addDataSourceProperty("useLocalSessionState", "true"); + databaseConfiguration.addDataSourceProperty("cacheResultSetMetadata", "true"); + databaseConfiguration.addDataSourceProperty("elideSetAutoCommits", "true"); + databaseConfiguration.addDataSourceProperty("maintainTimeStats", "false"); + + databaseConfiguration.setPoolName("HabboHikariPool"); + log.info("INITIALIZING DATABASE SERVER: " + config.getValue(DB_HOSTNAME_KEY)); log.info("ON PORT: " + config.getValue(DB_PORT_KEY)); log.info("HABBO DATABASE: " + config.getValue(DB_NAME_KEY)); + log.info("USING DRIVER: MariaDB Connector/J"); this.database = new HikariDataSource(databaseConfiguration); } catch (Exception e) { @@ -58,4 +102,4 @@ class DatabasePool { } return database; } -} \ No newline at end of file +}