diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 3517bc0..609fcba 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } dependencies { - compileOnlyApi("com.j256.ormlite:ormlite-core:6.1") + compileOnlyApi("org.jdbi:jdbi3-core:3.53.0") api("com.google.code.gson:gson:2.13.2") compileOnly("io.papermc.paper:paper-api:26.1.2.build.+") diff --git a/api/src/main/java/dev/plex/api/storage/ModuleOrm.java b/api/src/main/java/dev/plex/api/storage/ModuleOrm.java deleted file mode 100644 index bf13013..0000000 --- a/api/src/main/java/dev/plex/api/storage/ModuleOrm.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.plex.api.storage; - -import com.j256.ormlite.dao.Dao; -import java.sql.SQLException; - -/** - * Creates ORMLite DAOs for module-scoped tables. - */ -public interface ModuleOrm -{ - /** - * Creates or returns a cached DAO for a module-local table. - * - * @param entityClass ORMLite entity class - * @param localTableName module-local table name - * @param entity type - * @param entity ID type - * @return ORMLite DAO using the module-prefixed physical table - * @throws SQLException if the DAO cannot be created - */ - Dao dao(Class entityClass, String localTableName) throws SQLException; -} diff --git a/api/src/main/java/dev/plex/api/storage/ModuleStorage.java b/api/src/main/java/dev/plex/api/storage/ModuleStorage.java index 4887735..cbe2df2 100644 --- a/api/src/main/java/dev/plex/api/storage/ModuleStorage.java +++ b/api/src/main/java/dev/plex/api/storage/ModuleStorage.java @@ -1,6 +1,6 @@ package dev.plex.api.storage; -import java.sql.SQLException; +import org.jdbi.v3.core.Jdbi; /** * Module-scoped storage namespace. @@ -30,19 +30,10 @@ public interface ModuleStorage ModuleMigrations migrations(); /** - * Returns module ORMLite DAO operations. + * Returns the shared JDBI instance. Build SQL with {@link #table(String)} for + * physical-table resolution; use {@code jdbi().inTransaction(...)} for multi-statement transactions. * - * @return module ORMLite DAO operations + * @return shared JDBI instance */ - ModuleOrm orm(); - - /** - * Runs work inside a storage transaction. - * - * @param callable work to run - * @param callback result type - * @return callback result - * @throws SQLException if the transaction cannot complete - */ - T transaction(SqlCallable callable) throws SQLException; + Jdbi jdbi(); } diff --git a/api/src/main/java/dev/plex/api/storage/SqlCallable.java b/api/src/main/java/dev/plex/api/storage/SqlCallable.java deleted file mode 100644 index 89a7ae3..0000000 --- a/api/src/main/java/dev/plex/api/storage/SqlCallable.java +++ /dev/null @@ -1,20 +0,0 @@ -package dev.plex.api.storage; - -import java.sql.SQLException; - -/** - * Callback used for SQL work that does not receive a connection directly. - * - * @param callback result type - */ -@FunctionalInterface -public interface SqlCallable -{ - /** - * Runs SQL work. - * - * @return callback result - * @throws SQLException if the SQL work cannot complete - */ - T call() throws SQLException; -} diff --git a/server/build.gradle.kts b/server/build.gradle.kts index d059820..5d83fef 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -28,8 +28,7 @@ dependencies { library("org.postgresql:postgresql:42.7.11") library("org.xerial:sqlite-jdbc:3.53.1.0") library("com.zaxxer:HikariCP:7.0.2") - library("com.j256.ormlite:ormlite-core:6.1") - library("com.j256.ormlite:ormlite-jdbc:6.1") + library("org.jdbi:jdbi3-core:3.53.0") library("org.jetbrains:annotations:26.1.0") compileOnly("io.papermc.paper:paper-api:${paperApiVersion}.build.+") compileOnly("com.github.MilkBowl:VaultAPI:1.7.1") { diff --git a/server/src/main/java/dev/plex/Plex.java b/server/src/main/java/dev/plex/Plex.java index c4593e3..e16104b 100644 --- a/server/src/main/java/dev/plex/Plex.java +++ b/server/src/main/java/dev/plex/Plex.java @@ -19,8 +19,8 @@ import dev.plex.player.PlexPlayer; import dev.plex.punishment.PunishmentManager; import dev.plex.services.ServiceManager; import dev.plex.storage.RedisConnection; -import dev.plex.storage.SQLConnection; import dev.plex.storage.StorageType; +import dev.plex.storage.database.Database; import dev.plex.storage.player.SQLPlayerData; import dev.plex.storage.player.PlayerModuleDataRepository; import dev.plex.storage.player.SQLPlayerModuleData; @@ -63,7 +63,7 @@ public class Plex extends JavaPlugin public Config toggles; public File modulesFolder; private StorageType storageType = StorageType.SQLITE; - private SQLConnection sqlConnection; + private Database database; private RedisConnection redisConnection; private PlayerCache playerCache; @@ -146,7 +146,7 @@ public class Plex extends JavaPlugin // Don't add default entries to these files indefBans.load(false); - sqlConnection = new SQLConnection(this); + database = new Database(this); redisConnection = new RedisConnection(this); playerCache = new PlayerCache(); @@ -220,10 +220,10 @@ public class Plex extends JavaPlugin PlexLog.log("Redis is disabled in the configuration file, not connecting."); } - punishmentRepository = new SQLPunishment(sqlConnection.getConnectionSource(), api.scheduler().asyncExecutor()); - playerRepository = new SQLPlayerData(sqlConnection.getConnectionSource(), punishmentRepository); - playerModuleDataRepository = new SQLPlayerModuleData(sqlConnection, storageType); - noteRepository = new SQLNotes(sqlConnection.getConnectionSource(), api.scheduler().asyncExecutor()); + punishmentRepository = new SQLPunishment(database.getJdbi(), api.scheduler().asyncExecutor()); + playerRepository = new SQLPlayerData(database.getJdbi(), punishmentRepository, storageType); + playerModuleDataRepository = new SQLPlayerModuleData(database.getJdbi(), storageType); + noteRepository = new SQLNotes(database.getJdbi(), api.scheduler().asyncExecutor()); playerService = new PlayerService(playerCache, playerRepository); playerNameResolver = new PlayerNameResolver(playerService); @@ -275,9 +275,9 @@ public class Plex extends JavaPlugin moduleManager.disableModules(); - if (sqlConnection != null) + if (database != null) { - sqlConnection.close(); + database.close(); } } diff --git a/server/src/main/java/dev/plex/api/impl/DefaultStorageApi.java b/server/src/main/java/dev/plex/api/impl/DefaultStorageApi.java index 6dd8554..33fb441 100644 --- a/server/src/main/java/dev/plex/api/impl/DefaultStorageApi.java +++ b/server/src/main/java/dev/plex/api/impl/DefaultStorageApi.java @@ -18,7 +18,7 @@ final class DefaultStorageApi implements StorageApi @Override public T withConnection(SqlFunction function) throws SQLException { - try (Connection connection = plugin.getSqlConnection().getCon()) + try (Connection connection = plugin.getDatabase().getConnection()) { return function.apply(connection); } diff --git a/server/src/main/java/dev/plex/storage/SQLConnection.java b/server/src/main/java/dev/plex/storage/SQLConnection.java deleted file mode 100644 index aa28058..0000000 --- a/server/src/main/java/dev/plex/storage/SQLConnection.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.plex.storage; - -import dev.plex.Plex; -import dev.plex.storage.database.Database; - -/** - * Database bootstrap and connection holder. - * - *

The historical name is kept so existing module-facing accessors do not break.

- */ -public class SQLConnection extends Database -{ - public SQLConnection(Plex plugin) - { - super(plugin); - } - - public java.sql.Connection getCon() throws java.sql.SQLException - { - return getConnection(); - } -} diff --git a/server/src/main/java/dev/plex/storage/StorageType.java b/server/src/main/java/dev/plex/storage/StorageType.java index a011434..a69ae64 100644 --- a/server/src/main/java/dev/plex/storage/StorageType.java +++ b/server/src/main/java/dev/plex/storage/StorageType.java @@ -134,6 +134,33 @@ public enum StorageType }; } + public String playerUpsertSql() + { + return switch (this) + { + case SQLITE, POSTGRES -> """ + INSERT INTO players (uuid, last_known_name, login_msg, prefix, staffChat, commandspy) + VALUES (:uuid, :name, :login, :prefix, :staffChat, :commandSpy) + ON CONFLICT(uuid) DO UPDATE SET + last_known_name = excluded.last_known_name, + login_msg = excluded.login_msg, + prefix = excluded.prefix, + staffChat = excluded.staffChat, + commandspy = excluded.commandspy + """; + case MARIADB -> """ + INSERT INTO `players` (`uuid`, `last_known_name`, `login_msg`, `prefix`, `staffChat`, `commandspy`) + VALUES (:uuid, :name, :login, :prefix, :staffChat, :commandSpy) + ON DUPLICATE KEY UPDATE + `last_known_name` = VALUES(`last_known_name`), + `login_msg` = VALUES(`login_msg`), + `prefix` = VALUES(`prefix`), + `staffChat` = VALUES(`staffChat`), + `commandspy` = VALUES(`commandspy`) + """; + }; + } + public String getDisplayName() { return displayName; diff --git a/server/src/main/java/dev/plex/storage/database/Database.java b/server/src/main/java/dev/plex/storage/database/Database.java index cbcb006..aec6b60 100644 --- a/server/src/main/java/dev/plex/storage/database/Database.java +++ b/server/src/main/java/dev/plex/storage/database/Database.java @@ -1,13 +1,11 @@ package dev.plex.storage.database; -import com.j256.ormlite.jdbc.DataSourceConnectionSource; -import com.j256.ormlite.support.ConnectionSource; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import dev.plex.Plex; import dev.plex.storage.StorageType; -import dev.plex.util.PlexLog; import lombok.Getter; +import org.jdbi.v3.core.Jdbi; import java.util.List; @@ -16,7 +14,7 @@ public class Database { protected final Plex plugin; private final HikariDataSource dataSource; - private final ConnectionSource connectionSource; + private final Jdbi jdbi; private final StorageType storageType; private final MigrationRunner migrationRunner; @@ -39,9 +37,9 @@ public class Database this.dataSource = new HikariDataSource(config); try { - this.connectionSource = new DataSourceConnectionSource(dataSource, config.getJdbcUrl()); this.migrationRunner = new MigrationRunner(storageType); this.migrationRunner.runCore(dataSource, getClass().getClassLoader(), List.of("001_initial_schema")); + this.jdbi = Jdbi.create(dataSource); } catch (Exception e) { @@ -57,14 +55,6 @@ public class Database public void close() { - try - { - connectionSource.close(); - } - catch (Exception e) - { - PlexLog.warn("Failed to close ORMLite connection source: " + e.getMessage()); - } dataSource.close(); } } diff --git a/server/src/main/java/dev/plex/storage/database/entity/NoteEntity.java b/server/src/main/java/dev/plex/storage/database/entity/NoteEntity.java index 21a7444..a5f817a 100644 --- a/server/src/main/java/dev/plex/storage/database/entity/NoteEntity.java +++ b/server/src/main/java/dev/plex/storage/database/entity/NoteEntity.java @@ -1,31 +1,17 @@ package dev.plex.storage.database.entity; -import com.j256.ormlite.field.DatabaseField; -import com.j256.ormlite.table.DatabaseTable; import lombok.Getter; import lombok.Setter; @Getter @Setter -@DatabaseTable(tableName = "notes") public class NoteEntity { - @DatabaseField(generatedId = true, columnName = "row_id") private long rowId; - - @DatabaseField(columnName = "id", index = true) private int id; - - @DatabaseField(columnName = "uuid", canBeNull = false, index = true, width = 46) private String uuid; - - @DatabaseField(columnName = "written_by_uuid", width = 46) private String writtenByUuid; - - @DatabaseField(columnName = "note", width = 2000) private String note; - - @DatabaseField(columnName = "timestamp") private long timestamp; public NoteEntity() diff --git a/server/src/main/java/dev/plex/storage/database/entity/PlayerEntity.java b/server/src/main/java/dev/plex/storage/database/entity/PlayerEntity.java index d9c672f..b5839cb 100644 --- a/server/src/main/java/dev/plex/storage/database/entity/PlayerEntity.java +++ b/server/src/main/java/dev/plex/storage/database/entity/PlayerEntity.java @@ -1,34 +1,17 @@ package dev.plex.storage.database.entity; -import com.j256.ormlite.field.DatabaseField; -import com.j256.ormlite.table.DatabaseTable; import lombok.Getter; import lombok.Setter; @Getter @Setter -@DatabaseTable(tableName = "players") public class PlayerEntity { - @DatabaseField(id = true, columnName = "uuid", width = 46) private String uuid; - - @DatabaseField(columnName = "last_known_name", width = 18, index = true) private String lastKnownName; - - @DatabaseField(columnName = "login_msg", width = 2000) private String loginMessage; - - @DatabaseField(columnName = "prefix", width = 2000) private String prefix; - - @DatabaseField(columnName = "staffChat") private boolean staffChat; - - @DatabaseField(columnName = "ips", width = 2000) - private String ips; - - @DatabaseField(columnName = "commandspy") private boolean commandSpy; public PlayerEntity() diff --git a/server/src/main/java/dev/plex/storage/database/entity/PlayerIpEntity.java b/server/src/main/java/dev/plex/storage/database/entity/PlayerIpEntity.java index 56cf139..7c5fadf 100644 --- a/server/src/main/java/dev/plex/storage/database/entity/PlayerIpEntity.java +++ b/server/src/main/java/dev/plex/storage/database/entity/PlayerIpEntity.java @@ -1,22 +1,14 @@ package dev.plex.storage.database.entity; -import com.j256.ormlite.field.DatabaseField; -import com.j256.ormlite.table.DatabaseTable; import lombok.Getter; import lombok.Setter; @Getter @Setter -@DatabaseTable(tableName = "player_ips") public class PlayerIpEntity { - @DatabaseField(generatedId = true, columnName = "id") private long id; - - @DatabaseField(columnName = "player_uuid", canBeNull = false, index = true, width = 46) private String playerUuid; - - @DatabaseField(columnName = "ip", canBeNull = false, index = true, width = 64) private String ip; public PlayerIpEntity() diff --git a/server/src/main/java/dev/plex/storage/database/entity/PunishmentEntity.java b/server/src/main/java/dev/plex/storage/database/entity/PunishmentEntity.java index 335b8dd..c124766 100644 --- a/server/src/main/java/dev/plex/storage/database/entity/PunishmentEntity.java +++ b/server/src/main/java/dev/plex/storage/database/entity/PunishmentEntity.java @@ -1,49 +1,23 @@ package dev.plex.storage.database.entity; -import com.j256.ormlite.field.DatabaseField; -import com.j256.ormlite.table.DatabaseTable; import lombok.Getter; import lombok.Setter; @Getter @Setter -@DatabaseTable(tableName = "punishments") public class PunishmentEntity { - @DatabaseField(generatedId = true, columnName = "id") private long id; - - @DatabaseField(columnName = "punished_uuid", canBeNull = false, index = true, width = 46) private String punishedUuid; - - @DatabaseField(columnName = "punisher_uuid", width = 46) private String punisherUuid; - - @DatabaseField(columnName = "source", width = 20) private String source; - - @DatabaseField(columnName = "punisher_reference", width = 200) private String punisherReference; - - @DatabaseField(columnName = "ip", width = 2000, index = true) private String ip; - - @DatabaseField(columnName = "type", width = 30) private String type; - - @DatabaseField(columnName = "reason", width = 2000) private String reason; - - @DatabaseField(columnName = "customTime") private boolean customTime; - - @DatabaseField(columnName = "active", index = true) private boolean active; - - @DatabaseField(columnName = "issueDate") private long issueDate; - - @DatabaseField(columnName = "endDate") private long endDate; public PunishmentEntity() diff --git a/server/src/main/java/dev/plex/storage/module/ServerModuleMigrations.java b/server/src/main/java/dev/plex/storage/module/ServerModuleMigrations.java index 59654f3..e8a65a6 100644 --- a/server/src/main/java/dev/plex/storage/module/ServerModuleMigrations.java +++ b/server/src/main/java/dev/plex/storage/module/ServerModuleMigrations.java @@ -29,8 +29,8 @@ public class ServerModuleMigrations implements ModuleMigrations @Override public void run(String resourceRoot, List versions) throws SQLException { - plugin.getSqlConnection().getMigrationRunner().runModule( - plugin.getSqlConnection().getDataSource(), + plugin.getDatabase().getMigrationRunner().runModule( + plugin.getDatabase().getDataSource(), module, storage.scope(), resourceRoot, diff --git a/server/src/main/java/dev/plex/storage/module/ServerModuleOrm.java b/server/src/main/java/dev/plex/storage/module/ServerModuleOrm.java deleted file mode 100644 index 83041b9..0000000 --- a/server/src/main/java/dev/plex/storage/module/ServerModuleOrm.java +++ /dev/null @@ -1,42 +0,0 @@ -package dev.plex.storage.module; - -import com.j256.ormlite.dao.Dao; -import com.j256.ormlite.dao.DaoManager; -import com.j256.ormlite.support.ConnectionSource; -import com.j256.ormlite.table.DatabaseTableConfig; -import dev.plex.api.storage.ModuleOrm; - -import java.sql.SQLException; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class ServerModuleOrm implements ModuleOrm -{ - private final ConnectionSource connectionSource; - private final ServerModuleStorage storage; - private final Map> daos = new ConcurrentHashMap<>(); - - public ServerModuleOrm(ConnectionSource connectionSource, ServerModuleStorage storage) - { - this.connectionSource = connectionSource; - this.storage = storage; - } - - @Override - @SuppressWarnings("unchecked") - public Dao dao(Class entityClass, String localTableName) throws SQLException - { - String key = entityClass.getName() + ":" + localTableName; - Dao existing = daos.get(key); - if (existing != null) - { - return (Dao) existing; - } - - DatabaseTableConfig tableConfig = DatabaseTableConfig.fromClass(connectionSource.getDatabaseType(), entityClass); - tableConfig.setTableName(storage.table(localTableName)); - Dao dao = DaoManager.createDao(connectionSource, tableConfig); - daos.put(key, dao); - return dao; - } -} diff --git a/server/src/main/java/dev/plex/storage/module/ServerModuleStorage.java b/server/src/main/java/dev/plex/storage/module/ServerModuleStorage.java index 1cc1469..4f5e0f1 100644 --- a/server/src/main/java/dev/plex/storage/module/ServerModuleStorage.java +++ b/server/src/main/java/dev/plex/storage/module/ServerModuleStorage.java @@ -1,14 +1,10 @@ package dev.plex.storage.module; -import com.j256.ormlite.misc.TransactionManager; import dev.plex.Plex; import dev.plex.api.storage.ModuleMigrations; -import dev.plex.api.storage.ModuleOrm; import dev.plex.api.storage.ModuleStorage; -import dev.plex.api.storage.SqlCallable; import dev.plex.module.PlexModule; - -import java.sql.SQLException; +import org.jdbi.v3.core.Jdbi; public class ServerModuleStorage implements ModuleStorage { @@ -16,7 +12,6 @@ public class ServerModuleStorage implements ModuleStorage private final PlexModule module; private final String prefix; private final ModuleMigrations migrations; - private final ModuleOrm orm; public ServerModuleStorage(Plex plugin, PlexModule module) { @@ -24,7 +19,6 @@ public class ServerModuleStorage implements ModuleStorage this.module = module; this.prefix = ModuleNames.prefix(module); this.migrations = new ServerModuleMigrations(plugin, module, this); - this.orm = new ServerModuleOrm(plugin.getSqlConnection().getConnectionSource(), this); } @Override @@ -56,14 +50,8 @@ public class ServerModuleStorage implements ModuleStorage } @Override - public ModuleOrm orm() + public Jdbi jdbi() { - return orm; - } - - @Override - public T transaction(SqlCallable callable) throws SQLException - { - return TransactionManager.callInTransaction(plugin.getSqlConnection().getConnectionSource(), callable::call); + return plugin.getDatabase().getJdbi(); } } diff --git a/server/src/main/java/dev/plex/storage/player/SQLPlayerData.java b/server/src/main/java/dev/plex/storage/player/SQLPlayerData.java index d2361d7..ab0ec84 100644 --- a/server/src/main/java/dev/plex/storage/player/SQLPlayerData.java +++ b/server/src/main/java/dev/plex/storage/player/SQLPlayerData.java @@ -1,54 +1,44 @@ package dev.plex.storage.player; -import com.google.common.reflect.TypeToken; -import com.google.gson.Gson; -import com.j256.ormlite.dao.Dao; -import com.j256.ormlite.dao.DaoManager; -import com.j256.ormlite.support.ConnectionSource; -import com.j256.ormlite.stmt.DeleteBuilder; import dev.plex.player.PlexPlayer; +import dev.plex.storage.StorageType; import dev.plex.storage.database.entity.PlayerEntity; -import dev.plex.storage.database.entity.PlayerIpEntity; import dev.plex.storage.repository.PlayerRepository; import dev.plex.storage.repository.PunishmentRepository; +import dev.plex.util.PlexLog; +import org.jdbi.v3.core.Handle; +import org.jdbi.v3.core.Jdbi; +import org.jdbi.v3.core.JdbiException; -import java.sql.SQLException; import java.util.List; import java.util.UUID; /** - * Player persistence backed by ORMLite. + * Player persistence backed by JDBI. */ public class SQLPlayerData implements PlayerRepository { - private static final Gson GSON = new Gson(); - private final Dao players; - private final Dao playerIps; + private final Jdbi jdbi; private final PunishmentRepository punishmentRepository; + private final StorageType storageType; - public SQLPlayerData(ConnectionSource connectionSource, PunishmentRepository punishmentRepository) + public SQLPlayerData(Jdbi jdbi, PunishmentRepository punishmentRepository, StorageType storageType) { + this.jdbi = jdbi; this.punishmentRepository = punishmentRepository; - try - { - this.players = DaoManager.createDao(connectionSource, PlayerEntity.class); - this.playerIps = DaoManager.createDao(connectionSource, PlayerIpEntity.class); - } - catch (SQLException e) - { - throw new IllegalStateException("Failed to create player DAOs", e); - } + this.storageType = storageType; } public boolean exists(UUID uuid) { try { - return players.idExists(uuid.toString()); + return jdbi.withHandle(h -> h.createQuery("SELECT 1 FROM players WHERE uuid = :u") + .bind("u", uuid.toString()).mapTo(Integer.class).findFirst().isPresent()); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to check player existence for {0}: {1}", uuid, e.getMessage()); return false; } } @@ -57,11 +47,12 @@ public class SQLPlayerData implements PlayerRepository { try { - return players.queryBuilder().where().eq("last_known_name", username).queryForFirst() != null; + return jdbi.withHandle(h -> h.createQuery("SELECT 1 FROM players WHERE last_known_name = :n") + .bind("n", username).mapTo(Integer.class).findFirst().isPresent()); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to check player existence for {0}: {1}", username, e.getMessage()); return false; } } @@ -70,11 +61,16 @@ public class SQLPlayerData implements PlayerRepository { try { - return toPlayer(players.queryForId(uuid.toString()), loadExtraData); + return jdbi.withHandle(h -> + { + PlayerEntity e = h.createQuery("SELECT * FROM players WHERE uuid = :u") + .bind("u", uuid.toString()).map((rs, ctx) -> mapRow(rs)).findFirst().orElse(null); + return toPlayer(h, e, loadExtraData); + }); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to load player by UUID {0}: {1}", uuid, e.getMessage()); return null; } } @@ -83,12 +79,12 @@ public class SQLPlayerData implements PlayerRepository { try { - PlayerEntity entity = players.queryForId(uuid.toString()); - return entity == null ? null : entity.getLastKnownName(); + return jdbi.withHandle(h -> h.createQuery("SELECT last_known_name FROM players WHERE uuid = :u") + .bind("u", uuid.toString()).mapTo(String.class).findFirst().orElse(null)); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to load player name by UUID {0}: {1}", uuid, e.getMessage()); return null; } } @@ -102,11 +98,16 @@ public class SQLPlayerData implements PlayerRepository { try { - return toPlayer(players.queryBuilder().limit(1L).where().eq("last_known_name", username).queryForFirst(), loadExtraData); + return jdbi.withHandle(h -> + { + PlayerEntity e = h.createQuery("SELECT * FROM players WHERE last_known_name = :n LIMIT 1") + .bind("n", username).map((rs, ctx) -> mapRow(rs)).findFirst().orElse(null); + return toPlayer(h, e, loadExtraData); + }); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to load player by name {0}: {1}", username, e.getMessage()); return null; } } @@ -120,39 +121,46 @@ public class SQLPlayerData implements PlayerRepository { try { - PlayerIpEntity playerIp = playerIps.queryBuilder().limit(1L).where().eq("ip", ip).queryForFirst(); - if (playerIp != null) + return jdbi.withHandle(h -> { - return toPlayer(players.queryForId(playerIp.getPlayerUuid()), true); - } - - for (PlayerEntity entity : players.queryForAll()) - { - List ips = parseIps(entity.getIps()); - if (ips.contains(ip)) + String uuid = h.createQuery("SELECT player_uuid FROM player_ips WHERE ip = :ip LIMIT 1") + .bind("ip", ip).mapTo(String.class).findFirst().orElse(null); + if (uuid == null) { - syncIps(entity.getUuid(), ips); - return toPlayer(entity, true); + return null; } - } + PlayerEntity e = h.createQuery("SELECT * FROM players WHERE uuid = :u") + .bind("u", uuid).map((rs, ctx) -> mapRow(rs)).findFirst().orElse(null); + return toPlayer(h, e, true); + }); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to load player by IP {0}: {1}", ip, e.getMessage()); + return null; } - return null; } public void update(PlexPlayer player) { try { - players.createOrUpdate(toEntity(player)); - syncIps(player.getUuid().toString(), player.getIps()); + jdbi.useTransaction(h -> + { + h.createUpdate(storageType.playerUpsertSql()) + .bind("uuid", player.getUuid().toString()) + .bind("name", player.getName()) + .bind("login", player.getLoginMessage()) + .bind("prefix", player.getPrefix()) + .bind("staffChat", player.isStaffChat()) + .bind("commandSpy", player.isCommandSpy()) + .execute(); + syncIps(h, player.getUuid().toString(), player.getIps()); + }); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to update player {0}: {1}", player.getUuid(), e.getMessage()); } } @@ -161,7 +169,35 @@ public class SQLPlayerData implements PlayerRepository update(player); } - private PlexPlayer toPlayer(PlayerEntity entity, boolean loadExtraData) + private static PlayerEntity mapRow(java.sql.ResultSet rs) throws java.sql.SQLException + { + PlayerEntity e = new PlayerEntity(); + e.setUuid(rs.getString("uuid")); + e.setLastKnownName(rs.getString("last_known_name")); + e.setLoginMessage(rs.getString("login_msg")); + e.setPrefix(rs.getString("prefix")); + e.setStaffChat(rs.getBoolean("staffChat")); + e.setCommandSpy(rs.getBoolean("commandspy")); + return e; + } + + private List loadIps(Handle h, String uuid) + { + return h.createQuery("SELECT ip FROM player_ips WHERE player_uuid = :u") + .bind("u", uuid).mapTo(String.class).list(); + } + + private void syncIps(Handle h, String playerUuid, List ips) + { + h.createUpdate("DELETE FROM player_ips WHERE player_uuid = :u").bind("u", playerUuid).execute(); + for (String ip : ips.stream().distinct().toList()) + { + h.createUpdate("INSERT INTO player_ips (player_uuid, ip) VALUES (:u, :ip)") + .bind("u", playerUuid).bind("ip", ip).execute(); + } + } + + private PlexPlayer toPlayer(Handle h, PlayerEntity entity, boolean loadExtraData) { if (entity == null) { @@ -173,7 +209,7 @@ public class SQLPlayerData implements PlayerRepository plexPlayer.setLoginMessage(entity.getLoginMessage()); plexPlayer.setPrefix(entity.getPrefix()); plexPlayer.setStaffChat(entity.isStaffChat()); - plexPlayer.setIps(parseIps(entity.getIps())); + plexPlayer.setIps(loadIps(h, entity.getUuid())); plexPlayer.setCommandSpy(entity.isCommandSpy()); if (loadExtraData) { @@ -182,41 +218,4 @@ public class SQLPlayerData implements PlayerRepository } return plexPlayer; } - - private PlayerEntity toEntity(PlexPlayer player) - { - PlayerEntity entity = new PlayerEntity(); - entity.setUuid(player.getUuid().toString()); - entity.setLastKnownName(player.getName()); - entity.setLoginMessage(player.getLoginMessage()); - entity.setPrefix(player.getPrefix()); - entity.setStaffChat(player.isStaffChat()); - entity.setIps(GSON.toJson(player.getIps())); - entity.setCommandSpy(player.isCommandSpy()); - return entity; - } - - private List parseIps(String ips) - { - if (ips == null || ips.isBlank()) - { - return List.of(); - } - List parsed = GSON.fromJson(ips, new TypeToken>() - { - }.getType()); - return parsed == null ? List.of() : parsed; - } - - private void syncIps(String playerUuid, List ips) throws SQLException - { - DeleteBuilder delete = playerIps.deleteBuilder(); - delete.where().eq("player_uuid", playerUuid); - delete.delete(); - - for (String ip : ips.stream().distinct().toList()) - { - playerIps.create(new PlayerIpEntity(playerUuid, ip)); - } - } } diff --git a/server/src/main/java/dev/plex/storage/player/SQLPlayerModuleData.java b/server/src/main/java/dev/plex/storage/player/SQLPlayerModuleData.java index b7aab47..11c9cc4 100644 --- a/server/src/main/java/dev/plex/storage/player/SQLPlayerModuleData.java +++ b/server/src/main/java/dev/plex/storage/player/SQLPlayerModuleData.java @@ -3,48 +3,41 @@ package dev.plex.storage.player; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; -import dev.plex.storage.SQLConnection; import dev.plex.storage.StorageType; +import dev.plex.util.PlexLog; +import org.jdbi.v3.core.Jdbi; +import org.jdbi.v3.core.JdbiException; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.Optional; import java.util.UUID; public class SQLPlayerModuleData implements PlayerModuleDataRepository { - private final SQLConnection sqlConnection; + private final Jdbi jdbi; private final StorageType storageType; - public SQLPlayerModuleData(SQLConnection sqlConnection, StorageType storageType) + public SQLPlayerModuleData(Jdbi jdbi, StorageType storageType) { - this.sqlConnection = sqlConnection; + this.jdbi = jdbi; this.storageType = storageType; } @Override public Optional get(UUID playerUuid, String module, String key) { - String sql = "SELECT value_json FROM player_module_data WHERE player_uuid = ? AND module = ? AND data_key = ?"; - try (Connection connection = sqlConnection.getConnection(); PreparedStatement statement = connection.prepareStatement(sql)) + try { - statement.setString(1, playerUuid.toString()); - statement.setString(2, module); - statement.setString(3, key); - try (ResultSet resultSet = statement.executeQuery()) - { - if (!resultSet.next()) - { - return Optional.empty(); - } - return Optional.of(JsonParser.parseString(resultSet.getString("value_json"))); - } + return jdbi.withHandle(h -> h.createQuery( + "SELECT value_json FROM player_module_data WHERE player_uuid = :p AND module = :m AND data_key = :k") + .bind("p", playerUuid.toString()) + .bind("m", module) + .bind("k", key) + .mapTo(String.class).findFirst()) + .map(JsonParser::parseString); } - catch (SQLException | JsonSyntaxException e) + catch (JdbiException | JsonSyntaxException e) { - e.printStackTrace(); + PlexLog.warn("Failed to load player module data {0}/{1}/{2}: {3}", playerUuid, module, key, e.getMessage()); return Optional.empty(); } } @@ -52,35 +45,37 @@ public class SQLPlayerModuleData implements PlayerModuleDataRepository @Override public void set(UUID playerUuid, String module, String key, JsonElement value) { - try (Connection connection = sqlConnection.getConnection(); PreparedStatement statement = connection.prepareStatement(storageType.playerModuleDataUpsertSql())) + try { - statement.setString(1, playerUuid.toString()); - statement.setString(2, module); - statement.setString(3, key); - statement.setString(4, value.toString()); - statement.setLong(5, System.currentTimeMillis()); - statement.executeUpdate(); + jdbi.useHandle(h -> h.createUpdate(storageType.playerModuleDataUpsertSql()) + .bind(0, playerUuid.toString()) + .bind(1, module) + .bind(2, key) + .bind(3, value.toString()) + .bind(4, System.currentTimeMillis()) + .execute()); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to save player module data {0}/{1}/{2}: {3}", playerUuid, module, key, e.getMessage()); } } @Override public void remove(UUID playerUuid, String module, String key) { - String sql = "DELETE FROM player_module_data WHERE player_uuid = ? AND module = ? AND data_key = ?"; - try (Connection connection = sqlConnection.getConnection(); PreparedStatement statement = connection.prepareStatement(sql)) + try { - statement.setString(1, playerUuid.toString()); - statement.setString(2, module); - statement.setString(3, key); - statement.executeUpdate(); + jdbi.useHandle(h -> h.createUpdate( + "DELETE FROM player_module_data WHERE player_uuid = :p AND module = :m AND data_key = :k") + .bind("p", playerUuid.toString()) + .bind("m", module) + .bind("k", key) + .execute()); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to remove player module data {0}/{1}/{2}: {3}", playerUuid, module, key, e.getMessage()); } } } diff --git a/server/src/main/java/dev/plex/storage/punishment/SQLNotes.java b/server/src/main/java/dev/plex/storage/punishment/SQLNotes.java index d9412a6..8567838 100644 --- a/server/src/main/java/dev/plex/storage/punishment/SQLNotes.java +++ b/server/src/main/java/dev/plex/storage/punishment/SQLNotes.java @@ -1,16 +1,14 @@ package dev.plex.storage.punishment; import com.google.common.collect.Lists; -import com.j256.ormlite.dao.Dao; -import com.j256.ormlite.dao.DaoManager; -import com.j256.ormlite.support.ConnectionSource; -import com.j256.ormlite.stmt.DeleteBuilder; import dev.plex.punishment.extra.Note; import dev.plex.storage.database.entity.NoteEntity; import dev.plex.storage.repository.NoteRepository; +import dev.plex.util.PlexLog; import dev.plex.util.TimeUtils; +import org.jdbi.v3.core.Jdbi; +import org.jdbi.v3.core.JdbiException; -import java.sql.SQLException; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -23,20 +21,13 @@ import java.util.concurrent.Executor; public class SQLNotes implements NoteRepository { - private final Dao notes; + private final Jdbi jdbi; private final Executor executor; - public SQLNotes(ConnectionSource connectionSource, Executor executor) + public SQLNotes(Jdbi jdbi, Executor executor) { - try - { - this.notes = DaoManager.createDao(connectionSource, NoteEntity.class); - this.executor = executor; - } - catch (SQLException e) - { - throw new IllegalStateException("Failed to create note DAO", e); - } + this.jdbi = jdbi; + this.executor = executor; } public CompletableFuture> getNotes(UUID uuid) @@ -45,15 +36,16 @@ public class SQLNotes implements NoteRepository { try { - return notes.queryForEq("uuid", uuid.toString()).stream() + return jdbi.withHandle(h -> h.createQuery("SELECT * FROM notes WHERE uuid = :u") + .bind("u", uuid.toString()).map((rs, ctx) -> mapRow(rs)).list()).stream() .sorted(Comparator.comparingInt(NoteEntity::getId)) .map(this::toNote) .flatMap(Optional::stream) .toList(); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to load notes for {0}: {1}", uuid, e.getMessage()); return Lists.newArrayList(); } }, executor); @@ -65,13 +57,14 @@ public class SQLNotes implements NoteRepository { try { - DeleteBuilder delete = notes.deleteBuilder(); - delete.where().eq("uuid", uuid.toString()).and().eq("id", id); - delete.delete(); + jdbi.useHandle(h -> h.createUpdate("DELETE FROM notes WHERE uuid = :u AND id = :id") + .bind("u", uuid.toString()) + .bind("id", id) + .execute()); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to delete note {0} for {1}: {2}", id, uuid, e.getMessage()); } }, executor); } @@ -82,22 +75,40 @@ public class SQLNotes implements NoteRepository { try { - int nextId = notes.queryForEq("uuid", note.getUuid().toString()).stream() - .map(NoteEntity::getId) - .max(Integer::compareTo) - .orElse(0) + 1; + int nextId = jdbi.withHandle(h -> h.createQuery("SELECT COALESCE(MAX(id), 0) FROM notes WHERE uuid = :u") + .bind("u", note.getUuid().toString()).mapTo(Integer.class).one()) + 1; NoteEntity entity = toEntity(note); entity.setId(nextId); - notes.create(entity); + jdbi.useHandle(h -> h.createUpdate( + "INSERT INTO notes (id, uuid, written_by_uuid, note, timestamp) " + + "VALUES (:id, :uuid, :writtenBy, :note, :ts)") + .bind("id", entity.getId()) + .bind("uuid", entity.getUuid()) + .bind("writtenBy", entity.getWrittenByUuid()) + .bind("note", entity.getNote()) + .bind("ts", entity.getTimestamp()) + .execute()); note.setId(nextId); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to add note for {0}: {1}", note.getUuid(), e.getMessage()); } }, executor); } + private static NoteEntity mapRow(java.sql.ResultSet rs) throws java.sql.SQLException + { + NoteEntity e = new NoteEntity(); + e.setRowId(rs.getLong("row_id")); + e.setId(rs.getInt("id")); + e.setUuid(rs.getString("uuid")); + e.setWrittenByUuid(rs.getString("written_by_uuid")); + e.setNote(rs.getString("note")); + e.setTimestamp(rs.getLong("timestamp")); + return e; + } + private Optional toNote(NoteEntity entity) { try diff --git a/server/src/main/java/dev/plex/storage/punishment/SQLPunishment.java b/server/src/main/java/dev/plex/storage/punishment/SQLPunishment.java index 3a8a496..f5a4777 100644 --- a/server/src/main/java/dev/plex/storage/punishment/SQLPunishment.java +++ b/server/src/main/java/dev/plex/storage/punishment/SQLPunishment.java @@ -1,10 +1,6 @@ package dev.plex.storage.punishment; import com.google.common.collect.Lists; -import com.j256.ormlite.dao.Dao; -import com.j256.ormlite.dao.DaoManager; -import com.j256.ormlite.support.ConnectionSource; -import com.j256.ormlite.stmt.UpdateBuilder; import dev.plex.api.punishment.PunishmentSource; import dev.plex.punishment.Punishment; import dev.plex.punishment.PunishmentType; @@ -12,8 +8,9 @@ import dev.plex.storage.database.entity.PunishmentEntity; import dev.plex.storage.repository.PunishmentRepository; import dev.plex.util.PlexLog; import dev.plex.util.TimeUtils; +import org.jdbi.v3.core.Jdbi; +import org.jdbi.v3.core.JdbiException; -import java.sql.SQLException; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -24,20 +21,13 @@ import java.util.concurrent.Executor; public class SQLPunishment implements PunishmentRepository { - private final Dao punishments; + private final Jdbi jdbi; private final Executor executor; - public SQLPunishment(ConnectionSource connectionSource, Executor executor) + public SQLPunishment(Jdbi jdbi, Executor executor) { - try - { - this.punishments = DaoManager.createDao(connectionSource, PunishmentEntity.class); - this.executor = executor; - } - catch (SQLException e) - { - throw new IllegalStateException("Failed to create punishment DAO", e); - } + this.jdbi = jdbi; + this.executor = executor; } public CompletableFuture> getPunishments() @@ -46,11 +36,12 @@ public class SQLPunishment implements PunishmentRepository { try { - return punishments.queryForAll().stream().map(this::toPunishment).toList(); + return jdbi.withHandle(h -> h.createQuery("SELECT * FROM punishments") + .map((rs, ctx) -> mapRow(rs)).list()).stream().map(this::toPunishment).toList(); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to load punishments: {0}", e.getMessage()); return Lists.newArrayList(); } }, executor); @@ -60,11 +51,13 @@ public class SQLPunishment implements PunishmentRepository { try { - return punishments.queryForEq("punished_uuid", uuid.toString()).stream().map(this::toPunishment).toList(); + return jdbi.withHandle(h -> h.createQuery("SELECT * FROM punishments WHERE punished_uuid = :u") + .bind("u", uuid.toString()).map((rs, ctx) -> mapRow(rs)).list()) + .stream().map(this::toPunishment).toList(); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to load punishments for {0}: {1}", uuid, e.getMessage()); return Lists.newArrayList(); } } @@ -73,11 +66,13 @@ public class SQLPunishment implements PunishmentRepository { try { - return punishments.queryForEq("ip", ip).stream().map(this::toPunishment).toList(); + return jdbi.withHandle(h -> h.createQuery("SELECT * FROM punishments WHERE ip = :ip") + .bind("ip", ip).map((rs, ctx) -> mapRow(rs)).list()) + .stream().map(this::toPunishment).toList(); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to load punishments for IP {0}: {1}", ip, e.getMessage()); return Lists.newArrayList(); } } @@ -89,11 +84,26 @@ public class SQLPunishment implements PunishmentRepository try { PlexLog.debug("Persisting punishment for " + punishment.getPunished()); - punishments.create(toEntity(punishment)); + PunishmentEntity e = toEntity(punishment); + jdbi.useHandle(h -> h.createUpdate( + "INSERT INTO punishments (punished_uuid, punisher_uuid, source, punisher_reference, ip, type, reason, customTime, active, issueDate, endDate) " + + "VALUES (:punishedUuid, :punisherUuid, :source, :punisherReference, :ip, :type, :reason, :customTime, :active, :issueDate, :endDate)") + .bind("punishedUuid", e.getPunishedUuid()) + .bind("punisherUuid", e.getPunisherUuid()) + .bind("source", e.getSource()) + .bind("punisherReference", e.getPunisherReference()) + .bind("ip", e.getIp()) + .bind("type", e.getType()) + .bind("reason", e.getReason()) + .bind("customTime", e.isCustomTime()) + .bind("active", e.isActive()) + .bind("issueDate", e.getIssueDate()) + .bind("endDate", e.getEndDate()) + .execute()); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to persist punishment for {0}: {1}", punishment.getPunished(), e.getMessage()); } }, executor); } @@ -118,17 +128,37 @@ public class SQLPunishment implements PunishmentRepository { try { - UpdateBuilder update = punishments.updateBuilder(); - update.updateColumnValue("active", active); - update.where().eq("punished_uuid", punished.toString()).and().eq("type", type.name()); - update.update(); + jdbi.useHandle(h -> h.createUpdate( + "UPDATE punishments SET active = :active WHERE punished_uuid = :u AND type = :t") + .bind("active", active) + .bind("u", punished.toString()) + .bind("t", type.name()) + .execute()); } - catch (SQLException e) + catch (JdbiException e) { - e.printStackTrace(); + PlexLog.warn("Failed to update punishment state for {0}: {1}", punished, e.getMessage()); } } + private static PunishmentEntity mapRow(java.sql.ResultSet rs) throws java.sql.SQLException + { + PunishmentEntity e = new PunishmentEntity(); + e.setId(rs.getLong("id")); + e.setPunishedUuid(rs.getString("punished_uuid")); + e.setPunisherUuid(rs.getString("punisher_uuid")); + e.setSource(rs.getString("source")); + e.setPunisherReference(rs.getString("punisher_reference")); + e.setIp(rs.getString("ip")); + e.setType(rs.getString("type")); + e.setReason(rs.getString("reason")); + e.setCustomTime(rs.getBoolean("customTime")); + e.setActive(rs.getBoolean("active")); + e.setIssueDate(rs.getLong("issueDate")); + e.setEndDate(rs.getLong("endDate")); + return e; + } + private Punishment toPunishment(PunishmentEntity entity) { UUID punisher = entity.getPunisherUuid() == null || entity.getPunisherUuid().isBlank() ? null : UUID.fromString(entity.getPunisherUuid()); diff --git a/server/src/main/java/dev/plex/util/PlexUtils.java b/server/src/main/java/dev/plex/util/PlexUtils.java index 25d4ec1..0ac2dc9 100644 --- a/server/src/main/java/dev/plex/util/PlexUtils.java +++ b/server/src/main/java/dev/plex/util/PlexUtils.java @@ -88,9 +88,9 @@ public class PlexUtils public static void testConnections(Plex plugin) { - if (plugin.getSqlConnection().getDataSource() != null) + if (plugin.getDatabase().getDataSource() != null) { - try (Connection ignored = plugin.getSqlConnection().getCon()) + try (Connection ignored = plugin.getDatabase().getConnection()) { PlexLog.log("Successfully enabled " + plugin.getStorageType().getDisplayName() + "!"); } diff --git a/server/src/main/resources/db/migration/mariadb/001_initial_schema.sql b/server/src/main/resources/db/migration/mariadb/001_initial_schema.sql index 79c2eb4..4f774ba 100644 --- a/server/src/main/resources/db/migration/mariadb/001_initial_schema.sql +++ b/server/src/main/resources/db/migration/mariadb/001_initial_schema.sql @@ -4,7 +4,6 @@ CREATE TABLE IF NOT EXISTS `players` ( `login_msg` VARCHAR(2000), `prefix` VARCHAR(2000), `staffChat` BOOLEAN, - `ips` VARCHAR(2000), `commandspy` BOOLEAN, PRIMARY KEY (`uuid`), INDEX `idx_players_last_known_name` (`last_known_name`) diff --git a/server/src/main/resources/db/migration/postgres/001_initial_schema.sql b/server/src/main/resources/db/migration/postgres/001_initial_schema.sql index 7daf92a..8b0f1dc 100644 --- a/server/src/main/resources/db/migration/postgres/001_initial_schema.sql +++ b/server/src/main/resources/db/migration/postgres/001_initial_schema.sql @@ -4,7 +4,6 @@ CREATE TABLE IF NOT EXISTS players ( login_msg VARCHAR(2000), prefix VARCHAR(2000), staffChat BOOLEAN, - ips VARCHAR(2000), commandspy BOOLEAN ); diff --git a/server/src/main/resources/db/migration/sqlite/001_initial_schema.sql b/server/src/main/resources/db/migration/sqlite/001_initial_schema.sql index 5a17141..512ace8 100644 --- a/server/src/main/resources/db/migration/sqlite/001_initial_schema.sql +++ b/server/src/main/resources/db/migration/sqlite/001_initial_schema.sql @@ -4,7 +4,6 @@ CREATE TABLE IF NOT EXISTS players ( login_msg VARCHAR(2000), prefix VARCHAR(2000), staffChat BOOLEAN, - ips VARCHAR(2000), commandspy BOOLEAN );