Prepared and Callable Statements
Home ] Up ] Relational Databases ] Structured Query Language (SQL) ] What is JDBC? ] Connecting to Databases ] SQL Errors and Warnings ] Accessing Databases ] Creating and Populating a Database ] Transactions ] [ Prepared and Callable Statements ] Using Metadata ]

 

 

Prepared Statements

The work that a database must do to execute a SQL statement is not inconsiderable. In general, it must:

  1. Interpret/Parse the statement for valid syntax; throw an exception if invalid.
  2. Perform semantic analysis on the statement to match referenced tables and columns to tables and columns in the database; throw an exception if a referenced table or column does not exist, or there is a type mismatch of some kind, invalid operation, etc.
  3. Optimize the statement. In other words, figure out the most efficient way of executing the statement, given the existence (or otherwise) of indexes on columns referenced in the statement, etc.
  4. Create a plan. That is, create a series of instructions for how to execute the statement, based on the results of the optimization.
  5. Execute the statement, by following the plan instructions..
  6. Return any results to the user.

That's a lot of work!

There are many applications where the same statement, or almost the same statement, is executed many times during the course of a typical day. This is especially true in OLTP (On-Line Transaction Processing) applications, where there typically is a small number of transactions types being executed many, many times during a day.

What does 'almost the same statement' mean? Typically, it means that the application is repeatedly executing the same statement over and over again, with only certain parameters changing. For example, consider the following statements:

SELECT name, salary 
FROM employees
WHERE salary > 70000;

and:

SELECT name, salary 
FROM employees
WHERE salary > 200000;

You'll notice that they are essentially the same statement, except for the value supplied in the WHERE clause.

Normally, you'd execute this by using the following kind of code:

