Правильна ли процедура MySQL в этом плагине Minecraft?
Чтобы получить представление о том, как выглядит базовая структура, я скачал денежную систему, включающую MySQL, со Spigot и посмотрел на код.
public static boolean playerExists(String uuid) {
try {
ResultSet rs = Simplecoinsystem.mysql.query("SELECT * FROM CoinData WHERE UUID= '" + uuid + "'");
if (rs.next())
return (rs.getString("UUID") != null);
return false;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
public static void createPlayer(String uuid) {
if (!playerExists(uuid))
Simplecoinsystem.mysql.update("INSERT INTO CoinData (UUID, COINS) VALUES ('" + uuid +
"', '" + Simplecoinsystem.getInstance().getConfig().getInt("startcoins") + "');");
}
public static Integer getCoins(String uuid) {
Integer i = Integer.valueOf(0);
if (playerExists(uuid)) {
try {
ResultSet rs = Simplecoinsystem.mysql.query("SELECT * FROM CoinData WHERE UUID= '" + uuid + "'");
if (rs.next())
Integer.valueOf(rs.getInt("COINS"));
i = Integer.valueOf(rs.getInt("COINS"));
} catch (SQLException e) {
e.printStackTrace();
}
} else {
createPlayer(uuid);
}
return i;
}
public static void setCoins(String uuid, Integer coins) {
if (playerExists(uuid)) {
Simplecoinsystem.mysql.update("UPDATE CoinData SET COINS= '" + coins + "' WHERE UUID= '" + uuid + "';");
} else {
createPlayer(uuid);
}
}
Правильно ли я понимаю, что на самом деле нецелесообразно создавать новую запись с uuid несуществующего игрока после каждого запроса монет, если игрок не существует?
Не позволит ли это завалить базу тысячами ненужных записей, введя, например, команду «/money (player)» от злого игрока/админа?
А нельзя было просто при входе на сервер спросить, сохранен ли уже uuid и если нет, то просто ввести? Таким образом, будут записи только от игроков, которые уже были на сервере ранее. Нужна ли для этого высокая производительность сервера, я не уверен.
Это мой первый собственный класс MySQL.
public class MySQL {
private String host, database, user, password;
private int port;
private Connection con;
public MySQL(String host, int port, String database, String user, String password) {
this.host = host;
this.port = port;
this.database = database;
this.user = user;
this.password = password;
connect();
}
public void connect() {
try {
con = DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/" + database + "?autoReconnect=true", user, password);
System.out.println("&cDie MySQL Verbindung wurde erfolgreich aufgebaut!");
} catch (SQLException e) {
e.printStackTrace();
}
}
public void disconnect() {
try {
if(this.con != null) {
this.con.close();
System.out.println("§cDie MySQL Verbindung wurde erfolgreich beendet!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void update(String query) {
try {
Statement st = con.createStatement();
st.executeUpdate(query);
st.close();
} catch (Exception e) {
e.printStackTrace();
connect();
}
}
public ResultSet qry(String query) {
ResultSet rs = null;
try {
Statement st = con.createStatement();
rs = st.executeQuery(query);
} catch (Exception e) {
e.printStackTrace();
connect();
}
return rs;
}
public Connection getConnection() {
return this.con;
}
}
За исключением этой части, оба класса MySQL построены относительно одинаково. Это часть класса MySQL плагина Spigot.
2 ответа
Ваш код имеет несколько проблем.
- Когда соединение будет закрыто, в следующий раз у вас будет ошибка. В твоей
Mysql
класс, я предлагаю вам сделать:
public Connection getConnection() {
if(con == null || con.isClosed())
connect();
return con;
}
Затем используйте его во всех методах, например
getConnection().prepareStatement()
.
- Вас могут атаковать с помощью SQL Injection. Чтобы исправить это, попробуйте сделать что-то вроде:
PreparedStatement st = con.prepareStatement("SELECT * FROM CoinData WHERE UUID = ?");
st.setString(1, uuid.toString()); // Yes it start at 1 !!
st.executeUpdate();
При этом даже при всех значениях вас не могут атаковать инъекциями.
- У вас будет ошибка при получении монет:
if (rs.next()) // go to good line
Integer.valueOf(rs.getInt("COINS")); // useless convertion
i = Integer.valueOf(rs.getInt("COINS")); // error if no line.
Вы можете просто сделать:
if(rs.next())
i = rs.getInt("COINS");
- Если столбец «UUID» уникален, у вас не будет повторяющихся строк.
- Наконец, о производительности, лучше сделать это один раз: при входе в систему, а не все время. Вы также можете создать объект, хранящийся в хэш-карте, чтобы упростить доступ к нему без использования SQL, например:
public static HashMap<UUID, Integer> coinsByPlayer = new HashMap<>();
ИЛИ ЖЕ:
public static HashMap<UUID, MyObject> coinsByPlayer = new HashMap<>();
public class MyObject {
private int coins = 0;
public MyObject(UUID uuid) {
// make SQL request to get data
}
public int getCoins() {
return coins;
}
public void setCoins(int next){
coins = next;
// here make "UPDATE" sql query
}
}
Что ты говоришь? С функцией try/catch все в порядке? @Elikill58
public Connection getConnection() {
try {
if(con == null || con.isClosed()) {
connect();
}
} catch (SQLException e) {
e.printStackTrace();
}
return con;
}
редактировать:
public Connection getConnection_one() throws SQLException {
if(con == null || con.isClosed()) {
connect();
return con;
} else {
return con;
}
}