简体   繁体   中英

I don't think I am closing my connections properly - Java

Im still beginner, I've managed to link my Java project to an online MySQL DB with Heroku. It seemed preferable to expecting my tutor to configure my offline DB when he marks my work.

However, I now have a too many connections problem, and the performance of my program is very slow. I think I have closed the connections in my code properly - But I am sure someone can point out my mistakes :)

Maximum connections allowed is 10, which is getting maxed out somehow.

public class DBConnect {

Connection dbConnection;
Statement stmt;
ResultSet rs; 

protected Connection connectToDatabase() {
    try {
        dbConnection=DriverManager.getConnection("jdbc:mysql://us-cdbr-iron-east-05.cleardb.net", "be0f2e99e68dbf", "ad1ed239");
    } catch (SQLException error) {
          System.err.println("Error connecting to database: "+ error.toString());
    }
    return dbConnection;
}

public void closeConnection(){
    try {
        if (null != dbConnection && !dbConnection.isClosed()) {
            dbConnection.close();
        }
    }
    catch (SQLException sqle)
    {
        System.out.println("Error closing connection: " + sqle.toString());
    }
}
}

My DB Class

public class TeamDB extends DBConnect {

Team t;

public TeamDB(){
}

public void saveTeam(String teamName, int GoalsScored){
    final String insertStmt = "INSERT INTO heroku_2b89816185313b9.TEAM (TEAMNAME, GOALSSCORED) VALUES (?,?)";
    try (Connection con = connectToDatabase()) {
        try (PreparedStatement pstmt =  con.prepareStatement(insertStmt)) {
            pstmt.setString(1,teamName);
            pstmt.setInt(2, GoalsScored);
            pstmt.executeUpdate();
        }  
    } catch (SQLException sqle){ 
            System.out.println("Exception when inserting Team record: " + sqle.toString());
        }

}

public void updateTeam(String teamName, int GoalsScored){
    final String loadStmt = "SELECT * FROM heroku_2b89816185313b9.TEAM WHERE TEAMNAME = '" + teamName + "'";
    try (Connection con = connectToDatabase()) {
        try (PreparedStatement pstmt = con.prepareStatement(loadStmt)) {
            rs = pstmt.executeQuery(loadStmt);
            rs.next();
            deleteTeam(teamName);
            saveTeam(rs.getString("TEAMNAME"), (GoalsScored+rs.getInt("GOALSSCORED")));
            rs.close();
        } 
    } catch(SQLException error) { 
        System.err.println("Error updating team: " + error.toString());
    } 
}

public void deleteTeam(String teamName){
    final String deleteStmt = "DELETE FROM heroku_2b89816185313b9.TEAM WHERE TEAMNAME = '" + teamName + "'";
    try (Connection con = connectToDatabase()) {
        try (PreparedStatement pstmt = con.prepareStatement(deleteStmt)){
            pstmt.executeUpdate(deleteStmt);
        } catch (SQLException error) {
            System.err.println("Error deleting team from database: " + error.toString());
        }
    } catch (SQLException error) {
        System.out.println("Error connecting to database"+error.toString());
    }

}

public ArrayList<String> viewTeams() throws SQLException{
    ArrayList<String> teamNames = new ArrayList<>();
    String viewTeams = "SELECT TEAMNAME FROM heroku_2b89816185313b9.TEAM";
    try (Connection con = connectToDatabase()){
        try (PreparedStatement pstmt = con.prepareStatement(viewTeams)) {
            rs = pstmt.executeQuery();            
            while (rs.next()) {
                String nms = rs.getString("TEAMNAME");
                teamNames.add(nms);
            }
            rs.close();
        }

    } catch (SQLException error) {
        System.err.println("Error viewing teams from database: " + error.toString());
    }
    return teamNames;
}


public ArrayList<TeamScore> sortLeagueTable() throws SQLException {
    ArrayList<TeamScore> teamData = new ArrayList<>();
    String viewTeams = "SELECT * FROM heroku_2b89816185313b9.TEAM ORDER BY GOALSSCORED DESC";
    try (Connection con = connectToDatabase()){
        try (PreparedStatement pstmt = con.prepareStatement(viewTeams)) {
            rs = pstmt.executeQuery();
            while (rs.next()) {
            TeamScore ts = new TeamScore(rs.getString("TEAMNAME"),rs.getInt("GOALSSCORED"));
            teamData.add(ts);
            }
        }
    } catch (SQLException error) {
        System.err.println("Error sorting league table: " + error.toString());
    } 
    return teamData;
}
}

My TeamDB Class handles team querys

