feat(storage): add support for mysql

This commit is contained in:
ineanto 2023-10-05 13:56:49 +02:00
parent 83b7f90976
commit 52c7c00dd1
17 changed files with 296 additions and 33 deletions

View file

@ -91,6 +91,12 @@
<artifactId>mariadb-java-client</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.1.0</version>
</dependency>
<!-- YAML Reader -->
<dependency>
<groupId>com.github.jsixface</groupId>

View file

@ -4,13 +4,13 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public class Configuration {
@JsonProperty("sql")
private final DataSourceConfiguration sqlConfiguration;
private final SQLDataSourceConfiguration sqlConfiguration;
@JsonProperty("redis")
private final DataSourceConfiguration redisConfiguration;
private final String prefix;
private final Boolean customLocale;
public Configuration(DataSourceConfiguration sqlConfiguration, DataSourceConfiguration redisConfiguration, String prefix, Boolean customLocale) {
public Configuration(SQLDataSourceConfiguration sqlConfiguration, DataSourceConfiguration redisConfiguration, String prefix, Boolean customLocale) {
this.sqlConfiguration = sqlConfiguration;
this.redisConfiguration = redisConfiguration;
this.prefix = prefix;
@ -19,14 +19,14 @@ public class Configuration {
public Configuration() {
this(
new DataSourceConfiguration(false, "", 3306, "", ""),
new SQLDataSourceConfiguration(false, "", 3306, "", "", true),
new DataSourceConfiguration(false, "", 6379, "", ""),
"",
false
);
}
public DataSourceConfiguration getSqlConfiguration() {
public SQLDataSourceConfiguration getSqlConfiguration() {
return sqlConfiguration;
}

View file

@ -1,9 +1,6 @@
package xyz.atnrch.nicko.config;
public class DataSourceConfiguration {
public static final DataSourceConfiguration SQL_EMPTY = new DataSourceConfiguration(false, "127.0.0.1", 3306, "root", "");
public static final DataSourceConfiguration REDIS_EMPTY = new DataSourceConfiguration(false, "127.0.0.1", 6379, "", "");
private final boolean enabled;
private final String address;
private final Integer port;

View file

@ -0,0 +1,8 @@
package xyz.atnrch.nicko.config;
public class DefaultDataSources {
public static final DataSourceConfiguration REDIS_EMPTY = new DataSourceConfiguration(false, "127.0.0.1", 6379, "", "");
public static final SQLDataSourceConfiguration MARIADB_EMPTY = new SQLDataSourceConfiguration(false, "127.0.0.1", 3306, "root", "", true);
public static final SQLDataSourceConfiguration SQL_EMPTY = new SQLDataSourceConfiguration(false, "127.0.0.1", 3306, "root", "", false);
}

View file

@ -0,0 +1,14 @@
package xyz.atnrch.nicko.config;
public class SQLDataSourceConfiguration extends DataSourceConfiguration {
private final boolean mariadb;
public SQLDataSourceConfiguration(boolean enabled, String address, Integer port, String username, String password, boolean mariadb) {
super(enabled, address, port, username, password);
this.mariadb = mariadb;
}
public boolean isMariadb() {
return mariadb;
}
}

View file

@ -1,16 +1,17 @@
package xyz.atnrch.nicko.storage;
import org.bukkit.entity.Player;
import xyz.atnrch.nicko.config.Configuration;
import xyz.atnrch.nicko.appearance.ActionResult;
import xyz.atnrch.nicko.profile.NickoProfile;
import xyz.atnrch.nicko.config.Configuration;
import xyz.atnrch.nicko.i18n.I18NDict;
import xyz.atnrch.nicko.mojang.MojangAPI;
import xyz.atnrch.nicko.mojang.MojangUtils;
import xyz.atnrch.nicko.profile.NickoProfile;
import xyz.atnrch.nicko.storage.json.JSONStorage;
import xyz.atnrch.nicko.storage.map.MapCache;
import xyz.atnrch.nicko.storage.mariadb.MariaDBStorage;
import xyz.atnrch.nicko.storage.mysql.MySQLStorage;
import xyz.atnrch.nicko.storage.redis.RedisCache;
import xyz.atnrch.nicko.storage.sql.SQLStorage;
import java.io.IOException;
import java.util.Optional;
@ -24,7 +25,9 @@ public class PlayerDataStore {
public PlayerDataStore(MojangAPI mojangAPI, Configuration configuration) {
this.mojangAPI = mojangAPI;
this.storage = configuration.getSqlConfiguration().isEnabled() ? new SQLStorage(configuration) : new JSONStorage();
this.storage = configuration.getSqlConfiguration().isEnabled() ?
configuration.getSqlConfiguration().isMariadb() ? new MariaDBStorage(configuration) : new MySQLStorage(configuration)
: new JSONStorage();
this.cache = configuration.getRedisConfiguration().isEnabled() ? new RedisCache(configuration) : new MapCache();
}

View file

@ -0,0 +1,141 @@
package xyz.atnrch.nicko.storage.mariadb;
import xyz.atnrch.nicko.appearance.ActionResult;
import xyz.atnrch.nicko.config.Configuration;
import xyz.atnrch.nicko.i18n.I18NDict;
import xyz.atnrch.nicko.i18n.Locale;
import xyz.atnrch.nicko.profile.NickoProfile;
import xyz.atnrch.nicko.storage.Storage;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;
import java.util.UUID;
import java.util.logging.Logger;
public class MariaDBStorage extends Storage {
private final Logger logger = Logger.getLogger("SQLStorage");
private final Configuration configuration;
private MariaDBStorageProvider provider;
public MariaDBStorage(Configuration configuration) {
this.configuration = configuration;
}
@Override
public MariaDBStorageProvider getProvider() {
if (provider == null) {
provider = new MariaDBStorageProvider(configuration);
}
return provider;
}
@Override
public ActionResult store(UUID uuid, NickoProfile profile) {
final Connection connection = getProvider().getConnection();
if (connection == null) return ActionResult.error(I18NDict.Error.SQL_ERROR);
try {
final PreparedStatement statement = isStored(uuid) ?
getUpdateStatement(connection, uuid, profile) : getInsertStatement(connection, uuid, profile);
statement.executeUpdate();
return ActionResult.ok();
} catch (SQLException e) {
logger.warning("Couldn't send SQL Request: " + e.getMessage());
return ActionResult.error(I18NDict.Error.SQL_ERROR);
}
}
@Override
public boolean isStored(UUID uuid) {
final Connection connection = getProvider().getConnection();
if (connection == null) return false;
try {
final String sql = "SELECT uuid FROM nicko.DATA WHERE uuid = ?";
final PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, uuid.toString());
final ResultSet resultSet = statement.executeQuery();
return resultSet.next();
} catch (SQLException e) {
logger.warning("Couldn't check if data is present: " + e.getMessage());
return false;
}
}
@Override
public Optional<NickoProfile> retrieve(UUID uuid) {
final Connection connection = getProvider().getConnection();
if (connection == null) return Optional.empty();
if (!isStored(uuid)) return Optional.empty();
try {
final String sql = "SELECT * FROM nicko.DATA WHERE uuid = ?";
final PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, uuid.toString());
final ResultSet resultSet = statement.executeQuery();
String name = "";
String skin = "";
String locale = "";
boolean bungeecord = false;
while (resultSet.next()) {
name = resultSet.getString("name");
skin = resultSet.getString("skin");
locale = resultSet.getString("locale");
bungeecord = resultSet.getBoolean("bungeecord");
}
final NickoProfile profile = new NickoProfile(name, skin, Locale.fromCode(locale), bungeecord);
return Optional.of(profile);
} catch (SQLException e) {
logger.warning("Couldn't fetch profile: " + e.getMessage());
return Optional.empty();
}
}
@Override
public ActionResult delete(UUID uuid) {
final Connection connection = getProvider().getConnection();
if (connection == null) return ActionResult.error(I18NDict.Error.SQL_ERROR);
try {
final String sql = "DELETE FROM nicko.DATA WHERE uuid = ?";
final PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, uuid.toString());
int rows = statement.executeUpdate();
return (rows == 1 ? ActionResult.ok() : ActionResult.error(I18NDict.Error.SQL_ERROR));
} catch (SQLException e) {
logger.warning("Couldn't delete profile: " + e.getMessage());
return ActionResult.error(I18NDict.Error.SQL_ERROR);
}
}
private PreparedStatement getInsertStatement(Connection connection, UUID uuid, NickoProfile profile) throws SQLException {
final String sql = "INSERT IGNORE INTO nicko.DATA (`uuid`, `name`, `skin`, `locale`, `bungeecord`) VALUES (?, ?, ?, ?, ?)";
final PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, uuid.toString());
statement.setString(2, profile.getName() == null ? null : profile.getName());
statement.setString(3, profile.getSkin() == null ? null : profile.getSkin());
statement.setString(4, profile.getLocale().getCode());
statement.setBoolean(5, profile.isBungeecordTransfer());
return statement;
}
private PreparedStatement getUpdateStatement(Connection connection, UUID uuid, NickoProfile profile) throws SQLException {
final String sql = "UPDATE nicko.DATA SET name = ?, skin = ?, locale = ?, bungeecord = ? WHERE uuid = ?";
final PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, profile.getName() == null ? null : profile.getName());
statement.setString(2, profile.getSkin() == null ? null : profile.getSkin());
statement.setString(3, profile.getLocale().getCode());
statement.setBoolean(4, profile.isBungeecordTransfer());
statement.setString(5, uuid.toString());
return statement;
}
}

View file

@ -1,4 +1,4 @@
package xyz.atnrch.nicko.storage.sql;
package xyz.atnrch.nicko.storage.mariadb;
import org.mariadb.jdbc.MariaDbDataSource;
import xyz.atnrch.nicko.config.Configuration;
@ -10,15 +10,15 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.logging.Logger;
public class SQLStorageProvider implements StorageProvider {
private final Logger logger = Logger.getLogger("SQLStorageProvider");
public class MariaDBStorageProvider implements StorageProvider {
private final Logger logger = Logger.getLogger("MariaDBStorageProvider");
private final Configuration configuration;
private Connection connection;
private final String schemaName = "nicko";
public SQLStorageProvider(Configuration configuration) {
public MariaDBStorageProvider(Configuration configuration) {
this.configuration = configuration;
}
@ -40,7 +40,7 @@ public class SQLStorageProvider implements StorageProvider {
createTable();
return true;
} catch (SQLException e) {
logger.severe("Couldn't establish a connection to the MySQL database: " + e.getMessage());
logger.severe("Couldn't establish a connection to the MariaDB database: " + e.getMessage());
return false;
}
}

View file

@ -1,4 +1,4 @@
package xyz.atnrch.nicko.storage.sql;
package xyz.atnrch.nicko.storage.mysql;
import xyz.atnrch.nicko.appearance.ActionResult;
import xyz.atnrch.nicko.config.Configuration;
@ -15,20 +15,20 @@ import java.util.Optional;
import java.util.UUID;
import java.util.logging.Logger;
public class SQLStorage extends Storage {
public class MySQLStorage extends Storage {
private final Logger logger = Logger.getLogger("SQLStorage");
private final Configuration configuration;
private SQLStorageProvider provider;
private MySQLStorageProvider provider;
public SQLStorage(Configuration configuration) {
public MySQLStorage(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SQLStorageProvider getProvider() {
public MySQLStorageProvider getProvider() {
if (provider == null) {
provider = new SQLStorageProvider(configuration);
provider = new MySQLStorageProvider(configuration);
}
return provider;
}

View file

@ -0,0 +1,88 @@
package xyz.atnrch.nicko.storage.mysql;
import com.mysql.cj.jdbc.MysqlDataSource;
import xyz.atnrch.nicko.config.Configuration;
import xyz.atnrch.nicko.config.DataSourceConfiguration;
import xyz.atnrch.nicko.storage.StorageProvider;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.logging.Logger;
public class MySQLStorageProvider implements StorageProvider {
private final Logger logger = Logger.getLogger("MySQLStorageProvider");
private final Configuration configuration;
private Connection connection;
private final String schemaName = "nicko";
public MySQLStorageProvider(Configuration configuration) {
this.configuration = configuration;
}
@Override
public boolean init() {
try {
final MysqlDataSource dataSource = new MysqlDataSource();
final DataSourceConfiguration sqlConfiguration = configuration.getSqlConfiguration();
dataSource.setUrl("jdbc:mysql://" + sqlConfiguration.getAddress() + ":" + sqlConfiguration.getPort());
dataSource.setUser(sqlConfiguration.getUsername());
dataSource.setPassword(sqlConfiguration.getPassword());
connection = dataSource.getConnection();
connection.setAutoCommit(true);
final boolean initialized = connection != null && !connection.isClosed();
if (!initialized) return false;
createDatabase();
createTable();
return true;
} catch (SQLException e) {
logger.severe("Couldn't establish a connection to the MySQL database: " + e.getMessage());
return false;
}
}
@Override
public boolean close() {
if (connection == null) { return true; }
try {
connection.close();
return connection.isClosed();
} catch (SQLException e) {
return false;
}
}
private void createTable() throws SQLException {
final Connection connection = getConnection();
String query = "CREATE TABLE IF NOT EXISTS %s.DATA " +
"(uuid varchar(36) NOT NULL," +
"name varchar(16)," +
"skin varchar(16)," +
"locale char(2) NOT NULL," +
"bungeecord boolean NOT NULL," +
"PRIMARY KEY (uuid))";
query = query.replace("%s", schemaName);
final PreparedStatement statement = connection.prepareStatement(query);
statement.executeUpdate();
}
private void createDatabase() throws SQLException {
final Connection connection = getConnection();
String query = "CREATE DATABASE IF NOT EXISTS %s";
query = query.replace("%s", schemaName);
final PreparedStatement statement = connection.prepareStatement(query);
statement.executeUpdate();
}
public Connection getConnection() {
return connection;
}
}

View file

@ -5,6 +5,7 @@ import xyz.atnrch.nicko.NickoBukkit;
import xyz.atnrch.nicko.config.Configuration;
import xyz.atnrch.nicko.config.DataSourceConfiguration;
import org.junit.jupiter.api.*;
import xyz.atnrch.nicko.config.DefaultDataSources;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -15,7 +16,7 @@ public class NickoPluginTest {
public static void setup() {
final Configuration config = new Configuration(
new DataSourceConfiguration(true, "127.0.0.1", 3306, "root", "12345"),
DataSourceConfiguration.REDIS_EMPTY,
DefaultDataSources.REDIS_EMPTY,
"",
false);
MockBukkit.mock();

View file

@ -8,7 +8,7 @@ import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import xyz.atnrch.nicko.NickoBukkit;
import xyz.atnrch.nicko.config.Configuration;
import xyz.atnrch.nicko.config.DataSourceConfiguration;
import xyz.atnrch.nicko.config.DefaultDataSources;
import xyz.atnrch.nicko.i18n.I18N;
import xyz.atnrch.nicko.i18n.I18NDict;
import xyz.atnrch.nicko.i18n.ItemTranslation;
@ -22,8 +22,8 @@ public class ItemTranslationTest {
@BeforeAll
public static void setup() {
final Configuration config = new Configuration(
DataSourceConfiguration.SQL_EMPTY,
DataSourceConfiguration.REDIS_EMPTY,
DefaultDataSources.SQL_EMPTY,
DefaultDataSources.REDIS_EMPTY,
"",
false);
MockBukkit.mock();

View file

@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test;
import xyz.atnrch.nicko.NickoBukkit;
import xyz.atnrch.nicko.config.Configuration;
import xyz.atnrch.nicko.config.DataSourceConfiguration;
import xyz.atnrch.nicko.config.DefaultDataSources;
import xyz.atnrch.nicko.i18n.I18N;
import xyz.atnrch.nicko.i18n.I18NDict;
import xyz.atnrch.nicko.i18n.Locale;
@ -21,8 +22,8 @@ public class TranslationTest {
@BeforeAll
public static void setup() {
final Configuration config = new Configuration(
DataSourceConfiguration.SQL_EMPTY,
DataSourceConfiguration.REDIS_EMPTY,
DefaultDataSources.SQL_EMPTY,
DefaultDataSources.REDIS_EMPTY,
"",
false);
MockBukkit.mock();

View file

@ -7,6 +7,7 @@ import xyz.atnrch.nicko.NickoBukkit;
import xyz.atnrch.nicko.config.Configuration;
import xyz.atnrch.nicko.config.DataSourceConfiguration;
import xyz.atnrch.nicko.appearance.ActionResult;
import xyz.atnrch.nicko.config.DefaultDataSources;
import xyz.atnrch.nicko.profile.NickoProfile;
import org.junit.jupiter.api.*;
@ -23,7 +24,7 @@ public class BrokenSQLTest {
public static void setup() {
final Configuration config = new Configuration(
new DataSourceConfiguration(true, "127.0.0.1", 3306, "root", ""),
DataSourceConfiguration.REDIS_EMPTY,
DefaultDataSources.REDIS_EMPTY,
"",
false);
final ServerMock server = MockBukkit.mock();

View file

@ -6,6 +6,7 @@ import xyz.atnrch.nicko.NickoBukkit;
import xyz.atnrch.nicko.appearance.ActionResult;
import xyz.atnrch.nicko.config.Configuration;
import xyz.atnrch.nicko.config.DataSourceConfiguration;
import xyz.atnrch.nicko.config.DefaultDataSources;
import xyz.atnrch.nicko.i18n.Locale;
import xyz.atnrch.nicko.profile.NickoProfile;
import xyz.atnrch.nicko.storage.PlayerDataStore;
@ -24,7 +25,7 @@ public class SQLStorageTest {
public static void setup() {
final Configuration config = new Configuration(
new DataSourceConfiguration(true, "127.0.0.1", 3306, "root", "12345"),
DataSourceConfiguration.REDIS_EMPTY,
DefaultDataSources.REDIS_EMPTY,
"",
false);

View file

@ -6,6 +6,7 @@ import be.seeseemelk.mockbukkit.entity.PlayerMock;
import xyz.atnrch.nicko.NickoBukkit;
import xyz.atnrch.nicko.config.Configuration;
import xyz.atnrch.nicko.config.DataSourceConfiguration;
import xyz.atnrch.nicko.config.DefaultDataSources;
import xyz.atnrch.nicko.profile.NickoProfile;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@ -23,8 +24,8 @@ public class MapCacheTest {
@BeforeAll
public static void setup() {
final Configuration config = new Configuration(
DataSourceConfiguration.SQL_EMPTY,
DataSourceConfiguration.REDIS_EMPTY,
DefaultDataSources.SQL_EMPTY,
DefaultDataSources.REDIS_EMPTY,
"",
false);
final ServerMock server = MockBukkit.mock();

View file

@ -8,6 +8,7 @@ import xyz.atnrch.nicko.NickoBukkit;
import xyz.atnrch.nicko.appearance.ActionResult;
import xyz.atnrch.nicko.config.Configuration;
import xyz.atnrch.nicko.config.DataSourceConfiguration;
import xyz.atnrch.nicko.config.DefaultDataSources;
import xyz.atnrch.nicko.profile.NickoProfile;
import xyz.atnrch.nicko.storage.PlayerDataStore;
@ -23,7 +24,7 @@ public class RedisCacheTest {
@BeforeAll
public static void setup() {
final Configuration config = new Configuration(
DataSourceConfiguration.SQL_EMPTY,
DefaultDataSources.SQL_EMPTY,
new DataSourceConfiguration(true, "127.0.0.1", 6379, "", ""),
"",
false);