Sunday 8 January 2012

Java 7's automatically closing files and resources with try catch

As part of Java 7's project coin Java introduces automatically closing files and resources. This allows us to greatly simplify the error prone boiler plate code in Java 6.

I won't go into the Java 6 way of handling resources since it has been blogged about many times,  but I will show you an example of what it looks like  below. As you can see you declare your resources, then in a try block use them. The try block has a finally that you need to close your resources in. But when closing the resources you need surround each in try-catch to make sure if you get an exception in one close it doesn't prevent the other ones from closing. As you can see there are a lot of steps to take, and a lot of places to go wrong.

Connection connect = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
             
String metricValuename = "test";
int metricId = 0;
try {
                    
       //if we don't find the value lets create one
       String sqlQuery = "INSERT INTO  metricsvaluename VALUES(?,?)";
       connect = DBConnectionManager.getInstance().getConnection();
       connect.setAutoCommit(false);
       preparedStatement = connect.prepareStatement(sqlQuery,
              Statement.RETURN_GENERATED_KEYS);
       preparedStatement.setInt(1, 0);
       preparedStatement.setString(2, metricValuename);
      
       int updated = preparedStatement.executeUpdate();
                    
       if(updated == 0) {
              throw new ApplicationException(
                ReturnCodes.UNKNOWN_ERROR.toString(),
                "no rows changed when creating new MetricValueId "
                 + metricValuename);
       }
                    
       resultSet = preparedStatement.getGeneratedKeys();
       if(resultSet.next()) {
              metricId = resultSet.getInt(1);
              DoSomethingWithId(metricId);
              connect.commit();
       } else {
              connect.rollback();
              throw new ApplicationException(
                ReturnCodes.UNKNOWN_ERROR.toString(),
                "no metricTypeId generated " + metricValuename);
       }
                    
} catch (SQLException e) {
                    
       try { connect.rollback(); }
       catch(Throwable ignore) { /* Propagate the original exception */ }
       e.printStackTrace();
} catch (IllegalArgumentException e) {
                    
       try { connect.rollback(); }
       catch(Throwable ignore) { /* Propagate the original exception */ }
       e.printStackTrace();
} finally {
                    
       try { if(resultSet != null) resultSet.close(); }
       catch (Throwable  ignore) { /* Propagate the original exception */ }
       try { if(preparedStatement != null) preparedStatement.close(); }
       catch (Throwable  ignore) { /* Propagate the original exception */ }
       try { if(connect != null) connect.close(); }
       catch(Throwable ignore) { /* Propagate the original exception */ }
}

Using Java 7 we are able to remove a lot of the boiler plate code, and thus remove a lot of potential errors. By declaring your resources inside of the try ( ... ) statement we allow the system to manage the lifetime of the resources and automatically close them once we leave the try block.

String metricValuename = "test";
int metricId = 0;
String sqlQuery = "INSERT INTO  metricsvaluename VALUES(?,?)";
try (Connection connect = DBConnectionManager.getInstance().getConnection();
       PreparedStatement preparedStatement = connect.prepareStatement(sqlQuery,
              Statement.RETURN_GENERATED_KEYS);){
                    
      
       connect.setAutoCommit(false);
             
       preparedStatement.setInt(1, 0);
       preparedStatement.setString(2, metricValuename);
             
       try {
              int updated = preparedStatement.executeUpdate();
                          
              if(updated == 0) {
                     throw new ApplicationException(
                       ReturnCodes.UNKNOWN_ERROR.toString(),
                       "no rows changed creating new MetricValueId " +
                       metricValuename);
              }
                          
              try (ResultSet resultSet =
                     preparedStatement.getGeneratedKeys()) {
                                 
                     if(resultSet.next()) {
                            metricId = resultSet.getInt(1);
                            connect.commit();
                     } else {
                            connect.rollback();
                            throw new ApplicationException(
                             ReturnCodes.UNKNOWN_ERROR.toString(),
                              "no metricTypeId generated "
                             + metricValuename);
                     }
              }
                          
       } catch (IllegalArgumentException | SQLException e) {
                    
              try { connect.rollback(); }
              catch(Throwable ignore) { /* Propagate the original exception */ }
              e.printStackTrace();
       }
                    
} catch (SQLException e) {
                    
       e.printStackTrace();
}

Since the automatic resource closing only deals with the closing of the resources we still need to worry about when to commit and rollback transactions for JDBC. Before we would roll back the transaction as part of the catch

                try {
              ... //open and use some resources
} catch (SQLException e) {
                    
       try { connect.rollback(); }
       catch(Throwable ignore) { /* Propagate the original exception */ }
       e.printStackTrace();
} finally {
              ... //close the resources
}

However with Java 7  this does not work because the resources is closed before we reach the catch statement. The code below gives me a compile error that connect cannot be resolved.

try (Connection connect = DBConnectionManager.getInstance().getConnection()) {

              ... // use some resources
} catch (SQLException e) {
                    
       try { connect.rollback(); }              //compiler error
       catch(Throwable ignore)    { /* Propagate the original exception */ }
       e.printStackTrace();
}

To overcome this, inside of the try we create the resources with, we use another try to catch any exceptions so we can rollback our changes. Then using Java 7's enhanced exception handling you can manage the exception how you like.

try (Connection connect = DBConnectionManager.getInstance().getConnection()) {

              ... // do some work you dont need to rollback

        try {
      
                     ... // do any work you need to rollback
                        connect.commit();

              } catch (IllegalArgumentException | SQLException e) {
                       
                try { connect.rollback(); }
                catch(Throwable ignore) { /* Propagate the original exception */ }
                e.printStackTrace();
        }
      
} catch (SQLException e) {
                    
       e.printStackTrace();
}

Also above you can see that we can catch many exceptions in the same catch using the syntax
        catch (IllegalArgumentException | SQLException e)

this allows us to use the same code for multiple exceptions without cheating and just catching
       catch (Exception e)