public class PlayerDB extends DBConnect {

Player p;

public PlayerDB(){
}

public void savePlayer(final String playerName,
        final int playerGoals, final int redCards,
        final int yellowCards, final int gamesAsCap,
        final int forward, final int center,
        final int back) {
        final String insertStmt = "INSERT INTO heroku_2b89816185313b9.PLAYER (playerName,"
                + " playerGoals, redCards, yellowCards, gamesAsCap, forward,"
                + " center, back) VALUES (?,?,?,?,?,?,?,?)";
        try (Connection con = connectToDatabase()) {
            try (PreparedStatement pstmt = con.prepareStatement(insertStmt)) {
                pstmt.setString(1, playerName);
                pstmt.setInt(2, playerGoals);
                pstmt.setInt(3, redCards);
                pstmt.setInt(4, yellowCards);
                pstmt.setInt(5, gamesAsCap);
                pstmt.setInt(6, forward);
                pstmt.setInt(7, center);
                pstmt.setInt(8, back);
                pstmt.executeUpdate();
            }
        } 
        catch (SQLException sqle){ 
            System.out.println("Exception when inserting Player record: " + sqle.toString());
        }
}

public Player updatePlayer(String pN, int goalsThis, Boolean isCap, String posPlayed, int redC, int yelC){
    final String loadStmt = "SELECT * FROM heroku_2b89816185313b9.PLAYER WHERE PLAYERNAME = '" + pN + "'";
    try (Connection con = connectToDatabase()) {
        try (PreparedStatement pstmt = con.prepareStatement(loadStmt)) {
            rs = pstmt.executeQuery(loadStmt);
            rs.next();
            deletePlayer(pN);
            p = new Player(pN, goalsThis, isCap, posPlayed, redC, yelC);
            p.playerName = rs.getString("PLAYERNAME");
            p.totPlayerGoals += rs.getInt("PLAYERGOALS");
            p.totYellowCards += rs.getInt("YELLOWCARDS");
            p.totRedCards += rs.getInt("REDCARDS");
            p.totGamesAsCap += rs.getInt("GAMESASCAP");
            p.positionNum[0] += rs.getInt("FORWARD");
            p.positionNum[1] += rs.getInt("CENTER");
            p.positionNum[2] += rs.getInt("BACK");
            rs.close();
        }           
    }
    catch(SQLException error)
    { 
        System.err.println("Error connecting to database: " + error.toString());
    }
    finally {
        p.savePlayer();
        return p;
    }    
}

public Player loadPlayer(String plrName){
    final String loadStmt = "SELECT * FROM heroku_2b89816185313b9.PLAYER WHERE PLAYERNAME = '" + plrName + "'";
    try (Connection con = connectToDatabase()) {
        try (PreparedStatement pstmt = con.prepareStatement(loadStmt)) {
            rs = pstmt.executeQuery(loadStmt);
            rs.next();
            p = new Player("",0,Boolean.FALSE,"",0,0);
            p.playerName = rs.getString("PLAYERNAME");
            p.totPlayerGoals = rs.getInt("PLAYERGOALS");
            p.totYellowCards = rs.getInt("YELLOWCARDS");
            p.totRedCards = rs.getInt("REDCARDS");
            p.totGamesAsCap = rs.getInt("GAMESASCAP");
            p.positionNum[0] = rs.getInt("FORWARD");
            p.positionNum[1] = rs.getInt("CENTER");
            p.positionNum[2] = rs.getInt("BACK");
            rs.close();
        }
    } catch(SQLException error) { 
        System.err.println("Error connecting to database: " + error.toString());
    } finally {
        return p;
    } 
}

public void deletePlayer(final String playerName){
    final String deleteStmt = "DELETE FROM heroku_2b89816185313b9.PLAYER WHERE PLAYERNAME = '" + playerName + "'";
    try (Connection con = connectToDatabase()) {
        try (PreparedStatement pstmt = con.prepareStatement(deleteStmt)) {
            pstmt.executeUpdate(deleteStmt);
        } catch (SQLException error) {
            System.err.println("Error deleting player from database: " + error.toString());
        }
    } catch (SQLException error) {
        System.out.println("Error connecting to database"+error.toString());
    }

}

public ArrayList viewPlayers(){
    ArrayList vp = new ArrayList();
    String viewPlayers = "SELECT * FROM heroku_2b89816185313b9.PLAYER";
    connectToDatabase();
    try (Connection con = connectToDatabase()) {
        try (PreparedStatement pstmt = con.prepareStatement(viewPlayers)) {
            rs = pstmt.executeQuery(viewPlayers); 
            while (rs.next()){
            vp.add((rs.getString("PLAYERNAME")));
            }
            rs.close();
        }
    } catch (SQLException error) {
        System.err.println("Error querying database for player names: " + error.toString());
    } finally {
        return vp;
    }       
}
}

My PlayerDB class handles Player queries.

Super grateful for any suggestions,

Happy New Year to all

