Skip to content
This repository was archived by the owner on Nov 2, 2021. It is now read-only.

Commit

Permalink
Refactored database manager
Browse files Browse the repository at this point in the history
  • Loading branch information
PyvesB committed Dec 6, 2017
1 parent abcf282 commit ba8de0a
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 148 deletions.
31 changes: 25 additions & 6 deletions src/main/java/com/hm/achievement/AdvancedAchievements.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@
import com.hm.achievement.command.ToggleCommand;
import com.hm.achievement.command.TopCommand;
import com.hm.achievement.command.WeekCommand;
import com.hm.achievement.db.AbstractSQLDatabaseManager;
import com.hm.achievement.db.AsyncCachedRequestsSender;
import com.hm.achievement.db.DatabaseCacheManager;
import com.hm.achievement.db.SQLDatabaseManager;
import com.hm.achievement.db.MySQLDatabaseManager;
import com.hm.achievement.db.PostgreSQLDatabaseManager;
import com.hm.achievement.db.SQLiteDatabaseManager;
import com.hm.achievement.gui.CategoryGUI;
import com.hm.achievement.gui.MainGUI;
import com.hm.achievement.listener.AchieveArrowListener;
Expand Down Expand Up @@ -171,8 +174,8 @@ public class AdvancedAchievements extends JavaPlugin implements Reloadable {
private CommentedYamlConfiguration gui;

// Database related.
private final SQLDatabaseManager databaseManager;
private final DatabaseCacheManager cacheManager;
private AbstractSQLDatabaseManager databaseManager;
private AsyncCachedRequestsSender asyncCachedRequestsSender;

// Plugin runnable classes.
Expand All @@ -199,7 +202,6 @@ public class AdvancedAchievements extends JavaPlugin implements Reloadable {
public AdvancedAchievements() {
successfulLoad = true;
overrideDisable = false;
databaseManager = new SQLDatabaseManager(this);
cacheManager = new DatabaseCacheManager(this);
achievementsAndDisplayNames = new HashMap<>();
sortedThresholds = new HashMap<>();
Expand Down Expand Up @@ -232,7 +234,7 @@ public void onEnable() {
registerListeners();
initialiseTabCompleter();
initialiseGUIs();
databaseManager.initialise();
selectAndInitialiseDatabaseManager();

// Error while loading database do not do any further work.
if (overrideDisable) {
Expand Down Expand Up @@ -702,6 +704,23 @@ private void initialiseGUIs() {
mainGUI = new MainGUI(this);
categoryGUI = new CategoryGUI(this);
}

/**
* Selects a database manager implementation (SQLite, MySQL or PostgreSQL) and initialises it.
*/
private void selectAndInitialiseDatabaseManager() {
String dataHandler = config.getString("DatabaseType", "sqlite");
if ("mysql".equalsIgnoreCase(dataHandler)) {
databaseManager = new MySQLDatabaseManager(this);
} else if ("postgresql".equalsIgnoreCase(dataHandler)) {
databaseManager = new PostgreSQLDatabaseManager(this);
} else {
// User has specified "sqlite" or an invalid type.
databaseManager = new SQLiteDatabaseManager(this);
}
databaseManager.initialise();
}


/**
* Launches asynchronous scheduled tasks.
Expand Down Expand Up @@ -927,14 +946,14 @@ public Map<String, List<Long>> getSortedThresholds() {
return sortedThresholds;
}

public SQLDatabaseManager getDatabaseManager() {
public AbstractSQLDatabaseManager getDatabaseManager() {
return databaseManager;
}

public DatabaseCacheManager getCacheManager() {
return cacheManager;
}

public RewardParser getRewardParser() {
return rewardParser;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package com.hm.achievement.db;

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
Expand All @@ -27,34 +24,30 @@
import com.hm.achievement.category.MultipleAchievements;
import com.hm.achievement.category.NormalAchievements;
import com.hm.achievement.utils.Reloadable;
import com.hm.mcshared.file.FileManager;

/**
* Class used to deal with the database and provide functions to evaluate common queries and retrieve the relevant
* results.
* Abstract class in charge of factoring out common functionality for the database manager.
*
* @author Pyves
*
*/
public class SQLDatabaseManager implements Reloadable {
public abstract class AbstractSQLDatabaseManager implements Reloadable {

private final AdvancedAchievements plugin;
protected final AdvancedAchievements plugin;
// Used to do some write operations to the database asynchronously.
private final ExecutorService pool;
protected final ExecutorService pool;
// Connection to the database; remains opened and shared.
private final AtomicReference<Connection> sqlConnection;

private volatile String databaseAddress;
private volatile String databaseUser;
private volatile String databasePassword;
private volatile String tablePrefix;
private volatile String additionalConnectionOptions;
private volatile DatabaseType databaseType;
protected final AtomicReference<Connection> sqlConnection;

protected volatile String databaseAddress;
protected volatile String databaseUser;
protected volatile String databasePassword;
protected volatile String tablePrefix;
protected volatile String additionalConnectionOptions;

private DateFormat dateFormat;
private boolean configBookChronologicalOrder;
private boolean configDatabaseBackup;

public SQLDatabaseManager(AdvancedAchievements plugin) {
public AbstractSQLDatabaseManager(AdvancedAchievements plugin) {
this.plugin = plugin;
// We expect to execute many short writes to the database. The pool can grow dynamically under high load and
// allows to reuse threads.
Expand All @@ -65,7 +58,6 @@ public SQLDatabaseManager(AdvancedAchievements plugin) {
@Override
public void extractConfigurationParameters() {
configBookChronologicalOrder = plugin.getPluginConfig().getBoolean("BookChronologicalOrder", true);
configDatabaseBackup = plugin.getPluginConfig().getBoolean("DatabaseBackup", true);
String localeString = plugin.getPluginConfig().getString("DateLocale", "en");
boolean dateDisplayTime = plugin.getPluginConfig().getBoolean("DateDisplayTime", false);
Locale locale = new Locale(localeString);
Expand All @@ -77,44 +69,22 @@ public void extractConfigurationParameters() {
}

/**
* Initialises database system and plugin settings.
* Initialises the database system by extracting settings, performing setup tasks and updating schemas if necessary.
*/
public void initialise() {
plugin.getLogger().info("Initialising database... ");

// Load plugin settings.
configurationLoad();
tablePrefix = plugin.getPluginConfig().getString("TablePrefix", "");
additionalConnectionOptions = plugin.getPluginConfig().getString("AdditionalConnectionOptions", "");

// Check if JDBC library for the specified database system is available.
try {
if (databaseType == DatabaseType.SQLITE) {
Class.forName("org.sqlite.JDBC");
} else if (databaseType == DatabaseType.MYSQL) {
Class.forName("com.mysql.jdbc.Driver");
} else {
Class.forName("org.postgresql.Driver");
}
performPreliminaryTasks();
} catch (ClassNotFoundException e) {
plugin.getLogger().severe(
"The JBDC library for your database type was not found. Please read the plugin's support for more information.");
plugin.setSuccessfulLoad(false);
}

if (configDatabaseBackup && databaseType == DatabaseType.SQLITE) {
File backup = new File(plugin.getDataFolder(), "achievements.db.bak");
// Only do a daily backup for the .db file.
if (System.currentTimeMillis() - backup.lastModified() > 86400000L || backup.length() == 0L) {
plugin.getLogger().info("Backing up database file...");
try {
FileManager fileManager = new FileManager("achievements.db", plugin);
fileManager.backupFile();
} catch (IOException e) {
plugin.getLogger().log(Level.SEVERE, "Error while backing up database file:", e);
plugin.setSuccessfulLoad(false);
}
}
}

// Try to establish connection with database; stays opened until explicitely closed by the plugin..
Connection conn = getSQLConnection();

Expand All @@ -135,33 +105,11 @@ public void initialise() {
}

/**
* Loads plugin configuration and sets parameters relevant to the database system. These parameters are not reloaded
* and require a full server restart to change.
* Performs any needed tasks before opening a connection to the database.
*
* @throws ClassNotFoundException
*/
private void configurationLoad() {
tablePrefix = plugin.getPluginConfig().getString("TablePrefix", "");
additionalConnectionOptions = plugin.getPluginConfig().getString("AdditionalConnectionOptions", "");

String dataHandler = plugin.getPluginConfig().getString("DatabaseType", "sqlite");
if ("mysql".equalsIgnoreCase(dataHandler)) {
// Get parameters from the MySQL config category.
databaseType = DatabaseType.MYSQL;
databaseAddress = plugin.getPluginConfig().getString("MYSQL.Database",
"jdbc:mysql://localhost:3306/minecraft");
databaseUser = plugin.getPluginConfig().getString("MYSQL.User", "root");
databasePassword = plugin.getPluginConfig().getString("MYSQL.Password", "root");
} else if ("postgresql".equalsIgnoreCase(dataHandler)) {
// Get parameters from the PostgreSQL config category.
databaseType = DatabaseType.POSTGRESQL;
databaseAddress = plugin.getPluginConfig().getString("POSTGRESQL.Database",
"jdbc:postgresql://localhost:5432/minecraft");
databaseUser = plugin.getPluginConfig().getString("POSTGRESQL.User", "root");
databasePassword = plugin.getPluginConfig().getString("POSTGRESQL.Password", "root");
} else {
// No extra parameters to retrieve!
databaseType = DatabaseType.SQLITE;
}
}
protected abstract void performPreliminaryTasks() throws ClassNotFoundException;

/**
* Shuts the thread pool down and closes connection to database.
Expand Down Expand Up @@ -216,26 +164,7 @@ protected Connection getSQLConnection() {
* @return connection object to database
* @throws SQLException
*/
private Connection createSQLConnection() throws SQLException {
if (databaseType == DatabaseType.SQLITE) {
File dbfile = new File(plugin.getDataFolder(), "achievements.db");
try {
if (dbfile.createNewFile()) {
plugin.getLogger().info("Successfully created database file.");
}
} catch (IOException e) {
plugin.getLogger().log(Level.SEVERE, "Error while creating database file: ", e);
plugin.setSuccessfulLoad(false);
}
return DriverManager.getConnection("jdbc:sqlite:" + dbfile);
} else if (databaseType == DatabaseType.MYSQL) {
return DriverManager.getConnection(databaseAddress + "?useSSL=false&autoReconnect=true"
+ additionalConnectionOptions + "&user=" + databaseUser + "&password=" + databasePassword);
} else {
return DriverManager.getConnection(databaseAddress + "?autoReconnect=true" + additionalConnectionOptions
+ "&user=" + databaseUser + "&password=" + databasePassword);
}
}
protected abstract Connection createSQLConnection() throws SQLException;

/**
* Gets the list of all the achievements of a player, sorted by chronological or reverse ordering.
Expand Down Expand Up @@ -490,24 +419,13 @@ public void registerAchievement(final UUID player, final String achName, final S

@Override
protected void performWrite() throws SQLException {
String query;
if (databaseType == DatabaseType.POSTGRESQL) {
// PostgreSQL has no REPLACE operator. We have to use the INSERT ... ON CONFLICT construct, which is
// available for PostgreSQL 9.5+.
query = "INSERT INTO " + tablePrefix + "achievements VALUES ('" + player.toString()
+ "',?,?,?) ON CONFLICT (playername,achievement) DO UPDATE SET (description,date)=(?,?)";
} else {
query = "REPLACE INTO " + tablePrefix + "achievements VALUES ('" + player.toString() + "',?,?,?)";
}
String query = "REPLACE INTO " + tablePrefix + "achievements VALUES ('" + player.toString()
+ "',?,?,?)";
Connection conn = getSQLConnection();
try (PreparedStatement prep = conn.prepareStatement(query)) {
prep.setString(1, achName);
prep.setString(2, achMessage);
prep.setDate(3, new java.sql.Date(new java.util.Date().getTime()));
if (databaseType == DatabaseType.POSTGRESQL) {
prep.setString(4, achMessage);
prep.setDate(5, new java.sql.Date(new java.util.Date().getTime()));
}
prep.execute();
}
}
Expand Down Expand Up @@ -661,24 +579,10 @@ public int updateAndGetConnection(final UUID player, final String date) {
@Override
protected void performWrite() throws SQLException {
Connection conn = getSQLConnection();
String query;
if (databaseType == DatabaseType.POSTGRESQL) {
// PostgreSQL has no REPLACE operator. We have to use the INSERT ... ON CONFLICT construct,
// which is available for PostgreSQL 9.5+.
query = "INSERT INTO " + tablePrefix + dbName + " VALUES ('" + player.toString() + "', "
+ newConnections + ", ?)" + " ON CONFLICT (playername) DO UPDATE SET (" + dbName
+ ",date)=('" + newConnections + "', ?)";
} else {
query = "REPLACE INTO " + tablePrefix + dbName + " VALUES ('" + player.toString() + "', "
+ newConnections + ", ?)";
}
String query = "REPLACE INTO " + tablePrefix + dbName + " VALUES ('" + player.toString() + "', "
+ newConnections + ", ?)";
try (PreparedStatement prep = conn.prepareStatement(query)) {
if (databaseType == DatabaseType.POSTGRESQL) {
prep.setString(1, date);
prep.setString(2, date);
} else {
prep.setString(1, date);
}
prep.setString(1, date);
prep.execute();
}
}
Expand Down Expand Up @@ -739,10 +643,6 @@ protected void performWrite() throws SQLException {
}.executeOperation(pool, plugin.getLogger(), "SQL error while deleting connections.");
}

public DatabaseType getDatabaseType() {
return databaseType;
}

protected String getTablePrefix() {
return tablePrefix;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ private void addRequestsForMultipleCategory(List<String> batchedRequests, Multip
if (!entry.getValue().isDatabaseConsistent()) {
// Set flag before writing to database so that concurrent updates are not wrongly marked as consistent.
entry.getValue().prepareDatabaseWrite();
if (plugin.getDatabaseManager().getDatabaseType() == DatabaseType.POSTGRESQL) {
if (plugin.getDatabaseManager() instanceof PostgreSQLDatabaseManager) {
batchedRequests.add("INSERT INTO " + plugin.getDatabaseManager().getTablePrefix()
+ category.toDBName() + " VALUES ('" + entry.getKey().substring(0, 36) + "', '"
+ entry.getKey().substring(36) + "', " + entry.getValue().getValue()
Expand Down Expand Up @@ -116,7 +116,7 @@ private void addRequestsForNormalCategory(List<String> batchedRequests, NormalAc
if (!entry.getValue().isDatabaseConsistent()) {
// Set flag before writing to database so that concurrent updates are not wrongly marked as consistent.
entry.getValue().prepareDatabaseWrite();
if (plugin.getDatabaseManager().getDatabaseType() == DatabaseType.POSTGRESQL) {
if (plugin.getDatabaseManager() instanceof PostgreSQLDatabaseManager) {
batchedRequests.add("INSERT INTO " + plugin.getDatabaseManager().getTablePrefix()
+ category.toDBName() + " VALUES ('" + entry.getKey() + "', " + entry.getValue().getValue()
+ ") ON CONFLICT (playername) DO UPDATE SET (" + category.toDBName() + ")=("
Expand Down
7 changes: 0 additions & 7 deletions src/main/java/com/hm/achievement/db/DatabaseType.java

This file was deleted.

12 changes: 6 additions & 6 deletions src/main/java/com/hm/achievement/db/DatabaseUpdater.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
public class DatabaseUpdater {

private final AdvancedAchievements plugin;
private final SQLDatabaseManager sqlDatabaseManager;
private final AbstractSQLDatabaseManager sqlDatabaseManager;

protected DatabaseUpdater(AdvancedAchievements plugin, SQLDatabaseManager sqlDatabaseManager) {
protected DatabaseUpdater(AdvancedAchievements plugin, AbstractSQLDatabaseManager sqlDatabaseManager) {
this.plugin = plugin;
this.sqlDatabaseManager = sqlDatabaseManager;
}
Expand All @@ -46,9 +46,9 @@ protected void renameExistingTables(String databaseAddress) {
Connection conn = sqlDatabaseManager.getSQLConnection();
try (Statement st = conn.createStatement()) {
ResultSet rs;
if (sqlDatabaseManager.getDatabaseType() == DatabaseType.SQLITE) {
if (plugin.getDatabaseManager() instanceof SQLiteDatabaseManager) {
rs = st.executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='achievements'");
} else if (sqlDatabaseManager.getDatabaseType() == DatabaseType.MYSQL) {
} else if (plugin.getDatabaseManager() instanceof MySQLDatabaseManager) {
rs = st.executeQuery("SELECT table_name FROM information_schema.tables WHERE table_schema='"
+ databaseAddress.substring(databaseAddress.lastIndexOf('/') + 1)
+ "' AND table_name ='achievements'");
Expand Down Expand Up @@ -284,7 +284,7 @@ protected void updateOldDBToDates() {
protected void updateOldDBMobnameSize() {
Connection conn = sqlDatabaseManager.getSQLConnection();
// SQLite ignores size for varchar datatype.
if (sqlDatabaseManager.getDatabaseType() != DatabaseType.SQLITE) {
if (!(plugin.getDatabaseManager() instanceof SQLiteDatabaseManager)) {
int size = 51;
try (Statement st = conn.createStatement()) {
ResultSet rs = st
Expand All @@ -294,7 +294,7 @@ protected void updateOldDBMobnameSize() {
if (size == 32) {
plugin.getLogger().warning("Updating database table with extended mobname column, please wait...");
// Increase size of table.
if (sqlDatabaseManager.getDatabaseType() == DatabaseType.POSTGRESQL) {
if (plugin.getDatabaseManager() instanceof PostgreSQLDatabaseManager) {
st.execute("ALTER TABLE " + sqlDatabaseManager.getTablePrefix()
+ "kills ALTER COLUMN mobname TYPE varchar(51)");
} else {
Expand Down
Loading

0 comments on commit ba8de0a

Please sign in to comment.