Résolu [Java] Connexion MySQL

Statut
N'est pas ouverte pour d'autres réponses.

Kenda

Architecte en herbe
16 Juillet 2016
300
1
2
125
32
www.youtube.com
Bonjour/Bonsoir,

Bon, je reviens après un petit temps d'absence (et surtout d'une flemme de dev ces derniers temps xD), pour un nouveau problème.
Depuis quelques temps, je me suis remis au dev de plugin, mais en beaucoup plus complexe (liaison à des BDD) et faire des systèmes bien plus propres pour mes projets.
Or, j'ai regardé des tutos pour les connexions MySQL, sauf que c'est la que sa coince ;-; , trop d'explication de certains, trop fouilli pour d'autres, et certains utilise des "librairies" annexe pour link pour rapidement. Donc bon, je n'ai pas vraiment trouver mon bonheur....

Je me tourne donc vers vous, afin de m'aiguiez au mieux possible afin de faire une connexion propre et compréhensible sans passé par mille variables différentes, avec des connection pool sans explication etc...

Merci beaucoup de l'aide, ou de potentiel tutoriel à se sujet :3

PS : J'ai regardé ItsAlexousd sur le système de ban MySQL (1 système) et le système de report de sa série de modération (2eme système), sauf que les deux sont différents et ducoup j'ai pas trop réussi à lié les 2 méthodes pour infos)
 

Detobel36

Créateur de plugins (PhoenixRebirth)
Support
17 Août 2012
10 539
24
2 247
347
28
Bruxelles - Belgique
www.phoenix-rebirth.fr
Salut,

Tutoriel que je te recommande: https://openclassrooms.com/fr/cours...vec-java-ee/624392-communiquez-avec-votre-bdd

Exemple de code:
Java:
public class Database {
    
    private Connection connection;
    private Statement statement;

    public Database() {
        this.connectToDatabase();
    }