Updated code so now implements Try with Resource blocks, Im still having the same issue, in fact its worse now :(.

You are opening connections, not necessarily closing them. Besides using a connection pool , you can at every operation create a new connection.

Best use try-with-resources , and prepared statements (instead of composing an SQL string in pieces - escapes single quotes and prevents SQL injection).

List<Product> list = new ArrayList<>();
try (Connection connection = openConnection()) {
    try (PreparedStatement preparedStatement = connection.prepareStatement(query)) {
        preparedStatement.setInt(1, appId);
        try (resultSet = preparedStatement.executeQuery()) {
            while (resultSet.next()) {
                Product item = getProductById(resultSet.getInt("prodId"));
                list.add(item);
            } 
            return list;
        }
    }
} catch (Exception e) {
    e.printStackTrace();
}

The try-with-resources ensures automatic closing, also of Statement and ResultSet.

Class.forName no longer is needed to find a driver.


Further inspection

Troubling

I have seen a variable rs for a ResultSet that is AutoClosable too. Use a try-with-resources for it too. I do think the code has been muddied a bit; rs should not have been a field, but purely a local variable. As otherwise two methods might be using the same rs for different purposes. See code sample below.

Tip Use exception.getMessage() or exception.getLocalizedMessage() (language dependent) instead of toString() .

Maybe an improvement

In general, not necessarily when you need all fields as here: instead of SELECT * better list the columns you need, and get them by index( rs.getInt(1) and so on).

Improvement only

Replacing

"jdbc:mysql://us-cdbr-iron-east-05.cleardb.net"

by

"jdbc:mysql://us-cdbr-iron-east-05.cleardb.net/heroku_2b89816185313b9"

would shorten queries, as heroku_2b89816185313b9. could be removed on other places.

Improvement only

public Player loadPlayer(String plrName) {
    final String loadStmt = "SELECT * FROM PLAYER WHERE PLAYERNAME = ?";
    try (Connection con = connectToDatabase()) {
        try (PreparedStatement pstmt = con.prepareStatement(loadStmt)) {
            pstmt.setString(1, plrName);
            try (ResultSet rs = pstmt.executeQuery(loadStmt)) {
                if (!rs.next()) {
                    throw new SQLException("Player does not exist: " + plrName);
                }
                Player p = new Player("",0,Boolean.FALSE,"",0,0);
                p.playerName = rs.getString("PLAYERNAME");
                p.totPlayerGoals = rs.getInt("PLAYERGOALS");
                p.totYellowCards = rs.getInt("YELLOWCARDS");
                p.totRedCards = rs.getInt("REDCARDS");
                p.totGamesAsCap = rs.getInt("GAMESASCAP");
                p.positionNum[0] = rs.getInt("FORWARD");
                p.positionNum[1] = rs.getInt("CENTER");
                p.positionNum[2] = rs.getInt("BACK");
                return p;
            }
        }
    } catch(SQLException error) { 
        System.err.println("Error connecting to database: " + error.getMessage());
        throw new IllegalStateException("Loading player", error);
    } 
}

The return can be used innermost. On a return/break/throw an implicit final will be called that does a close.

I myself do not catch the SQLException, but add throws SQLException to the method header. This allows delete...(...); save...(); delete...(...); save...(); to be done in a safe way. The catch simply moves at the call location. Here I was forced to throw an other (runtime) exception, as nothing could be returned when no player loadable. Removing the catch and adding throws SQLException would be nicer.

Further problems I did not see; merely rs was odd, and not handling a failed rs.next() returning false. On data problems, like character set allowed by mysql, SQL data types and such, one would receive SQLExceptions.

Your Connection is static , so if you happen to open multiple connections, only the last created one will be closed when you call closeConnection , the other ones will stay alive .

Consider forgetting the static , and have a look at connection pooling, since creating a new connection each time you need one has poor performance.

The following is an example where you create two connections, but the first one is never closed :

    public ArrayList viewPlayers(){

        ArrayList vp = new ArrayList();
        String viewPlayers = "SELECT * FROM heroku_2b89816185313b9.PLAYER";
        // here dbConnection becomes a new connection, say connection1
        connectToDatabase();

        // here you are creating a new connection, say connection2
        try (Connection con = connectToDatabase()) {
            try (PreparedStatement pstmt = con.prepareStatement(viewPlayers)) {
                rs = pstmt.executeQuery(viewPlayers); 
                while (rs.next()){
                vp.add((rs.getString("PLAYERNAME")));
                }
                rs.close();
            }
        } catch (SQLException error) {
            System.err.println("Error querying database for player names: " + error.toString());
        } finally {
            // try-with-resources is closing the connection2
            // but connection1 has never been closed
            return vp;
        }       
    }

If connection pooling is too problematic for you, then you should wrap your connections in a "Try with resources" structure, which will guarantee closure, when you are finished with them.

see https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

As others have said, don't make your connection static.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM