Thursday 8 November 2012

Automatic Resource Management - understanding the pre ARM mess

One of the topics that I touch on in the new introductory Java course that I have been writing for Virtual Pair Programmers is Automatic Resource Management (ARM). This is a new feature in Java 7 that attempts to make it easier for the developer to write code that uses external resources safely.

I only give this a light touch in the course as for people new to Java, a basic understanding of the syntax is all that is really needed. However I do think it’s worth spending a little more time on ARM, and so this is the first of two blog posts on the topic. In this post, we'll look at why code pre-ARM is so messy.

Suppose we want to do something relatively simple like connect to a database, and insert a row in a table. In this example, I’ve set up a database using derby, called testDB, which has one table, called testTable. The table has 2 fields, an integer called id, and a varchar called title.

The task ahead of us is to write some code which runs a prepared statement to insert a new row into our testTable - our starting point will be something like:

public void InsertRow()
{
     Class.forName("org.apache.derby.jdbc.ClientDriver");
     Connection conn = DriverManager.getConnection("jdbc:derby://localhost/testDB");
     String sql = "INSERT INTO testTable (id,title) values(?,?)";
     PreparedStatement stm = conn.prepareStatement(sql);
     stm.setInt(1, 1);
     stm.setString(2, "The Best Java Course Ever");
     int results = stm.executeUpdate(); 
     System.out.println("Records added: " + results);
     stm.close();
     conn.close();
 }

If you haven't written database access code in Java before then this code might need some explanation. We first load the driver for our database, in this case Derby, on line 3. We then create a connection object and open a connection to the database on line 4. Line 5 defines an SQL string, using question marks where parameter values will be placed. We use this string to set up a prepared statement on line 6, and then supply the values for each parameter on lines 7 and 8. We then execute the prepared statement on line 9, and finally close the prepared statement and the connection to the database on lines 11 and 12.

Now this code as it stands won't compile as there are exceptions which need to be handed. The Class.forName command could throw a ClassNotFoundException, and lines 4,6,7,8,10,11 and 12 could each throw a SQLException.

Lets handle these exceptions with a simple try catch block which output a message to the console. Our code will now look something like this:


public void InsertRow()
{
     try
     {
          Class.forName("org.apache.derby.jdbc.ClientDriver");
          Connection conn = DriverManager.getConnection("jdbc:derby://localhost/testDB");
          String sql = "INSERT INTO testTable (id,title) values(?,?)";
          PreparedStatement stm = conn.prepareStatement(sql);
          stm.setInt(1, 1);
          stm.setString(2, "The Best Java Course Ever");
          int results = stm.executeUpdate(); 
          System.out.println("Records added: " + results);
          stm.close();
          conn.close();
     }
     catch (ClassNotFoundException e)
     {
           System.out.println("Could not load database driver");
     }
     catch (SQLException e)
     {
           System.out.println("Something went wrong with the database");
     }
}

We now have code that compiles, and even if errors occur, the inexperienced developer may think that the job is complete.

The reality, however, is that we have something rather dangerous. Suppose there is an error in our SQL statement - for example if the field name was actually booktitle rather than title, then the main part of our function would fall over at line 11.  Our SQLException catch block would run, displaying a nice friendly message on the console, and lines 12, 13 and 14 never get executed.

This is the crux of the problem - because we don't execute lines 13 and 14 we potentially leave open connections to the database. In more generic terms, the database resource remains open. The same sort of problem can occur when we write code that accesses other resources, such as accessing files, printer streams or  communication sockets.

So why is leaving resources open a problem? If you've used a computer running Microsoft Windows for any length of time, you will almost certainly have come across the situation where you can't delete a file because Windows complains it is currently in use, and yet there are no apparent applications accessing that file. This is an example of a resource left open - something has probably been accessing the file and has crashed without closing down the connection. With multi user databases,  you could see the issue occur when there are no free connections available for the next user. In even the most simple production systems, avoiding such situations is important.

So how do we get around this problem? Well in our example, we want to ensure that the stm.close() command and the conn.close() command run every time, even if an exception occurs. We might try and achieve this by putting these two lines into our SQLException catch block like so:


