Search code examples
javamysqlbukkit

MySQLNonTransientConnectionException when getting player stats


I'm storing player stats for my minigame in a SQL database and I'm getting the following error while loading stats:

com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.
[23:37:09] [Server thread/WARN]:    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
[23:37:09] [Server thread/WARN]:    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
[23:37:09] [Server thread/WARN]:    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
[23:37:09] [Server thread/WARN]:    at java.lang.reflect.Constructor.newInstance(Unknown Source)
[23:37:09] [Server thread/WARN]:    at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
[23:37:09] [Server thread/WARN]:    at com.mysql.jdbc.Util.getInstance(Util.java:408)
[23:37:09] [Server thread/WARN]:    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:918)
[23:37:09] [Server thread/WARN]:    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:897)
[23:37:09] [Server thread/WARN]:    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:886)
[23:37:09] [Server thread/WARN]:    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:860)
[23:37:09] [Server thread/WARN]:    at com.mysql.jdbc.ConnectionImpl.throwConnectionClosedException(ConnectionImpl.java:1187)
[23:37:09] [Server thread/WARN]:    at com.mysql.jdbc.ConnectionImpl.checkClosed(ConnectionImpl.java:1182)
[23:37:09] [Server thread/WARN]:    at com.mysql.jdbc.ConnectionImpl.prepareStatement(ConnectionImpl.java:4035)
[23:37:09] [Server thread/WARN]:    at com.mysql.jdbc.ConnectionImpl.prepareStatement(ConnectionImpl.java:4004)

Code:

public void loadPlayer(Player p) {
    if (!isPlayerInDataBase(p)) {
        Bukkit.getScheduler().runTaskAsynchronously(Main.getInstance(), new Runnable() {
            @Override
            public void run() {
                try (Connection connection = sqlConnection.c) {
                    PreparedStatement insert = connection.prepareStatement(
                            "INSERT INTO `MurderData` (uuid, wins, deaths, loses, kills, score) VALUES (?, ?, ?, ?, ?, ?)");
                    insert.setString(1, p.getUniqueId().toString());
                    insert.setInt(2, 0);
                    insert.setInt(3, 0);
                    insert.setInt(4, 0);
                    insert.setInt(5, 0);
                    insert.setInt(6, 0);
                    insert.execute();
                    ClosePreparedStatement(insert);
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    if (isPlayerInDataBase(p)) {
        Bukkit.getScheduler().runTaskAsynchronously(Main.getInstance(), new Runnable() {
            @Override
            public void run() {
                try (Connection connection = sqlConnection.c;
                     PreparedStatement select = connection.prepareStatement(
                             "SELECT * FROM `MurderData` WHERE uuid='" + p.getUniqueId().toString() + "'")) {
                    ResultSet result = select.executeQuery();
                    if (result.next()) {
                        if (getPlayerData(p) != null) {
                            getPlayerData(p).adddeaths(result.getInt("deaths"));
                            getPlayerData(p).addkill(result.getInt("kills"));
                            getPlayerData(p).addwins(result.getInt("wins"));
                            getPlayerData(p).addlose(result.getInt("loses"));
                            getPlayerData(p).addscore(result.getInt("score"));
                        }
                        CloseResultSet(result);
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

The error at this code:

PreparedStatement select = connection.prepareStatement(
                        "SELECT * FROM `MurderData` WHERE uuid='" + p.getUniqueId().toString() + "'")) {

Full code:

public boolean isPlayerInDataBase(Player p) {
    try (Connection connection = sqlConnection.c;
         PreparedStatement select = connection.prepareStatement(
                 "SELECT * FROM `MurderData` WHERE uuid='" + p.getUniqueId().toString() + "'")) {
        ResultSet result = select.executeQuery();
        if (result.next()) {
            result.close();
            return true;
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return false;
}

Side question: should I close the result set and prepared statements after completing the process? And should I do that instantly or with a delay?


Solution

  • It seems that you have taken the try-with-resources statement from some other code without understanding what it does. From that link:

    The try-with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try-with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable, can be used as a resource.

    In other words, when you use the syntax

    try (Connection connection = sqlConnection.c) {
         // ...
    }
    

    You are implicitly calling close() on sqlConnection.c, which is not assigned to again. Therefore, the next time you try to query your database from this connection, it is still closed, so you get this error.

    A simple fix would be to move the declaration of local variable connection out of the try-with-resources statement so it does not get closed at the end of the method. You should also look at where you use the try-with-resources syntax elsewhere in your program and make sure you're not closing anything you don't want to close.

    This is the fixed version of your code (from the pastebin you linked in a comment):

    public boolean isPlayerInDataBase(Player p) {
        Connection connection = sql.getConnection();
        try (PreparedStatement select = connection.prepareStatement(
                "SELECT * FROM `MurderData` WHERE uuid='" + p.getUniqueId().toString() + "'")) {
    
            ResultSet result = select.executeQuery();
            if (result.next()) {
                CloseResultSet(result);
                return true;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return false;
    }
    

    Additionally, in your loadPlayer() method, you can replace the two if statements with this:

    if (isPlayerInDataBase(p)) {
        // code if player is in database
    } else {
        // code if player is not in database
    }