FindBugs is right about a potential leak in the event of an exception, because setInt and setString are declared as throwing "SQLException". If any of these lines throws a SQLException, then the PreparedStatement is leaked because there is no scope block that has access to close it.
To better understand this problem, let's analyze the illusion of code, get rid of the types of springs, and add a method to the method that approximately corresponds to how the scope of the call stack works when the method returns the resource.
public void leakyMethod(Connection con) throws SQLException { PreparedStatement notAssignedOnThrow = null; //Simulate calling method storing the returned value. try { //Start of what would be createPreparedStatement method PreparedStatement inMethod = con.prepareStatement("select * from foo where key = ?"); //If we made it here a resource was allocated. inMethod.setString(1, "foo"); //<--- This can throw which will skip next line. notAssignedOnThrow = inMethod; //return from createPreparedStatement method call. } finally { if (notAssignedOnThrow != null) { //No way to close because it never notAssignedOnThrow.close(); //made it out of the try block statement. } } }
Returning to the original problem, the same is true if user is null, which leads to a NullPointerException due to the absence of the specified user or due to some other user exception, for example, UserNotLoggedInException thrown from the depth of getUserId() .
Here is an example of an ugly solution to this problem:
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { boolean fail = true; PreparedStatement ps = connection.prepareStatement(insertQuery, new String[] { "id" }); try { ps.setInt(1, user.getUserId()); ps.setString(2, user.getUserName()); ps.setString(3, user.getFirstName()); ps.setInt(4, user.getLastName()); fail = false; } finally { if (fail) { try { ps.close(); } catch(SQLException warn) { } } } return ps; }
So in this example, it closes the statement only if something went wrong. Otherwise, return an open statement for the caller to clear. The finally block is used on the catch block, because a driver implementation with errors can throw more than just SQLException objects. Blocking and re-throwing are not used, since a check of the type of throw may fail in very rare cases.
In JDK 7 and JDK 8, you can write a patch as follows:
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { PreparedStatement ps = connection.prepareStatement(insertQuery, new String[] { "id" }); try { ps.setInt(1, user.getUserId()); ps.setString(2, user.getUserName()); ps.setString(3, user.getFirstName()); ps.setInt(4, user.getLastName()); } catch (Throwable t) { try { ps.close(); } catch (SQLException warn) { if (t != warn) { t.addSuppressed(warn); } } throw t; } return ps; }
In JDK 9 and later, you can write a patch as follows:
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { PreparedStatement ps = connection.prepareStatement(insertQuery, new String[] { "id" }); try { ps.setInt(1, user.getUserId()); ps.setString(2, user.getUserName()); ps.setString(3, user.getFirstName()); ps.setInt(4, user.getLastName()); } catch (Throwable t) { try (ps) {
As for Spring, let's say that your user.getUserId() method can throw an IllegalStateException, or the given user is null . In the contract, Spring does not indicate what happens if java.lang.RuntimeException or java.lang.Error is thrown from PreparedStatementCreator. According to the documentation:
Implementations should not care about SQLExceptions that can be thrown out of the operations they try. The JdbcTemplate class will intercept and handle SQLExceptions accordingly.
This verbal expression implies that Spring relies on connection.close () to do the work .
Let's do a concept check to see what Spring documentation promises.
public class LeakByStackPop { public static void main(String[] args) throws Exception { Connection con = new Connection(); try { PreparedStatement ps = createPreparedStatement(con); try { } finally { ps.close(); } } finally { con.close(); } } static PreparedStatement createPreparedStatement(Connection connection) throws Exception { PreparedStatement ps = connection.prepareStatement(); ps.setXXX(1, "");
The result is:
Connection closed the statement. Exception in thread "main" java.lang.Exception at LeakByStackPop$PreparedStatement.setXXX(LeakByStackPop.java:52) at LeakByStackPop.createPreparedStatement(LeakByStackPop.java:28) at LeakByStackPop.main(LeakByStackPop.java:15)
As you can see, the connection is the only reference to the prepared statement.
Let's update the example to fix a memory leak by fixing our fake PreparedStatementCreator method.
public class LeakByStackPop { public static void main(String[] args) throws Exception { Connection con = new Connection(); try { PreparedStatement ps = createPreparedStatement(con); try { } finally { ps.close(); } } finally { con.close(); } } static PreparedStatement createPreparedStatement(Connection connection) throws Exception { PreparedStatement ps = connection.prepareStatement(); try {
The result is:
Closed the statement. Exception in thread "main" java.lang.Exception Connection closed the statement. at LeakByStackPop$PreparedStatement.setXXX(LeakByStackPop.java:63) at LeakByStackPop.createPreparedStatement(LeakByStackPop.java:29) at LeakByStackPop.main(LeakByStackPop.java:15)
As you can see, each distribution was balanced with the closure of the resource.