catch (SQLException e)
{
     System.out.println("Something went wrong with the database");
     stm.close();
     conn.close();
}

Of course that code won't compile, because the 2 new lines we have added might themselves throw a SQLException which needs to be caught. If the stm.close() throws an exception we still need to try and close the conn object's connection, so we could have some really nasty looking nested try catch blocks which would look like this:


catch (SQLException e)
{
     System.out.println("Something went wrong with the database");
     try
     {
          stm.close();
     }
     catch(SQLException e1)
     {
         //stm.close has failed but we still need to close the connection if that's open
         try
         {
              conn.close();
         }
         catch (SQLException e2)
         {
              //we can ignore this exception as it means the connection wasn't established
         }
    }
    try
    {
         conn.close();
    }
    catch (SQLException e3)
    {
         //we can ignore this exception as it means the connection wasn't established
    }
}

There's still one more job to do here to get the code to compile - we need to move the declaration to the conn and stm objects outside the initial try block, so that their scope becomes method-wide, and they are then available to the catch blocks. Our current code block now looks like this:


public void InsertRow()
{
     Connection conn = null;
     PreparedStatement stm = null;
     try
     {
          Class.forName("org.apache.derby.jdbc.ClientDriver");
          conn = DriverManager.getConnection("jdbc:derby://localhost/testDB");
          String sql = "INSERT INTO testTable (id,title) values(?,?)";
          stm = conn.prepareStatement(sql);
          stm.setInt(1, 1);
          stm.setString(2, "The Best Java Course Ever");
          int results = stm.executeUpdate(); 
          System.out.println("Records added: " + results);
          stm.close();
          conn.close();    }
     catch (ClassNotFoundException e)
     {
          System.out.println("Could not load database driver");
     }
     catch (SQLException e)
     {
          System.out.println("Something went wrong with the database");
          try
          {
               stm.close();
          }
          catch(SQLException e1)
          {
               //stm.close has failed but we still need to close the connection if that's open
               try
               {
                    conn.close();
               }
               catch (SQLException e2)
               {
                    //we can ignore this exception as it means the connection wasn't established
               }
          }
          try
          {
               conn.close();
          }
          catch (SQLException e3)
          {
                //we can ignore this exception as it means the connection wasn't established
          }
     }
}

So I hope you agree that this code works, will close the required resources, but is absolutely horrible. Imagine how complex writing code like this would get with more complex starting code.

Thankfully Java gives us a way around this problem by using the try...finally construct nested within a try...catch block. Here, the finally block will always run, and we can put our code to close our resources within that finally block. The generic syntax looks like this:

public void ExampleOfResourceManagedCode {

     //declare resources

     try
     {
          try 
          {
               //resource access code goes here
          }
          finally
          {
               //close all resources here
          }
     }
     catch (ExceptionType1 e)
     {
          // handle the exception
     }
     catch (ExceptionType2 e)
     {
          //handle the exception
     }
}

So let's see what our code looks like in this neater format.
public void InsertRow()
{
     Connection conn = null;
     PreparedStatement stm = null;
     try
     {
          try 
          {
               Class.forName("org.apache.derby.jdbc.ClientDriver");
               conn = DriverManager.getConnection("jdbc:derby://localhost/testDB");
               String sql = "INSERT INTO testTable (id,title) values(?,?)";
               stm = conn.prepareStatement(sql);
               stm.setInt(1, 1);
               stm.setString(2, "The Best Java Course Ever");
               int results = stm.executeUpdate(); 
               System.out.println("Records added: " + results);
          }
          finally 
          {
               if (stm!=null) stm.close();
               if (conn!=null) conn.close();
          }
     }
     catch (ClassNotFoundException e)
     {
          System.out.println("Could not load database driver");
     }
     catch (SQLException e)
     {
          System.out.println("Something went wrong with the database");
     }
}
So this is certainly neater, but it's not completely intuitive, and unless you write resource access code a lot, you'll probably need to refresh your memory about how this construct works each time you need it.

So we now understand the problem, and the nicer construct within Java to get around it... in the next post we'll convert this code to the ARM version and see if it really is that much nicer!

No comments:

Post a Comment