    private void connectToDatabase() {
        // Paramètre que tu mets normalement en config
        final String hostname = "127.0.0.1";
        final String database = "nomBaseDeDonnee";
        final String user = "user";
        final String password = "password";
        final Integer port = 3306;

        final String url = "jdbc:mysql://" + hostname + ":" + port + "/" + database;

        try {
            Class.forName("com.mysql.jdbc.Driver");
            this.connexion = DriverManager.getConnection(url, user, password);
            this.statement = this.connexion.createStatement();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public HashMap<String, String> getDataInDatabase() {
        final HashMap<String, String> listResultats = new HashMap<String, String>();
        final ResultSet resultat = this.statement.executeQuery("SELECT pseudo, raison FROM ban");

        while (resultat.next()) {
            final String pseudo = resultat.getString("pseudo");
            final String raison = resultat.getString("email");

            listResultats.put(pseudo, raison);
        }

        return listResultats;
    }

    public void addBan(final String pseudo, final String raison) {
        final PreparedStatement preparedStatement = this.connexion.prepareStatement(
            "INSERT INTO ban (pseudo, raison) VALUES (?, ?);");
        preparedStatement.setString(1, pseudo);
        preparedStatement.setString(2, raison);
        preparedStatement.executeUpdate();
    }

}
Ce code n'a pas été testé et a été écrit sans IDE. Il est donc possible qu'il y ai des "typo" ou qu'il ne fonctionne pas. Mais tu as déjà la une belle idée de "style" de code à faire :)


Cordialement,
Detobel36
 

Kenda

Architecte en herbe
16 Juillet 2016
300
1
2
125
32
www.youtube.com
Salut,

Tutoriel que je te recommande: https://openclassrooms.com/fr/cours...vec-java-ee/624392-communiquez-avec-votre-bdd

Exemple de code:
Java:
public class Database {
   
    private Connection connection;
    private Statement statement;

    public Database() {
        this.connectToDatabase();
    }

    private void connectToDatabase() {
        // Paramètre que tu mets normalement en config
        final String hostname = "127.0.0.1";
        final String database = "nomBaseDeDonnee";
        final String user = "user";
        final String password = "password";
        final Integer port = 3306;

        final String url = "jdbc:mysql://" + hostname + ":" + port + "/" + database;

        try {
            Class.forName("com.mysql.jdbc.Driver");
            this.connexion = DriverManager.getConnection(url, user, password);
            this.statement = this.connexion.createStatement();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public HashMap<String, String> getDataInDatabase() {
        final HashMap<String, String> listResultats = new HashMap<String, String>();
        final ResultSet resultat = this.statement.executeQuery("SELECT pseudo, raison FROM ban");

        while (resultat.next()) {
            final String pseudo = resultat.getString("pseudo");
            final String raison = resultat.getString("email");

            listResultats.put(pseudo, raison);
        }

        return listResultats;
    }

    public void addBan(final String pseudo, final String raison) {
        final PreparedStatement preparedStatement = this.connexion.prepareStatement(
            "INSERT INTO ban (pseudo, raison) VALUES (?, ?);");
        preparedStatement.setString(1, pseudo);
        preparedStatement.setString(2, raison);
        preparedStatement.executeUpdate();
    }

}
Ce code n'a pas été testé et a été écrit sans IDE. Il est donc possible qu'il y ai des "typo" ou qu'il ne fonctionne pas. Mais tu as déjà la une belle idée de "style" de code à faire :)


Cordialement,
Detobel36


Merci, je testerai dans les jours à venir quand j'aurais fini mon jeu :3
je reviendrais surement pour dire mon avis :3
 

Kenda

Architecte en herbe
16 Juillet 2016
300
1
2
125
32
www.youtube.com
Re-Bonsoir,


Après voir regardé des tutoriels (car bon, on va dire que j'ai pas tout tout compris, mais j'ai trouvé quelques choses qui marche), j'ai crée ma class avec mon système de connexion, et ducoup je cherche à crée mes tables avec différents paramètres.

Problème : Ma première table se crée, mais pas la deuxième avecune erreur.


Java:
  //Table de création pour un lobby  (Marche)
public void createTable() {
        Main main = Main.getInstance();
        update("CREATE TABLE IF NOT EXISTS " + main.Lobbys +"("
                + "ID INT AUTO_INCREMENT NOT NULL PRIMARY KEY,"
                + "Pseudonyme VARCHAR(255) NOT NULL,"
                + "uuid VARCHAR(255) NOT NULL,"
                + "IP VARCHAR(255) NOT NULL,"
                + "Coins INT NOT NULL,"
                + "Credits INT NOT NULL);");
       
      
//Table de création pour un jeu (ne marche pas)
        update("CREATE TABLE IF NOT EXISTS " + main.OneShot +"("
                + "ID INT AUTO_INCREMENT NOT NULL PRIMARY KEY,"
                + "Pseudonyme VARCHAR(255) NOT NULL,"
                + "uuid DATETIME NOT NULL,"
                + "Kills INT NOT NULL,"
                + "Morts INT NOT NULL,"
                + "Wins INT NOT NULL,"
                + "Lose INT NOT NULL);");
    }

Voici l'erreur :


YAML:
[20:54:00] [Server thread/WARN]: com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.
[20:54:00] [Server thread/WARN]:     at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
[20:54:00] [Server thread/WARN]:     at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
[20:54:00] [Server thread/WARN]:     at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
[20:54:00] [Server thread/WARN]:     at java.lang.reflect.Constructor.newInstance(Unknown Source)
[20:54:00] [Server thread/WARN]:     at com.mysql.jdbc.Util.handleNewInstance(Util.java:404)
[20:54:00] [Server thread/WARN]:     at com.mysql.jdbc.Util.getInstance(Util.java:387)
[20:54:00] [Server thread/WARN]:     at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:919)
[20:54:00] [Server thread/WARN]:     at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:898)
[20:54:00] [Server thread/WARN]:     at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:887)
[20:54:00] [Server thread/WARN]:     at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:862)
[20:54:00] [Server thread/WARN]:     at com.mysql.jdbc.ConnectionImpl.throwConnectionClosedException(ConnectionImpl.java:1237)
[20:54:00] [Server thread/WARN]:     at com.mysql.jdbc.ConnectionImpl.checkClosed(ConnectionImpl.java:1232)
[20:54:00] [Server thread/WARN]:     at com.mysql.jdbc.ConnectionImpl.prepareStatement(ConnectionImpl.java:4131)
[20:54:00] [Server thread/WARN]:     at com.mysql.jdbc.ConnectionImpl.prepareStatement(ConnectionImpl.java:4100)
[20:54:00] [Server thread/WARN]:     at fr.kenda.mysql.mysql.MySQL_Connect.update(MySQL_Connect.java:80)
[20:54:00] [Server thread/WARN]:     at fr.kenda.mysql.mysql.MySQL_Connect.createTable(MySQL_Connect.java:69)
[20:54:00] [Server thread/WARN]:     at fr.kenda.mysql.Main.onEnable(Main.java:18)
[20:54:00] [Server thread/WARN]:     at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:292)
[20:54:00] [Server thread/WARN]:     at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:340)
[20:54:00] [Server thread/WARN]:     at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:405)
[20:54:00] [Server thread/WARN]:     at org.bukkit.craftbukkit.v1_9_R1.CraftServer.loadPlugin(CraftServer.java:361)
[20:54:00] [Server thread/WARN]:     at org.bukkit.craftbukkit.v1_9_R1.CraftServer.enablePlugins(CraftServer.java:321)
[20:54:00] [Server thread/WARN]:     at net.minecraft.server.v1_9_R1.MinecraftServer.t(MinecraftServer.java:411)
[20:54:00] [Server thread/WARN]:     at net.minecraft.server.v1_9_R1.MinecraftServer.l(MinecraftServer.java:376)
[20:54:00] [Server thread/WARN]:     at net.minecraft.server.v1_9_R1.MinecraftServer.a(MinecraftServer.java:331)
[20:54:00] [Server thread/WARN]:     at net.minecraft.server.v1_9_R1.DedicatedServer.init(DedicatedServer.java:269)
[20:54:00] [Server thread/WARN]:     at net.minecraft.server.v1_9_R1.MinecraftServer.run(MinecraftServer.java:527)
[20:54:00] [Server thread/WARN]:     at java.lang.Thread.run(Unknown Source)

Je n'arrive donc pas à la comprendre, même avec l'aide de Google (ou alors je suis juste bête :3)

Merci de l'aide.

Edit : De se que je comprend, la table 1 se crée, et une connexion se ferme, ducoup la table 2 ne se crée pas.
 

ShE3py

Enbogueuse
Support
26 Septembre 2015
4 413
191
488
247
21
Mìlhüsa
Bonsoir,

Sans le code source de ta fonction update() on risque pas de pouvoir faire grand chose.

Par contre pourquoi définir uuid une fois sur VARCHAR(255), et une fois sur DATETIME ? Si déjà ce serait plutôt un VARBINARY(16), mais tu dois un peu rester cohérent entre tes tables.
De plus je ne comprends pas pourquoi tu veux stocker le pseudo du joueur, tu ne dois mais au grand jamais identifier un joueur par son pseudonyme, parce que si jamais il change de nom bah pour ton plugin c'est un nouveau joueur. L'UUID ne change jamais et est unique, donc c'est ça que tu dois utiliser pour l'identifier. D'ailleurs c'est aussi ton uuid que tu dois mettre en PRIMARY KEY et non ton compteur ID, parce qu'en soit ton uuid est déjà un id et donc à part prendre de la place en plus c'est un peu redondant.

Cordialement,
ShE3py
 

Kenda

Architecte en herbe
16 Juillet 2016
300
1
2
125
32
www.youtube.com
Bonsoir,

Sans le code source de ta fonction update() on risque pas de pouvoir faire grand chose.

Par contre pourquoi définir uuid une fois sur VARCHAR(255), et une fois sur DATETIME ? Si déjà ce serait plutôt un VARBINARY(16), mais tu dois un peu rester cohérent entre tes tables.
De plus je ne comprends pas pourquoi tu veux stocker le pseudo du joueur, tu ne dois mais au grand jamais identifier un joueur par son pseudonyme, parce que si jamais il change de nom bah pour ton plugin c'est un nouveau joueur. L'UUID ne change jamais et est unique, donc c'est ça que tu dois utiliser pour l'identifier. D'ailleurs c'est aussi ton uuid que tu dois mettre en PRIMARY KEY et non ton compteur ID, parce qu'en soit ton uuid est déjà un id et donc à part prendre de la place en plus c'est un peu redondant.

Cordialement,
ShE3py

Bonsoir,

