17

I have recently having some discussions with my professor about how to handle the basic jdbc connection scheme. Suppose we want to execute two queries, this is what he proposes

public void doQueries() throws MyException{
    Connection con = null;
    try {
        con = DriverManager.getConnection(dataSource);
        PreparedStatement s1 = con.prepareStatement(updateSqlQuery);
        PreparedStatement s2 = con.prepareStatement(selectSqlQuery);

        // Set the parameters of the PreparedStatements and maybe do other things

        s1.executeUpdate();
        ResultSet rs = s2.executeQuery();

        rs.close();
        s2.close();
        s1.close();
    } catch (SQLException e) {
        throw new MyException(e);
    } finally {
        try {
            if (con != null) {
                con.close();
            }
        } catch (SQLException e2) {
            // Can't really do anything
        }
    }
}

I don't like this approach, and I have two questions about it:

1.A) I think that, if any exception is thrown where we do 'other things', or in the line rs.close() or s2.close() then s1 wouldn't have been closed when the method ends. Am I right about that?

1.B) The professor keeps asking me to explicitly close the ResultSet (even when the Statement documentation makes clear that it will close the ResultSet) She says that Sun recommends it. Is there any reason to do so?

Now this is what I think is the correct code for the same thing:

public void doQueries() throws MyException{
    Connection con = null;
    PreparedStatement s1 = null;
    PreparedStatement s2 = null;
    try {
        con = DriverManager.getConnection(dataSource);
        s1 = con.prepareStatement(updateSqlQuery);
        s2 = con.prepareStatement(selectSqlQuery);

        // Set the parameters of the PreparedStatements and maybe do other things

        s1.executeUpdate();
        ResultSet rs = s2.executeQuery();

    } catch (SQLException e) {
        throw new MyException(e);
    } finally {
        try {
            if (s2 != null) {
                s2.close();
            }
        } catch (SQLException e3) {
            // Can't do nothing
        }
        try {
            if (s1 != null) {
                s1.close();
            }
        } catch (SQLException e3) {
            // Can't do nothing
        }
        try {
            if (con != null) {
                con.close();
            }
        } catch (SQLException e2) {
            // Can't do nothing
        }
    }
}

2.A) Is this code correct? (Is it guaranteed that all will be closed when the method ends?)

2.B) This is very large and verbose (and it gets worse if there are more Statements) Is there any shorter or more elegant way to do this without using try-with-resources?

Finally this is the code I like the most

public void doQueries() throws MyException{
    try (Connection con = DriverManager.getConnection(dataSource);
         PreparedStatement s1 = con.prepareStatement(updateSqlQuery);
         PreparedStatement s2 = con.prepareStatement(selectSqlQuery))
    {

        // Set the parameters of the PreparedStatements and maybe do other things

        s1.executeUpdate();
        ResultSet rs = s2.executeQuery();

    } catch (SQLException e) {
        throw new MyException(e);
    }
}

3) Is this code correct? I think my professor doesn't like this way because there is no explicit close of the ResultSet, but she has told me that she is fine with it as long as in the documentation it is clear that all is closed. Can you give any link to the official documentation with a similar example, or based in the documentation show that there is are no problems with this code?

tshepang
  • 12,111
  • 21
  • 91
  • 136
José D.
  • 4,175
  • 7
  • 28
  • 47
  • here you go http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html ... and it does have an example on `Statement` and `ResultSet` – AKS Mar 26 '14 at 19:56
  • Thanks, I had already checked that but it doesn't make it explicitly clear that the ResultSet is closed. – José D. Mar 26 '14 at 20:00
  • 6
    `A ResultSet object is automatically closed when the Statement object that generated it is closed` Read [here](http://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html) – AKS Mar 26 '14 at 20:01
  • 2
    *2.A) Is this code correct?* No. ```close``` might throw a ```RuntimeException```. Before try-with-resources, I used nested try/finally blocks so each resource got its own finally. – David Ehrmann Mar 26 '14 at 20:04
  • 2
    I think your professor is right when she says to close the ResultSet yourself. Relying on other code to do it is dangerous. How do you know that in future versions they don't change it so ResultSets are not closed automatically? Also, I would catch Throwable instead of SqlException. It will cover all possible scenarios. – CodeChimp Mar 26 '14 at 20:05
  • @DavidEhrmann really? How do you know that? It is not specified in http://docs.oracle.com/javase/7/docs/api/java/sql/Connection.html#close() nor in http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#close() :s – José D. Mar 26 '14 at 20:09
  • 1
    @Trollkemada ```RuntimeException``` can *always* be thrown. You don't want a buggy driver throwing a ```NullPointerException``` to prevent resources from being freed. – David Ehrmann Mar 26 '14 at 20:12
  • (2b.) This is why I recommend avoiding raw jdbc and instead recommend spring-jdbc, jdbi, jooq or an ORM. – Adam Gent Mar 26 '14 at 20:17
  • 1
    There have been known problems with some JDBC drivers failing to properly close the ResultSet when the Connection is closed. So while in theory you are correct in your interpretation of the doc saying you needn't explicitly close your ResultSet, in practice you should. – Basil Bourque Mar 27 '14 at 05:39
  • It would be interesting to see people's solutions where they are using `commit` or `rollback` as well, since we are discussing code with an update after all. – Adam Oct 23 '17 at 11:16
  • @CodeChimp Catching SQLException is exactly correct in this case. There is NEVER a reason to catch Throwable! We should only catch errors we can handle internally. Throwable supersedes all Java Exceptions, and will catch errors like OutOfMemoryException and KernelException. For what purpose would you capture those, and how would you plan on handling them? – Craig Parton Feb 27 '19 at 10:39
  • @CraigParton 2019 CodeChimp would agree with you. 2014 CodeChimp was a little overzealous with his catching of errors. – CodeChimp Feb 28 '19 at 16:31
  • 1
    @CodeChimp Hey at least we live and learn! I'm currently rewriting some old code that I wrote a few years and.. it's a little painful. – Craig Parton Mar 04 '19 at 12:31

6 Answers6

25

tl;dr

  • In theory closing the statement closes the result set.
  • In practice, some faulty JDBC driver implementations failed to do so, notoriously. Thus the advice from your instructor that she learned from the School Of Hard Knocks.

Unless you are familiar with every implementation of every JDBC driver that might be deployed for your app, use try-with-resources to auto-close every level of your JDBC work such as statements and result sets.

Use try-with-resources syntax

None of your code is fully using try-with-resources. In try-with-resources syntax, you declare and instantiate your Connection, PreparedStatement, and ResultSet in parentheses, before the braces. See Tutorial by Oracle.

While your ResultSet is not being explicitly closed in your last code example, it should be closed indirectly when its statement is closed. But as discussed below, it might not be closed because of faulty JDBC driver.

AutoCloseable

Any such objects implementing AutoCloseable will automatically have their close method invoked. So no need for those finally clauses.

How do you know which objects are auto-closable and which are not? Look at their class documentation to see if it declares AutoCloseable as a super-interface. Conversely, see the JavaDoc page for AutoCloseable for a list of all the bundled sub-interfaces and implementing classes (dozens actually).

For example, for SQL work, we see that Connection, Statement, PreparedStatement, ResultSet, and RowSet are all auto-closable but DataSource is not. This makes sense, as DataSource stores data about potential resources (database connections) but is not itself a resource. A DataSource is never “open” so no need to close.

See Oracle Tutorial, The try-with-resources Statement.

Code example

Your last code example is getting close to good, but should have wrapped ResultSet in a try-with-resources statement to get automatically closed.

To quote ResultSet JavaDoc:

A ResultSet object is automatically closed when the Statement object that generated it is closed, re-executed, or used to retrieve the next result from a sequence of multiple results.

As your teacher has been suggesting, there have been serious flaws in some JDBC drivers that failed to live up to the promise of the JDBC spec to close the ResultSet when its Statement or PreparedStatement is closed. So many programmers make a habit of closing each ResultSet object explicitly.

This extra duty is easier now with the try-with-resources syntax. In real work you’ll likely have a try-else around all your AutoCloseable objects such as ResultSet anyways. So my own opinion is: Why not make it a try-with-resources + else? Does not hurt, makes your code more self-documenting about your intentions, and it might help if your code ever encounters one of those faulty JDBC drivers. The only cost is a pair of parens, assuming you’d have a try-catch-else in place anyways.

As stated in the Oracle Tutorial, multiple AutoCloseable objects declared together will be closed in reverse order, just as we would want.

Tip: The try-with-resources syntax allows an optional semicolon on the last declared resource item. I include the semicolon as a habit because it reads well to my eye, is consistent, and facilitates cut-and-paste editing. I include it on your PreparedStatement s2 line.

public void doQueries() throws MyException{
    // First try-with-resources.
    try ( Connection con = DriverManager.getConnection( dataSource ) ;
          PreparedStatement s1 = con.prepareStatement( updateSqlQuery ) ;
          PreparedStatement s2 = con.prepareStatement( selectSqlQuery ) ;
    ) {

        … Set parameters of PreparedStatements, etc.

        s1.executeUpdate() ;

        // Second try-with-resources, nested within first.
        try (
            ResultSet rs = s2.executeQuery() ;
        ) {
            … process ResultSet
        } catch ( SQLException e2 ) {  
            … handle exception related to ResultSet.
        }

    } catch ( SQLException e ) {  
        … handle exception related to Connection or PreparedStatements.
    }
}

I suppose there is a more elegant syntax for this kind of work that might be invented in a future programming language. But for now, we have try-with-resources, and I do use it happily. While try-with-resources is not perfectly elegant, it is a big improvement over the older syntax.

By the way, Oracle recommends using a DataSource implementation for getting connections rather than the DriverManager approach seen in your code. Using DataSource throughout your code makes it easier to switch drivers or switch to a connection pool. See if your JDBC driver provides an implementation of DataSource.

Update: Java 9

Now in Java 9 you can initialize the resources before the try-with-resources. See this article. This flexibility may be useful in some scenarios.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • I still find annoying that I have to make my code messier for the ResultSet just because people (db implementations) doesnt follow the documentation... Anyways Thanks for your nice answer – José D. Jul 16 '17 at 22:18
  • 4
    Came across this answer in 2018 and this is one of the most beautifully written answer. I was running into similar issue and thanks to this answer, my concept is clear. – stackFan Dec 05 '18 at 04:24
  • Very Nice Explanation, Thank You. – Prasanna Mondkar May 03 '19 at 20:56
  • Why is the s1.executeUpdate() not wrapped with a try-catch ? – fire_water Jan 08 '20 at 18:13
  • 1
    @fire_water That statement `s1.executeUpdate()` *is* indeed wrapped in a try-catch, the outer try-with-resources defining three resources (the `Connection` and the pair of `PreparedStatement` objects). If that `executeUpdate` call throws an exception, the exception will be caught by the `catch` clause at the bottom, and the three resources (`con`, `s1`, and `s2`) will be closed. – Basil Bourque Feb 18 '20 at 07:42
7

The fun thing about JDBC code is that you're coding to a spec where it's not always clear how compliant your implementation is. There are a lot of different databases and drivers and some drivers are better-behaved than others. That tends to make people err on the side of caution, recommending things like closing everything explicitly. You could be ok with closing only the connection here. Closing the resultSet just to be on the safe side is hard to argue with. You don't indicate what database or driver you're using here, i wouldn't want to hardcode in assumptions about the driver that might not be valid for some implementation.

Closing things in sequence does leave you open to problems where an exception can get thrown and cause some of the closes to be skipped. You're right to be concerned about that.

Be aware this is a toy example. Most real code uses a connection pool, where calling the close method doesn't actually close the connection, instead it returns the connection to the pool. So resources may not get closed once you use a pool. If you want to change this code to use a connection pool then you'll have to go back and close the statements at least.

Also if you're objecting to the verbosity of this, the answer to that is to hide the code in reusable utilities that use strategies, resultSet mappers, prepared statement setters, etc. This has all been done before, of course; you'll be on the road to reinventing Spring JDBC.

Speaking of which: Spring JDBC closes everything explicitly (probably because it needs to work with as many drivers as possible, and doesn't want to cause problems due to some driver's not being well-behaved).

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
2

This is what I find to be the best solution for handling resources like JDBC. This method provides an immutable function, by leveraging final variables, and only declaring and assigning those variables if they are needed, it is very CPU efficient, and guarantees in all cases, that all resources that are assigned and opened are closed regardless of the state of exceptions. The technique you are using leaves gaps that can result in resource leaks if not carefully implemented to address all scenarios. This technique does not allow for a resource leak provided the pattern is always followed:
1) assign resource
2) try
3) use resource
4) finally close resource

public void doQueries() throws MyException {
   try {
      final Connection con = DriverManager.getConnection(dataSource);
      try {
         final PreparedStatement s1 = con.prepareStatement(updateSqlQuery);
         try {

            // Set the parameters of the PreparedStatements and maybe do other things

            s1.executeUpdate();

         } finally {
            try { s1.close(); } catch (SQLException e) {}
         }

         final PreparedStatement s2 = con.prepareStatement(selectSqlQuery);
         try {

            // Set the parameters of the PreparedStatements and maybe do other things

            final ResultSet rs = s2.executeQuery();
            try {

               // Do something with rs

            } finally {
               try { rs.close(); } catch (SQLException e) {}
            }
         } finally {
            try { s2.close(); } catch (SQLException e) {}
         }
      } finally {
         try { con.close(); } catch (SQLException e) {}
      }
   } catch (SQLException e) {
      throw new MyException(e);
   }
}

With Java 7, you can leverage the new try -with-resources to simplify this even more: The new try -with-resources follows the above logic flow, in that it will guarantee all resources include in the with resources block that are assigned get closed. any exception thrown in the with resources block will the thrown, but those assigned resources will still be closed. This code is much simplified and looks like this:

public void doQueries() throws MyException {
   try (
      final Connection con = DriverManager.getConnection(dataSource);
      final PreparedStatement s1 = con.prepareStatement(updateSqlQuery);
      final PreparedStatement s2 = con.prepareStatement(selectSqlQuery);
      final ResultSet rs = s2.executeQuery()) {

      s1.executeUpdate();

         // Do something with rs

   } catch (SQLException e) {
      throw new MyException(e);
   }
}

[EDIT]: moved rs assignment into the resources block to show the simplest implementation. In practice, this simple solution does not really work, as this is not efficient. A connection should be reused, since establishing the connection is a very costly operation. Additionally, this simple example does not assign query parameters to the prepared statement. Care should be taken to handle these scenarios, as the the resource block should only include assignment statements. To depict this, I have also added another example

   public void doQueries() throws MyException {

      final String updateSqlQuery = "select @@servername";
      final String selecSqlQuery  = "select * from mytable where col1 = ? and col2 > ?";
      final Object[] queryParams  = {"somevalue", 1};

      try (final Connection con = DriverManager.getConnection(dataSource);
         final PreparedStatement s1 = newPreparedStatement(con, updateSqlQuery);
         final PreparedStatement s2 = newPreparedStatement(con, selectSqlQuery, queryParams);
         final ResultSet rs = s2.executeQuery()) {

         s1.executeUpdate();

         while (!rs.next()) {
            // do something with the db record.
         }
      } catch (SQLException e) {
         throw new MyException(e);
      }
   }

   private static PreparedStatement newPreparedStatement(Connection con, String sql, Object... args) throws SQLException
   {
      final PreparedStatement stmt = con.prepareStatement(sql);
      for (int i = 0; i < args.length; i++)
         stmt.setObject(i, args[i]);
      return stmt;
   }
Armand
  • 493
  • 5
  • 7
  • Can you justify why is the last try-with-resources necesary? (the one for the ResultSet) – José D. Oct 22 '15 at 17:45
  • Looking at this again I don't see my answer. Because I was showing an example of two ways since there were two statements. You can do the whole thing in the resource block. The key is that you should not assign the statement parameters from within the resource block, instead you should use a function to construct the statement so the only code in the block are assignments of closable resources. In this literal case you only need one try with resources. – Armand Oct 23 '15 at 04:25
1

This is indeed the primary motivation for try-with-resources. See the Java tutorials as reference. Your professor is out-of-date. If you want to deal with the result set issue you can always enclose it in another try-with-resources statement.

Martin Serrano
  • 3,727
  • 1
  • 35
  • 48
0

You could make a util class to handle closing of these resources. i.e FYI i just ignored SQLExceptions from trying to close resources in the util class, but you could as verywell log or collect and throw them once you are done closing the resources in the collection depending on your needs

public class DBUtil {
public static void closeConnections(Connection ...connections){
    if(connections != null ){
        for(Connection conn : connections){
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException ignored) {
                    //ignored
                }
            }
        }
    }
}

public static void closeResultSets(ResultSet ...resultSets){
    if(resultSets != null ){
        for(ResultSet rs: resultSets){
            if(rs != null){
                try {
                    rs.close();
                } catch (SQLException ignored) {
                    //ignored
                }
            }
        }
    }
}

public static void closeStatements(Statement ...statements){
    if(statements != null){
        for(Statement statement : statements){
            if(statement != null){
                try {
                    statement.close();
                } catch (SQLException ignored) {
                    //ignored
                }
            }
        }
    }
}

}

and then just call it from you method:

    public void doQueries() throws MyException {
    Connection con = null;
    try {
        con = DriverManager.getConnection(dataSource);
        PreparedStatement s1 = null;
        PreparedStatement s2 = null;
        try {
            s1 = con.prepareStatement(updateSqlQuery);
            s2 = con.prepareStatement(selectSqlQuery);

            // Set the parameters of the PreparedStatements and maybe do other things
            s1.executeUpdate();
            ResultSet rs = null;
            try {
                rs = s2.executeQuery();
            } finally {
                DBUtil.closeResultSets(rs);
            }
        } finally {
            DBUtil.closeStatements(s2, s1);
        }

    } catch (SQLException e) {
        throw new MyException(e);
    } finally {
        DBUtil.closeConnections(con);
    }
}
nbsp
  • 340
  • 3
  • 7
0

I prefer to let Java auto-close. So I do something like this when I have to set values for ResultSet.

try (Connection conn = DB.getConn();
     PreparedStatement ps = conn.prepareStatement("SELECT * FROM x WHERE y = ?")
) {
    ps.setString(1, "yValue");
   
    try(ResultSet rs = ps.executeQuery()) {
        while(rs.next()) {
            ...
        }
    }
} catch (SQLException e) {
    e.printStackTrace(e);
    ...
}
Frankie
  • 24,627
  • 10
  • 79
  • 121