diff --git a/pom.xml b/pom.xml
index 38db221..20ac7d5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,6 +18,18 @@
UTF-8
+
+
+
+ org.junit
+ junit-bom
+ 5.11.2
+ pom
+ import
+
+
+
+
${project.name}
@@ -36,6 +48,51 @@
${java.version}
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.0
+
+ false
+
+
+ *:*
+
+ module-info.class
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+ META-INF/*.MF
+
+
+
+
+
+ org.spigotmc:1.12.2-R0.1-SNAPSHOT
+ org.junit.jupiter:junit-jupiter
+
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+ maven-surefire-plugin
+ 3.5.1
+
+
+ org.apache.maven.surefire
+ surefire-junit-platform
+ 3.5.1
+
+
+
@@ -55,6 +112,7 @@
org.spigotmc
spigot-api
1.12.2-R0.1-SNAPSHOT
+ provided
org.projectlombok
@@ -66,5 +124,26 @@
jsonmessage
1.3.1
+
+ com.mysql
+ mysql-connector-j
+ 9.0.0
+
+
+ com.google.protobuf
+ protobuf-java
+
+
+
+
+ com.zaxxer
+ HikariCP
+ 4.0.3
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
diff --git a/src/main/java/jp/azisaba/lgw/kdstatus/KDStatusReloaded.java b/src/main/java/jp/azisaba/lgw/kdstatus/KDStatusReloaded.java
index 0dead57..d4548d7 100644
--- a/src/main/java/jp/azisaba/lgw/kdstatus/KDStatusReloaded.java
+++ b/src/main/java/jp/azisaba/lgw/kdstatus/KDStatusReloaded.java
@@ -1,7 +1,9 @@
package jp.azisaba.lgw.kdstatus;
import java.io.File;
+import java.sql.PreparedStatement;
import java.sql.SQLException;
+import java.util.logging.Level;
import jp.azisaba.lgw.kdstatus.sql.*;
import jp.azisaba.lgw.kdstatus.task.DBConnectionCheckTask;
@@ -31,7 +33,7 @@ public class KDStatusReloaded extends JavaPlugin {
private SQLHandler sqlHandler = null;
- public MySQLHandler sql;
+ public HikariMySQLDatabase sql;
private PlayerDataMySQLController kdData;
@@ -55,22 +57,33 @@ public void onEnable() {
sqlHandler = new SQLHandler(new File(getDataFolder(), "playerData.db"));
kdDataContainer = new KillDeathDataContainer(new PlayerDataSQLController(sqlHandler).init());
- sql = new MySQLHandler();
-
this.kdData = new PlayerDataMySQLController(this);
- try {
- sql.connect();
- } catch (SQLException throwables) {
- throwables.printStackTrace();
- getLogger().warning("Failed to connect SQLDatabase.");
- }
+ DBAuthConfig.loadAuthConfig();
+ sql = DBAuthConfig.getDatabase(getLogger(), 10);
+
+ sql.connect();
+
if(sql.isConnected()){
+ getLogger().info("SQL Testing...");
+ try(PreparedStatement pstmt = sql.getConnection().prepareStatement("SELECT 1")) {
+ if(pstmt.executeQuery().next()) {
+ getLogger().info("SQL Test was success!");
+ } else {
+ getLogger().warning("Failed to test SQL Connection");
+ }
+ } catch (SQLException e) {
+ getLogger().log(Level.SEVERE, "Error on SQL Testing", e);
+ }
+ getLogger().info("SQL Test is finished!");
+
getLogger().info("Connected SQLDatabase!");
//ここでテーブル作るぞ
this.kdData.createTable();
+ getLogger().info("Table was created!");
+
}
saveTask = new SavePlayerDataTask(this);
diff --git a/src/main/java/jp/azisaba/lgw/kdstatus/sql/DBAuthConfig.java b/src/main/java/jp/azisaba/lgw/kdstatus/sql/DBAuthConfig.java
new file mode 100644
index 0000000..3da737f
--- /dev/null
+++ b/src/main/java/jp/azisaba/lgw/kdstatus/sql/DBAuthConfig.java
@@ -0,0 +1,47 @@
+package jp.azisaba.lgw.kdstatus.sql;
+
+import jp.azisaba.lgw.kdstatus.KDStatusReloaded;
+import lombok.AccessLevel;
+import lombok.Getter;
+
+import java.util.logging.Logger;
+
+/**
+ * Safe auth config loader
+ */
+public class DBAuthConfig {
+ @Getter(AccessLevel.PROTECTED)
+ private static String host;
+ @Getter(AccessLevel.PROTECTED)
+ private static String port;
+ @Getter(AccessLevel.PROTECTED)
+ private static String database;
+ @Getter(AccessLevel.PROTECTED)
+ private static String user;
+ @Getter(AccessLevel.PROTECTED)
+ private static String password;
+
+ public static void loadAuthConfig() {
+ host = getConfigAsString("host");
+ port = getConfigAsString("port");
+ database = getConfigAsString("database");
+ user = getConfigAsString("username");
+ password = getConfigAsString("password");
+ }
+
+ public static HikariMySQLDatabase getDatabase(Logger logger, int maxPoolSize) {
+ return new HikariMySQLDatabase(
+ logger,
+ maxPoolSize,
+ host,
+ port,
+ database,
+ user,
+ password
+ );
+ }
+
+ private static String getConfigAsString(String path) {
+ return KDStatusReloaded.getPlugin().getConfig().getString(path);
+ }
+}
diff --git a/src/main/java/jp/azisaba/lgw/kdstatus/sql/HikariMySQLDatabase.java b/src/main/java/jp/azisaba/lgw/kdstatus/sql/HikariMySQLDatabase.java
new file mode 100644
index 0000000..95ba602
--- /dev/null
+++ b/src/main/java/jp/azisaba/lgw/kdstatus/sql/HikariMySQLDatabase.java
@@ -0,0 +1,167 @@
+package jp.azisaba.lgw.kdstatus.sql;
+
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.function.Consumer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Wrapper class of HikariDataSource for MySQL
+ */
+@RequiredArgsConstructor
+public class HikariMySQLDatabase {
+ private final Logger logger;
+ private final int maxPoolSize;
+ private final String host, port, databaseName, user, password;
+
+ @Getter
+ private boolean initialized;
+ private HikariDataSource hikari;
+
+ public boolean isConnected() {
+ if(hikari == null) return false;
+ return hikari.isRunning();
+ }
+
+ /**
+ * connect to database.
+ */
+ public void connect() {
+ if (initialized) {
+ logger.warning("Database is already initialized!");
+ return;
+ }
+ String jdbcUrl = String.format(
+ "jdbc:mysql://%s:%s/%s?useSSL=false",
+ host,
+ port,
+ databaseName
+ );
+
+ HikariConfig config = new HikariConfig();
+ config.setUsername(user);
+ config.setPassword(password);
+ config.setJdbcUrl(jdbcUrl);
+ config.setMaximumPoolSize(maxPoolSize);
+// config.setConnectionInitSql("SELECT 1");
+ config.setAutoCommit(true);
+ config.setConnectionTimeout(1500); // TODO change this
+ hikari = new HikariDataSource(config);
+ }
+
+ /**
+ * close database connection
+ */
+ public void close() {
+ hikari.close();
+ initialized = false;
+ }
+
+ /**
+ * reconnect database
+ */
+ public void reconnect() {
+ close();
+ connect();
+ }
+
+ /**
+ * CAUTION: this method throws {@link SQLException} so need to catch it
+ *
+ * @return a connection of database
+ * @throws SQLException from {@link HikariDataSource#getConnection()}
+ */
+ public Connection getConnection() throws SQLException {
+ return hikari.getConnection();
+ }
+
+ /**
+ * get a connection (Safer than {@link #getConnection()})
+ *
+ * @return database connection. If failed, return null.
+ */
+
+ public Connection getConnectionOrNull() {
+ try {
+ return hikari.getConnection();
+ } catch (SQLException e) {
+ logger.log(Level.SEVERE, "Failed to get connection", e);
+ return null;
+ }
+ }
+
+
+ public PreparedStatement preparedStatement(@NonNull String sql) {
+ Connection conn = getConnectionOrNull();
+ if (conn == null) {
+ logger.log(Level.SEVERE, "Failed to create preparedStatement: connection is null");
+ return null;
+ }
+ try {
+ return conn.prepareStatement(sql);
+ } catch (SQLException e) {
+ logger.log(Level.SEVERE, "Failed to create preparedStatement", e);
+ return null;
+ }
+ }
+
+ /**
+ * execute a SQL statement
+ *
+ * @param sql SQL statement
+ * @param pstmtConsumer to process PreparedStatement (ex. {@link PreparedStatement#setString(int, String)})
+ * @return result of execution. If failed, return null
+ */
+
+ public ResultSet executeQuery(@NonNull String sql, Consumer pstmtConsumer) {
+ // get a connection
+ Connection conn = getConnectionOrNull();
+ if (conn == null) {
+ logger.warning("Failed to execute query: connection is null");
+ return null;
+ }
+
+ // execute query
+ try {
+ PreparedStatement pstmt = conn.prepareStatement(sql);
+ if (pstmtConsumer != null) {
+ pstmtConsumer.accept(pstmt);
+ }
+ return pstmt.executeQuery();
+ } catch (SQLException e) {
+ logger.log(Level.SEVERE, "Failed to execute query", e);
+ return null;
+ }
+ }
+
+ /**
+ * @param sql SQL statement
+ * @return is succeeded
+ */
+ public boolean executeUpdate(String sql) {
+ Connection conn = getConnectionOrNull();
+ if (conn == null) {
+ logger.severe("Failed to execute update: connection is null");
+ return false;
+ }
+
+ try {
+ PreparedStatement pstmt = conn.prepareStatement(sql);
+ pstmt.executeUpdate();
+ return true;
+ } catch (SQLException e) {
+ logger.log(Level.SEVERE, "Failed to execute update", e);
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/jp/azisaba/lgw/kdstatus/sql/MySQLHandler.java b/src/main/java/jp/azisaba/lgw/kdstatus/sql/MySQLHandler.java
index f05e6a0..4cd3be8 100644
--- a/src/main/java/jp/azisaba/lgw/kdstatus/sql/MySQLHandler.java
+++ b/src/main/java/jp/azisaba/lgw/kdstatus/sql/MySQLHandler.java
@@ -1,6 +1,7 @@
package jp.azisaba.lgw.kdstatus.sql;
import jp.azisaba.lgw.kdstatus.KDStatusReloaded;
+import lombok.Getter;
import java.sql.Connection;
import java.sql.DriverManager;
@@ -10,6 +11,7 @@
public class MySQLHandler {
+ @Getter
private Connection connection;
private final String host = KDStatusReloaded.getPlugin().getConfig().getString("host");
@@ -22,10 +24,18 @@ public boolean isConnected(){
return (connection != null);
}
+ /**
+ * Internal method for connect MySQL
+ * @throws SQLException from {@link DriverManager#getConnection(String, String, String)}
+ */
public void connect() throws SQLException {
-
- if(!isConnected())
- connection = DriverManager.getConnection("jdbc:mysql://" + host +":"+ port + "/" + database + "?useSLL=false",user,password );
+ if(isConnected()) return;
+ String jdbcUrl = String.format("jdbc:mysql://%s:%s/%s?useSSL=false",
+ DBAuthConfig.getHost(),
+ DBAuthConfig.getPort(),
+ DBAuthConfig.getDatabase()
+ );
+ connection = DriverManager.getConnection(jdbcUrl, DBAuthConfig.getUser(), DBAuthConfig.getPassword());
}
public void reconnect() throws SQLException {
@@ -33,19 +43,12 @@ public void reconnect() throws SQLException {
connect();
}
- public void close(){
+ public void close() throws SQLException {
if(isConnected()) {
- try {
- connection.close();
- } catch (SQLException throwables) {
- throwables.printStackTrace();
- }
+ connection.close();
}
connection = null;
}
- public Connection getConnection() {
- return connection;
- }
}
diff --git a/src/main/java/jp/azisaba/lgw/kdstatus/sql/PlayerDataController.java b/src/main/java/jp/azisaba/lgw/kdstatus/sql/PlayerDataController.java
new file mode 100644
index 0000000..58c76ad
--- /dev/null
+++ b/src/main/java/jp/azisaba/lgw/kdstatus/sql/PlayerDataController.java
@@ -0,0 +1,23 @@
+package jp.azisaba.lgw.kdstatus.sql;
+
+import jp.azisaba.lgw.kdstatus.utils.TimeUnit;
+import lombok.NonNull;
+
+import java.math.BigInteger;
+import java.sql.ResultSet;
+import java.util.List;
+import java.util.UUID;
+
+public interface PlayerDataController {
+ boolean createTable();
+ boolean exist(UUID uuid);
+ boolean create(KDUserData data);
+ boolean update(KDUserData data);
+ BigInteger getKills(@NonNull UUID uuid, @NonNull TimeUnit unit);
+ BigInteger getDeaths(@NonNull UUID uuid);
+ String getName(UUID uuid);
+ long getLastUpdated(@NonNull UUID uuid);
+ ResultSet getRawData(@NonNull UUID uuid);
+ int getRank(UUID uuid, TimeUnit unit);
+ List getTopKillRankingData(TimeUnit unit, int count);
+}
diff --git a/src/main/java/jp/azisaba/lgw/kdstatus/sql/PlayerDataHikariMySQLController.java b/src/main/java/jp/azisaba/lgw/kdstatus/sql/PlayerDataHikariMySQLController.java
new file mode 100644
index 0000000..d38f223
--- /dev/null
+++ b/src/main/java/jp/azisaba/lgw/kdstatus/sql/PlayerDataHikariMySQLController.java
@@ -0,0 +1,127 @@
+package jp.azisaba.lgw.kdstatus.sql;
+
+import jp.azisaba.lgw.kdstatus.utils.TimeUnit;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
+import java.math.BigInteger;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+@RequiredArgsConstructor
+public class PlayerDataHikariMySQLController implements PlayerDataController {
+ private final Logger logger;
+ private HikariMySQLDatabase db;
+
+ public void connect() {}
+
+ @Override
+ public boolean createTable() {
+ return db.executeUpdate("CREATE TABLE IF NOT EXISTS kill_death_data "
+ + "(uuid VARCHAR(64) NOT NULL ,name VARCHAR(36) NOT NULL," +
+ "kills INT DEFAULT 0, " +
+ "deaths INT DEFAULT 0 ," +
+ "daily_kills INT DEFAULT 0," +
+ "monthly_kills INT DEFAULT 0," +
+ "yearly_kills INT DEFAULT 0," +
+ "last_updated BIGINT DEFAULT -1 )");
+ }
+
+ @Override
+ public boolean exist(UUID uuid) {
+ try(PreparedStatement pstmt = db.preparedStatement("SELECT * FROM kill_death_data WHERE name=?")) {
+ if(pstmt == null) return false;
+ pstmt.setString(1, uuid.toString());
+ // TODO check this resource leak
+ return pstmt.executeQuery().next();
+ } catch (SQLException e) {
+ logger.log(Level.SEVERE, "Failed to get exists", e);
+ return false;
+ }
+ }
+
+ @Override
+ public boolean create(KDUserData data) {
+ if(exist(data.getUuid())) {
+ logger.warning("This user data was already created!");
+ return false;
+ }
+
+ try(PreparedStatement ps = db.preparedStatement("INSERT INTO kill_death_data (uuid,name,kills,deaths,daily_kills,monthly_kills,yearly_kills,last_updated) VALUES (?,?,?,?,?,?,?,?)")) {
+ ps.setString(1,data.getUuid().toString());
+ ps.setString(2,data.getName());
+ ps.setInt(3,data.getKills(TimeUnit.LIFETIME));
+ ps.setInt(4,data.getDeaths());
+ ps.setInt(5,data.getKills(TimeUnit.DAILY));
+ ps.setInt(6,data.getKills(TimeUnit.MONTHLY));
+ ps.setInt(7,data.getKills(TimeUnit.YEARLY));
+ ps.setLong(8,data.getLastUpdated());
+ ps.executeUpdate();
+ return true;
+ } catch (SQLException e) {
+ logger.log(Level.SEVERE, "Failed to create userdata", e);
+ return false;
+ }
+ }
+
+ @Override
+ public boolean update(KDUserData data) {
+ try(PreparedStatement ps = db.preparedStatement("UPDATE kill_death_data SET name=? ,kills=? ,deaths=? ,daily_kills=? ,monthly_kills=? ,yearly_kills=? ,last_updated=? WHERE uuid=?")) {
+ ps.setString(8,data.getUuid().toString());
+ ps.setString(1,data.getName());
+ ps.setInt(2,data.getKills(TimeUnit.LIFETIME));
+ ps.setInt(3,data.getDeaths());
+ ps.setInt(4,data.getKills(TimeUnit.DAILY));
+ ps.setInt(5,data.getKills(TimeUnit.MONTHLY));
+ ps.setInt(6,data.getKills(TimeUnit.YEARLY));
+ ps.setLong(7,data.getLastUpdated());
+ ps.executeUpdate();
+ return true;
+ } catch (SQLException e) {
+ logger.log(Level.SEVERE, "Failed to update userdata", e);
+ return false;
+ }
+ }
+
+ @Override
+ public BigInteger getKills(@NonNull UUID uuid, @NonNull TimeUnit unit) {
+ return null;
+ }
+
+ @Override
+ public BigInteger getDeaths(@NonNull UUID uuid) {
+ return null;
+ }
+
+ @Override
+ public String getName(UUID uuid) {
+ return "";
+ }
+
+ @Override
+ public long getLastUpdated(@NonNull UUID uuid) {
+ return 0;
+ }
+
+ @Override
+ public ResultSet getRawData(@NonNull UUID uuid) {
+ return null;
+ }
+
+ @Override
+ public int getRank(UUID uuid, TimeUnit unit) {
+ return 0;
+ }
+
+ @Override
+ public List getTopKillRankingData(TimeUnit unit, int count) {
+ return Collections.emptyList();
+ }
+}
diff --git a/src/main/java/jp/azisaba/lgw/kdstatus/sql/PlayerDataMySQLController.java b/src/main/java/jp/azisaba/lgw/kdstatus/sql/PlayerDataMySQLController.java
index 12b9294..28d4c9e 100644
--- a/src/main/java/jp/azisaba/lgw/kdstatus/sql/PlayerDataMySQLController.java
+++ b/src/main/java/jp/azisaba/lgw/kdstatus/sql/PlayerDataMySQLController.java
@@ -19,6 +19,7 @@ public class PlayerDataMySQLController {
public void createTable(){
try{
+ plugin.getLogger().info("Creating database table...");
PreparedStatement ps = plugin.sql.getConnection().prepareStatement("CREATE TABLE IF NOT EXISTS kill_death_data "
+ "(uuid VARCHAR(64) NOT NULL ,name VARCHAR(36) NOT NULL," +
"kills INT DEFAULT 0, " +
@@ -29,6 +30,8 @@ public void createTable(){
"last_updated BIGINT DEFAULT -1 )");
ps.executeUpdate();
+ ps.close();
+ plugin.getLogger().info("Successfully to create database table!");
}catch (SQLException e){e.printStackTrace();}
@@ -221,13 +224,14 @@ public ResultSet getRawData(@NonNull UUID uuid) {
return null;
}
- private static final String RANK_QUERY = "SELECT kills, uuid, (SELECT count(*) FROM kill_death_data i2 WHERE i1.%s < i2.%s) + 1 AS 'rank' FROM kill_death_data i1 WHERE uuid=? and last_updated > ?";
+ public static final String RANK_QUERY = "SELECT * FROM (SELECT uuid, ?, last_updated, RANK() over (ORDER BY ? DESC) as 'rank' FROM kill_death_data WHERE last_updated > ?) s WHERE s.uuid=?";
public int getRank(UUID uuid,TimeUnit unit){
- String columnName = unit.getSqlColumnName();
- try(PreparedStatement p = plugin.sql.getConnection().prepareStatement(String.format(RANK_QUERY, columnName, columnName))) {
- p.setString(1, uuid.toString());
- p.setLong(2, TimeUnit.getFirstMilliSecond(unit));
+ try(PreparedStatement p = plugin.sql.getConnection().prepareStatement(RANK_QUERY)) {
+ p.setString(1, unit.getSqlColumnName());
+ p.setString(2, unit.getSqlColumnName());
+ p.setLong(3, TimeUnit.getFirstMilliSecond(unit));
+ p.setString(4, uuid.toString());
ResultSet result = p.executeQuery();
if(result.next()) {
return result.getInt("rank");
diff --git a/src/main/java/jp/azisaba/lgw/kdstatus/task/DBConnectionCheckTask.java b/src/main/java/jp/azisaba/lgw/kdstatus/task/DBConnectionCheckTask.java
index 5f558dc..7f94cab 100644
--- a/src/main/java/jp/azisaba/lgw/kdstatus/task/DBConnectionCheckTask.java
+++ b/src/main/java/jp/azisaba/lgw/kdstatus/task/DBConnectionCheckTask.java
@@ -30,11 +30,7 @@ public void run() {
plugin.getLogger().log(Level.WARNING, "Failed to pass health check", e);
}
plugin.getLogger().info("Reconnecting to database...");
- try {
- plugin.sql.reconnect();
- plugin.getLogger().info("Successfully to reconnect database!");
- } catch (SQLException ex) {
- plugin.getLogger().log(Level.SEVERE, "Failed to reconnect database", ex);
- }
+ plugin.sql.reconnect();
+ plugin.getLogger().info("Successfully to reconnect database!");
}
}
diff --git a/src/test/java/jp/azisaba/lgw/kdstatus/utils/UUIDConverterTest.java b/src/test/java/jp/azisaba/lgw/kdstatus/utils/UUIDConverterTest.java
new file mode 100644
index 0000000..7967c3f
--- /dev/null
+++ b/src/test/java/jp/azisaba/lgw/kdstatus/utils/UUIDConverterTest.java
@@ -0,0 +1,16 @@
+package jp.azisaba.lgw.kdstatus.utils;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class UUIDConverterTest {
+ @Test
+ public void UUIDToString() {
+ UUID uuid = UUID.randomUUID();
+ assertEquals(uuid.toString(), UUIDConverter.insertDashUUID(uuid.toString().replace("-", "")));
+ assertEquals(uuid, UUID.fromString(UUIDConverter.insertDashUUID(UUIDConverter.convert(uuid))));
+ }
+}