stmt = conn.createStatement();
rs = stmt.executeQuery(
                  "SELECT name, salary " + 
                  "FROM employees " +
                  "WHERE salary > 70000"
// ...
rs = stmt.executeQuery(
                  "SELECT name, salary " + 
                  "FROM employees " +
                  "WHERE salary > 200000");
// ...

In other words, you'd execute almost the same query twice (or in a typically application, many hundreds or thousands of times a day). This can be quite inefficient, because it repeats many of the steps involved in that execution unnecessarily.

The PreparedStatement Interface

There's a better way. Notice that the small change in the WHERE clause value doesn't really have any significant effect on many of the initial steps for execution of the SQL statement? Let's extract the essence of the above statement:

SELECT name, salary 
FROM employees
WHERE salary > ?;

By replacing the literal value with a '?', we have parameterized the statement.

Now, let's see if we can separate out those steps which don't have to be performed every time, in order to execute the statement:

  1. Interpret/Parse the statement for valid syntax; throw an exception if invalid.
  2. Perform semantic analysis on the statement to match referenced tables and columns to tables and columns in the database; throw an exception if a referenced table or column does not exist, or there is a type mismatch of some kind, invalid operation, etc.
  3. Optimize the statement. In other words, figure out the most efficient way of executing the statement, given the existence (or otherwise) of indexes on columns referenced in the statement, etc.
  4. Create a plan. That is, create a series of instructions for how to execute the statement, based on the results of the optimization.

In other words, we need to perform most of the work only once, and then when we actually wish to execute the statement, all we have to do is:

  1. Supply the actual value for the parameter
  2. Execute the query, using the plan.

The execution of the first 4 steps, which need only to be performed once, is known as preparing the statement.

To represent this concept, JDBC provides the PreparedStatement class. A PreparedStatement extends from Statement, so it is a kind of statement that provides additional capabilities: the ability to be prepared.

PreparedStatement provides the ability to bind parameters to the various ? present in the statement source. In order to accomplish this, it provides a number of setXXX(...) methods which can specify the data and type to be bound to each ? in the statement.

Here's an example:

package jdbc;

import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;

public class PreparedStatementUse
{
    public static void main(String[] args)
    {
        Connection conn = null;
        PreparedStatement stmt = null;
        try
        {
            Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            conn = DriverManager.getConnection("jdbc:odbc:Employees");
            stmt = conn.prepareStatement(
                        "SELECT name, salary " +
                        "FROM employees " +
                        "WHERE salary > ? " +
                        "ORDER BY salary");

            listEmployees(stmt, 80000.00);
            listEmployees(stmt, 200000.00);
        }
        catch(ClassNotFoundException ex)
        {
            ex.printStackTrace();
        }
        catch(SQLException ex)
        {
            ex.printStackTrace();
        }
        finally
        {
            if (conn != null)
            {
                try
                {
                    if (stmt != null)
                    {
                        stmt.close();
                    }
                    conn.close();
                }
                catch(SQLException ex)
                { /* Do nothing */ }
            }
        }
    }

    private static void listEmployees(PreparedStatement stmt, 
                                      double minSalary)
        throws SQLException
    {
        System.out.println(
            "==List of employees with salary > " + minSalary + "==");
        
        ResultSet rs = null;
        try
        {
            stmt.setDouble(1, minSalary);
            rs = stmt.executeQuery();
            
            int count = 0;
            while (rs.next())
            {
                String name = rs.getString(1);
                double salary = rs.getDouble(2);
                System.out.print(" Name:  " + name);
                System.out.println(", Salary: $" + salary);
            }
        }
        finally
        {
            if (rs != null)
            {
                try
                {
                    rs.close();
                }
                catch(SQLException ex)
                { /* Do nothing */ }
            }
        }
    }
}

which outputs something like:

==List of employees with salary > 80000.0==
 Name:  Michael Solistes                        , Salary: $90000.0
 Name:  Alfred J Prufrock                       , Salary: $120000.0
 Name:  Barbara Smith                           , Salary: $134000.0
 Name:  Charles Schultz                         , Salary: $230000.0
==List of employees with salary > 200000.0==
 Name:  Charles Schultz                         , Salary: $230000.0

Callable Statements

Most real relational databases now provide the ability to store executable procedures, which may be called by clients. These are called persistent stored modules (PSM) by the SQL standard. To use these procedures, you must do the following:

  1. Create (and debug) the stored procedure.
  2. Store it in the database
  3. Call the procedure from SQL

In the case of JDBC, the call to the procedure is done with a CallableStatement, which extends PreparedStatement, and provides additional capabilities above and beyond what that class provides.

While PreparedStatement supplied a number of setXXX(...) methods which are used to bind input parameters for the statement, CallableStatement can also bind output parameters, that is parameters that return data back to the client. So CallableStatement provides a set of getXXX() methods for this purpose.

  • To call a stored procedure, you first construct a CallableStatement. For example:
CallableStatement cs = con.prepareCall("{ call DOWORK(?, ?) }");

Notice the syntax of the "SQL statement":

{ call DOWORK(?, ?) }

This is an example of the syntax used by JDBC when it needs to be able to adapt to different native database syntax for a feature. In this case, many databases have their own special syntax for how they make a call to a stored procedure. Rather than forcing clients to write explicit syntax for a call, different for each database driver, JDBC, like ODBC, provides a way of specifying that the appropriate syntax needs to be invoked in a database-specific way. The JDBC driver is responsible for interpreting the syntax, and automatically converting it into the native database call syntax.

  • Once you have constructed the CallableStatement, you must register each parameter that can return a value (i.e. each OUT or INOUT parameter):
cs.registerOutParameter(2, java.sql.VARCHAR);
  • You must set the values of the input parameters exactly as you would for a PreparedStatement.
cs.setDouble(1, minSalary);
  • You can execute the CallableStatement the same way you execute a PreparedStatement:
boolean resultSetReturned = cs.execute();
  • To retrieve the values of returned parameters, you use the getXXX() methods:
String s = cs.getString(2);
  • It is possible for the CallableStatement to return more than one ResultSet, in which case you can call getMoreResults() to check if there is another result set available.

Stored procedures can also return values.

  • If the stored procedure returns a value, it can be constructed using the following syntax:
CallableStatement cs = con.prepareCall("{ ? = call GETVALUE(?) }");

In this case, the result is identified as an OUT parameter by the first ?. A return value is treated as teh first OUT parameter, and it must be registered in the same way as other OUT parameters.

 
The page was last updated February 19, 2008