Alors voilà la class entière (que j'ai récupéré sur cette vidéo : Création des tables automatiques, et celle-ci Création de la BDD) (Je les trouve simple et facile à comprendre).


Java:
package fr.kenda.mysql.mysql;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.function.Consumer;
import java.util.function.Function;

import org.bukkit.Bukkit;
import org.bukkit.ChatColor;

import fr.kenda.mysql.Main;

public class MySQL_Connect {

   
    private Connection connection;

    public void connect(String host, int port, String database, String user, String password) {
        if (!isConnected()) {
            Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "[------LOADING DATABASE-----]");
            try {
                connection = DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/" + database, user,
                        password);              
                Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "Liaison avec la base de données effectué.");
            } catch (SQLException e) {
                e.printStackTrace();              
            }
            Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[------LOADING DATABASE-----]");
        }
    }

    public void disconnect() {
        if (isConnected()) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public boolean isConnected() {
        try {
            if ((connection == null) || (connection.isClosed()) || (connection.isValid(5))) {
                return false;
            }
            return true;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return false;

    }

    public void createTable() {
        Main main = Main.getInstance();
        update("CREATE TABLE IF NOT EXISTS " + main.Lobbys +"("
                + "ID INT AUTO_INCREMENT NOT NULL PRIMARY KEY,"
                + "Pseudo VARCHAR(255) NULL,"
                + "IP VARCHAR(255) NULL,"
                + "Coins INT NULL,"
                + "Credits INT NULL);");
       
       
        update("CREATE TABLE IF NOT EXISTS " + main.OneShot +"("
                + "ID INT AUTO_INCREMENT NOT NULL PRIMARY KEY,"
                + "Pseudo VARCHAR(255) NOT NULL,"
                + "Kills INT NOT NULL,"
                + "Morts INT NULL,"
                + "Wins INT NULL,"
                + "Lose INT NULL);");
    }

    public void update(String qry) {
        try (Connection c = getConnection(); PreparedStatement s = c.prepareStatement(qry)) {
            s.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Object query(String qry, Function<ResultSet, Object> consumer) {
        try (Connection c = getConnection();
                PreparedStatement s = c.prepareStatement(qry);
                ResultSet rs = s.executeQuery()) {
            return consumer.apply(rs);
        } catch (SQLException e) {
            throw new IllegalStateException(e.getMessage());
        }
    }

    public void query(String qry, Consumer<ResultSet> consumer) {
        try (Connection c = getConnection();
                PreparedStatement s = c.prepareStatement(qry);
                ResultSet rs = s.executeQuery()) {
            consumer.accept(rs);

        } catch (SQLException e) {
            throw new IllegalStateException(e.getMessage());
        }

    }

    public Connection getConnection() {
        return connection;
    }

}

Ensuite, j'utilise les UUID pour le moment en phase de test, mais je me rend compte, que vu que le serveur sera en cracké, récupéré les UUID ne servira à rien étant donner que les UUID crack et premium change totalement. (Enfin je crois).
Ducoup pour la primary key, celle-ci reviens bien à l'ID de la personne je suppose (car au final, je verrai si je l'utilise ultérieurement car pour le moment, elle me sert juste à afficher le nombre d'inscription total).
 

ShE3py

Enbogueuse
Support
26 Septembre 2015
4 413
191
488
247
21
Mìlhüsa
Je ne sais pas trop pourquoi mais dans son code il utilise des try-with-resources, ce qui fait qu'en cas d'erreur sur une requête, il ferme la connexion avec la base de données.
Donc pour moi tu as dû avoir une erreur avant, ce qui a interrompu la connexion et ce qui crée le message d'erreur que tu as dit ci-dessus.

les UUID crack et premium change totalement. (Enfin je crois)
Oui, lorsque online-mode est sur true ce sont les UUIDs des comptes Mojang qui sont utilisés, lorsqu'il est sur false l'UUID est généré à partir du nom du joueur.

Ducoup pour la primary key, celle-ci reviens bien à l'ID de la personne je suppose (car au final, je verrai si je l'utilise ultérieurement car pour le moment, elle me sert juste à afficher le nombre d'inscription total).
Dans ce cas-là la clef primaire serait le nom du joueur (vu qu'il est a priori unique), mais bon si après tu veux renommer manuellement des joueurs faudrait effectivement créer un nouvel id.
 

Kenda

Architecte en herbe
16 Juillet 2016
300
1
2
125
32
www.youtube.com
Je ne sais pas trop pourquoi mais dans son code il utilise des try-with-resources, ce qui fait qu'en cas d'erreur sur une requête, il ferme la connexion avec la base de données.
Donc pour moi tu as dû avoir une erreur avant, ce qui a interrompu la connexion et ce qui crée le message d'erreur que tu as dit ci-dessus.

Donc je pense enlever l'update de la deuxième table, et la mettre dans le plugin qui fera effet (Au moins, si j'ai un soucis, je sais que cela ne viendra pas de la table).

Dans ce cas-là la clef primaire serait le nom du joueur (vu qu'il est a priori unique), mais bon si après tu veux renommer manuellement des joueurs faudrait effectivement créer un nouvel id.

Ducoup, pour le moment, je laisse la clé primaire sur l'ID, car je pense, dans le futur, crée une commande admin pour rename le joueur dans la BDD et donc si je la met en clé, c'est vrai que sa buguera.

Merci en tout cas de m'avoir aiguillé sur le problème, mais je comprend toujours pas d'où viens l'erreur, car la première UPDATE se fais, mais pas la deuxième, alors que théoriquement aucun problème n'est présent :confused:

Enfin bon, pour le moment, je vais faire comment j'ai dis, et supprimé la deuxième table pour évité ce problème.
 

ShE3py

Enbogueuse
Support
26 Septembre 2015
4 413
191
488
247
21
Mìlhüsa
Merci en tout cas de m'avoir aiguillé sur le problème, mais je comprend toujours pas d'où viens l'erreur, car la première UPDATE se fais, mais pas la deuxième, alors que théoriquement aucun problème n'est présent

Attend c'est moi qui suis stupide.
Java:
// faire
public void update(String qry) {
    try(Connection c = getConnection(); PreparedStatement s = c.prepareStatement(qry)) {
        s.executeUpdate();
    }
    catch(Exception e) {
        e.printStackTrace();
    }
}

// est équivalent à faire
public void update(String qry) {
    Connection c = null;
    PreparedStatement s = null;
   
    try {
        c = getConnection();
        s = c.prepareStatement(qry);
       
        s.executeUpdate();
    }
    catch(Exception e) {
        e.printStackTrace();
    }
    finally {
        if(c != null) c.close();
        if(s != null) s.close();
    }
}

// sans le bloc `finally` :
public void update(String qry) {
    Connection c = null;
    PreparedStatement s = null;
   
    try {
        c = getConnection();
        s = c.prepareStatement(qry);
       
        s.executeUpdate();
       
        if(c != null) c.close();
        if(s != null) s.close();
    }
    catch(Exception e) {
        e.printStackTrace();
       
        // note: le bloc `finally` est plutôt un catch(Throwable)
        if(c != null) c.close();
        if(s != null) s.close();
    }
}

Le try-with-ressources ferme la connexion, donc oui tu ne peux appeler la fonction qu'une seule fois vu qu'après tu n'es plus connecté.
Il faudrait faire un truc dans ce style :
Java:
public void update(String query) {
    Connection connection = getConnection();
   
    try(PreparedStatement statement = connection.prepareStatement(query)) {
        connection.executeUpdate();
    }
    catch(SQLException e) {
        // TODO: handle exception
        // Il faut que ton plugin réagisse s'il ne peut pas créer la table
        e.printStackTrace();
    }
}

// l'autocomplétion te permet d'écrire une variable de 1268977 caractères en tapant les 3 premières lettres,
// donc évite de compresser tes variables en prenant les initiales ; tu gagneras en lisibilité.

// + évite de faire des catch trop larges `catch(Exception)`
 
  • J'aime
Reactions: Detobel36

Kenda

Architecte en herbe
16 Juillet 2016
300
1
2
125
32
www.youtube.com
Le try-with-ressources ferme la connexion, donc oui tu ne peux appeler la fonction qu'une seule fois vu qu'après tu n'es plus connecté.
Il faudrait faire un truc dans ce style :
Java:
public void update(String query) {
Connection connection = getConnection();

try(PreparedStatement statement = connection.prepareStatement(query)) {
connection.executeUpdate();
}
catch(SQLException e) {
// TODO: handle exception
// Il faut que ton plugin réagisse s'il ne peut pas créer la table
e.printStackTrace();
}
}

// l'autocomplétion te permet d'écrire une variable de 1268977 caractères en tapant les 3 premières lettres,
// donc évite de compresser tes variables en prenant les initiales ; tu gagneras en lisibilité.

// + évite de faire des catch trop larges `catch(Exception)`

Oui en effet. Je viens d'essayé ta requête, et celle-ci marche à merveille. Les deux tables viennent de se crée.
 
Statut
N'est pas ouverte pour d'autres réponses.