6 Enhancing the Application: Advanced JDBC Features

This chapter describes additional functionality that you can use in your Java application. Some of these features have not been implemented in the sample application, while some features are enhancements you can use in your code to improve performance.

This chapter includes the following sections:

6.1 Using Dynamic SQL

Dynamic SQL, or generating SQL statements on the fly, is a constant need in a production environment. Very often, and especially in the matter of updates to be performed on a database, the final query is not known until run time.

For scenarios where many similar queries with differing update values must be run on the database, you can use the OraclePreparedStatement object, which extends the Statement object. This is done by substituting the literal update values with bind variables. You can also use stored PL/SQL functions on the database by calling stored procedures through the OracleCallableStatement object.

This section discusses the following topics:

6.1.1 Using OraclePreparedStatement

To run static SQL queries on the database, you use the Statement object. However, to run multiple similar queries or perform multiple updates that affect many columns in the database, it is not feasible to hard-code each query in your application.

You can use OraclePreparedStatement when you run the same SQL statement multiple times. Consider a query like the following:

SELECT * FROM Employees WHERE ID=xyz;

Every time the value of xyz in this query changes, the SQL statement needs to be compiled again.

If you use OraclePreparedStatement functionality, the SQL statement you want to run is precompiled and stored in a PreparedStatement object, and you can run it as many times as required without compiling it every time it is run. If the data in the statement changes, you can use bind variables as placeholders for the data and then provide literal values at run time.

Consider the following example of using OraclePreparedStatement:

Example 6-1 Creating a PreparedStatement

OraclePreparedStatement pstmt = conn.prepareStatement("UPDATE Employees 
                                SET salary = ? WHERE ID = ?");
   pstmt.setBigDecimal(1, 153833.00)
   pstmt.setInt(2, 110592)   

The advantages of using the OraclePreparedStatement interface include:

  • You can batch updates by using the same PreparedStatement object

  • You can improve performance because the SQL statement that is run many times is compiled only the first time it is run.

  • You can use bind variables to make the code simpler and reusable.

6.1.2 Using OracleCallableStatement

You can access stored procedures on databases using the OracleCallableStatement interface. This interface extends the OraclePreparedStatement interface. The OracleCallableStatement interface consists of standard JDBC escape syntax to call stored procedures. You may use this with or without a result parameter. However, if you do use a result parameter, it must be registered as an OUT parameter. Other parameters that you use with this interface can be either IN, OUT, or both.

These parameters are set by using accessor methods inherited from the OraclePreparedStatement interface. IN parameters are set by using the setXXX methods and OUT parameters are retrieved by using the getXXX methods, XXX being the Java data type of the parameter.

A CallableStatement can also return multiple ResultSet objects.

As an example, you can create an OracleCallableStatement to call the stored procedure called foo, as follows:

Example 6-2 Creating a CallableStatement

OracleCallableStatement cs = (OracleCallableStatement)
conn.prepareCall("{call foo(?)}");

You can pass the string bar to this procedure in one of the following two ways:

cs.setString(1,"bar"); // JDBC standard
// or...
cs.setStringAtName(X, "value"); // Oracle extension

6.1.3 Using Bind Variables

Bind variables are variable substitutes for literals in a SQL statement. They are used in conjunction with OraclePreparedStatement and OracleCallableStatement to specify parameter values that are used to build the SQL statement. Using bind variables has remarkable performance advantages in a production environment.

For PL/SQL blocks or stored procedure calls, you can use the following qualifiers to differentiate between input and output variables: IN, OUT, and IN OUT. Input variable values are set by using setXXX methods and OUT variable values can be retrieved by using getXXX methods, where XXX is the Java data type of the values. This depends on the SQL data types of the columns that you are accessing in the database.

6.2 Calling Stored Procedures

Oracle Java Database Connectivity (JDBC) drivers support the processing of PL/SQL stored procedures and anonymous blocks. They support Oracle PL/SQL block syntax and most of JDBC escape syntax. The following PL/SQL calls would work with any Oracle JDBC driver:

Example 6-3 Calling Stored Procedures

// JDBC syntaxCallableStatement cs1 = conn.prepareCall
                        ( "{call proc (?,?)}" ) ; // stored proc
CallableStatement cs2 = conn.prepareCall
                        ( "{? = call func (?,?)}" ) ; // stored func

// Oracle PL/SQL block syntax
CallableStatement cs3 = conn.prepareCall
                        ( "begin proc (?,?); end;" ) ; // stored proc
CallableStatement cs4 = conn.prepareCall
                        ( "begin ? := func(?,?); end;" ) ; // stored func

As an example of using the Oracle syntax, here is a PL/SQL code snippet that creates a stored function. The PL/SQL function gets a character sequence and concatenates a suffix to it:

Example 6-4 Creating a Stored Function

create or replace function foo (val1 char)
return char as
begin
return val1 || 'suffix';
end;

You can call this stored function in a Java program as follows:

Example 6-5 Calling a Stored Function in Java

OracleDataSource ods = new OracleDataSource();
ods.setURL("jdbc:oracle:thin:@<hoststring>");
ods.setUser("hr");
ods.setPassword("hr");
Connection conn = ods.getConnection();
CallableStatement cs = conn.prepareCall ("begin ? := foo(?); end;");
cs.registerOutParameter(1,Types.CHAR);
cs.setString(2, "aa");
cs.executeUpdate();
String result = cs.getString(1);

The following sections describe how you can use stored procedures in the sample application in this guide:

6.2.1 Creating a PL/SQL Stored Procedure in JDeveloper

JDeveloper allows you to create stored procedures in the database through the Database Navigator. In these steps, you create a stored procedure that can be used as an alternative way of inserting an employee record in the sample application.

  1. Select the DatabaseNavigatorName tab to view the Database Navigator.

  2. Expand the database connection node (by default called Connection1) to see the objects in the HR database.

  3. Right-click Procedures, and select New Procedure.

  4. In the Create PL/SQL Procedure dialog, enter insert_employee as the object name. Click OK.

    The skeleton code for the procedure is displayed in the Source Editor.

  5. After the procedure name, enter the following lines of code:

    PROCEDURE    "INSERT_EMPLOYEE" (p_first_name  employees.first_name%type, 
      p_last_name    employees.last_name%type,
      p_email        employees.email%type,
      p_phone_number employees.phone_number%type,
      p_job_id       employees.job_id%type,
      p_salary       employees.salary%type
    ) 
    
  6. After the BEGIN statement, replace the line that reads NULL with the following:

      INSERT INTO Employees VALUES (EMPLOYEES_SEQ.nextval, p_first_name , 
        p_last_name , p_email , p_phone_number, SYSDATE, p_job_id, 
        p_salary,.30,100,80);
    

    You can see that the statement uses the same hard-coded values that are used for the last three columns in the addEmployee method in the DataHandler.java class.

  7. Add the procedure name in the END statement:

    END insert_employee;
    
  8. Save the file, and check whether there are any compilation errors.

The complete code for the stored procedure is shown in Example 6-6.

Example 6-6 Creating a PL/SQL Stored Procedure to Insert Employee Data

PROCEDURE    "INSERT_EMPLOYEE" (p_first_name  employees.first_name%type, 
  p_last_name    employees.last_name%type,
  p_email        employees.email%type,
  p_phone_number employees.phone_number%type,
  p_job_id       employees.job_id%type,
  p_salary       employees.salary%type
) 
AS
BEGIN
  INSERT INTO Employees VALUES (EMPLOYEES_SEQ.nextval, p_first_name , 
    p_last_name , p_email , p_phone_number, SYSDATE, p_job_id, 
    p_salary,.30,100,80);
END insert_employee;

6.2.2 Creating a Method to Use the Stored Procedure

In these steps, you add a method to the DataHandler.java class that can be used as an alternative to the addEmployee method. The new method you add here makes use of the insert_employee stored procedure.

  1. Select the Application tab to display the Application Navigator.

  2. If the DataHandler.java file is not already open in the Java Source Editor, double-click it to open it.

  3. Import the CallableStatement interface as follows:

    import java.sql.CallableStatement;
    
  4. After the addEmployee method, add the declaration for the addEmployeeSP method.

    public String addEmployeeSP(String first_name, String last_name, 
      String email, String phone_number, String job_id,
      int salary) throws SQLException {
    }
    

    The method signature is the same as that for addEmployee.

  5. Inside the method, add a try block, and inside that, connect to the database.

    try {
      getDBConnection(); 
    }
    
  6. In addition, inside the try block, create the SQL string:

    sqlString = "begin hr.insert_employee(?,?,?,?,?,?); end;";
    

    The question marks (?) in the statement are bind variables, acting as placeholders for the values of first_name, last_name, and so on expected by the stored procedure.

  7. Create the CallableStatement:

    CallableStatement callstmt = conn.prepareCall(sqlString);
    
  8. Set the IN parameters:

      callstmt.setString(1, first_name);
      callstmt.setString(2, last_name);
      callstmt.setString(3, email);
      callstmt.setString(4, phone_number);
      callstmt.setString(5, job_id);
      callstmt.setInt(6, salary);
    
  9. Add a trace message, and run the callable statement.

      System.out.println("\nInserting with stored procedure: " + 
                          sqlString);
      callstmt.execute();
    
  10. Add a return message:

      return "success"; 
    
  11. After the try block, add a catch block to trap any errors. Call the logException created in Example 5-5.

    catch ( SQLException ex ) {
      System.out.println("Possible source of error: Make sure you have created the stored procedure"); 
      logException( ex ); 
      return "failure";
    }
    
  12. Save DataHandler.java.

The complete method is shown in Example 6-7.

Note:

If you have not added the logException() method (see Example 5-5), JDeveloper will indicate an error by showing a red curly line under logException(ex). This method must be present in the DataHandler.java class before you proceed with compiling the file.

Example 6-7 Using PL/SQL Stored Procedures in Java

public String addEmployeeSP(String first_name, String last_name, 
  String email, String phone_number, String job_id,
  int salary) throws SQLException {
 
  try {
    getDBConnection(); 
    sqlString = "begin hr.insert_employee(?,?,?,?,?,?); end;";
    CallableStatement callstmt = conn.prepareCall(sqlString);
    callstmt.setString(1, first_name);
    callstmt.setString(2, last_name);
    callstmt.setString(3, email);
    callstmt.setString(4, phone_number);
    callstmt.setString(5, job_id);
    callstmt.setInt(6, salary);
    System.out.println("\nInserting with stored procedure: " + 
                       sqlString);
 
    callstmt.execute();
    return "success"; 
  }
  catch ( SQLException ex ) {
    System.out.println("Possible source of error: Make sure you have created the stored procedure"); 
    logException( ex ); 
    return "failure";
  }
}

6.2.3 Allowing Users to Choose the Stored Procedure

The steps in this section add a radio button group to the insert.jsp page, which allows a user to choose between inserting an employee record using the stored procedure, or by using a SQL query in Java code.

  1. Open insert.jsp in the Visual Editor, if it is not already open.

  2. Create a new line after the Insert Employee Record heading. With the cursor on this new line, drag UseBean from the JSP page of the Component Palette to add a jsp:useBean tag to the page. Enter empsbean as the ID, browse to select hr.DataHandler as the Class, and set the Scope to session. With the UseBean still selected on the page, set the style of this line to None instead of Heading 3.

  3. Drag a Radio Button component from the HTML Forms page of the Component Palette onto the page inside the form above the table. In the Insert Radio Button dialog, enter useSP as the Name, false as the Value, and select Checked. Click OK.

  4. In the Visual Editor, position the cursor to the right of the button, and enter text to describe the purpose of the button, for example, 'Use only JDBC to insert a new record'.

  5. Press Enter at the end of the current line to create a new line.

  6. Drag a second Radio Button below the first one. In the Insert Radio Button dialog, use useSP as the Name, true as the Value, and ensure that the Checked checkbox is not selected.

  7. In the Visual Editor, position the cursor directly to the right of the button, and enter text to describe the purpose of the button, for example, 'Use stored procedure called via JDBC to insert a record'.

  8. Save the page.

Figure 6-1 shows insert.jsp with the radio button that provides the option to use a stored procedure.

Figure 6-1 Adding a Link to Provide the Stored Procedure Option

Description of Figure 6-1 follows
Description of "Figure 6-1 Adding a Link to Provide the Stored Procedure Option"

6.2.4 Calling the Stored Procedure from the Application

The steps in this section modify the insert_action.jsp file, which processes the form on the insert.jsp page, to use the radio button selection and select the appropriate method for inserting a new employee record.

  1. Open insert_action.jsp in the Visual Editor, if it is not already open.

  2. Double-click the scriptlet to invoke the Scriptlet Properties dialog box and add a new variable after the salary variable, as follows:

    String useSPFlag = request.getParameter("useSP");
    
  3. Below that, still in the Scriptlet Properties dialog box, replace the existing empsbean.addEmployee line with the following lines of code to select the addEmployeeSP method or the pure JDBC addEmployee method to insert the record.

    if ( useSPFlag.equalsIgnoreCase("true"))
      empsbean.addEmployeeSP(first_name, last_name, email, 
      phone_number, job_id, salary.intValue()); 
    // otherwise use pure JDBC insert
    else
      empsbean.addEmployee(first_name, last_name, email, 
      phone_number, job_id, salary.intValue()); 
    
  4. Save insert_action.jsp.

You can now run the application and use the radio buttons on the insert page to choose how you want to insert the new employee record. In a browser, the page will appear as shown in Figure 6-2.

Figure 6-2 Using Stored Procedures to Enter Records

Description of Figure 6-2 follows
Description of "Figure 6-2 Using Stored Procedures to Enter Records"

6.3 Using Cursor Variables

Oracle JDBC drivers support cursor variables with the REF CURSOR types, which are not a part of the JDBC standard. REF CURSOR types are supported as JDBC result sets.

A cursor variable holds the memory location of a query work area, rather than the contents of the area. Declaring a cursor variable creates a pointer. In SQL, a pointer has the data type REF x, where REF is short for REFERENCE and x represents the entity being referenced. A REF CURSOR, then, identifies a reference to a cursor variable. Because many cursor variables might exist to point to many work areas, REF CURSOR can be thought of as a category or data type specifier that identifies many different types of cursor variables. A REF CURSOR essentially encapsulates the results of a query.

Oracle does not return ResultSets. To access data returned by a query, you use CURSORS and REF CURSORS. CURSORS contain query results and metadata. A REF CURSOR (or CURSOR variable) data type contains a reference to a cursor. It can be passed between the RDBMS and the client, or between PL/SQL and Java in the database. It can also be returned from a query or a stored procedure.

Note:

REF CURSOR instances are not scrollable.

This section contains the following subsections:

6.3.1 Oracle REF CURSOR Type Category

To create a cursor variable, begin by identifying a type that belongs to the REF CURSOR category. For example:

dept_cv DeptCursorTyp 
...

Then, create the cursor variable by declaring it to be of the type DeptCursorTyp:

Example 6-8 Declaring a REF CURSOR Type

DECLARE TYPE DeptCursorTyp IS REF CURSOR

REF CURSOR, then, is a category of data types, rather than a particular data type. Stored procedures can return cursor variables of the REF CURSOR category. This output is equivalent to a database cursor or a JDBC result set.

6.3.2 Accessing REF CURSOR Data

In Java, a REF CURSOR is materialized as a ResultSet object and can be accessed as follows:

Example 6-9 Accessing REF Cursor Data in Java

import oracle.jdbc.*;
...
CallableStatement cstmt;
ResultSet cursor;

// Use a PL/SQL block to open the cursor
cstmt = conn.prepareCall
         ("begin open ? for select ename from emp; end;");

cstmt.registerOutParameter(1, OracleTypes.CURSOR);
cstmt.execute();
cursor = ((OracleCallableStatement)cstmt).getCursor(1);

// Use the cursor like a normal ResultSet
while (cursor.next ())
    {System.out.println (cursor.getString(1));} 

In the preceding example:

  1. A CallableStatement object is created by using the prepareCall method of the connection class.

  2. The callable statement implements a PL/SQL procedure that returns a REF CURSOR.

  3. As always, the output parameter of the callable statement must be registered to define its type. Use the type code OracleTypes.CURSOR for a REF CURSOR.

  4. The callable statement is run, returning the REF CURSOR.

  5. The CallableStatement object is cast to OracleCallableStatement to use the getCursor method, which is an Oracle extension to the standard JDBC application programming interface (API), and returns the REF CURSOR into a ResultSet object.

6.3.3 Using REF CURSOR in the Sample Application

In the following sections, you enhance the sample application to display a dynamically-generated list of job IDs and job titles in the Job field when they are inserting a new employee record.

To do this, you create a database function, GET_JOBS, that uses a REF CURSOR to retrieve a result set of jobs from the Jobs table. A new Java method, getJobs, calls this database function to retrieve the result set.

6.3.3.1 Creating a Package in the Database

The following steps create a new package in the database to hold a REF CURSOR declaration.

  1. Select the DatabaseNavigatorName tab to view it in the Navigator.

  2. Expand the Connection1 node to view the list of database objects. Scroll down to Packages. Right-click Packages and select New Package.

  3. In the Create PL/SQL Package dialog, enter JOBSPKG as the name. Click OK. The package definition is displayed in the Source Editor.

  4. Position the cursor at the end of the first line and press Enter to create a new line. In the new line, declare a REF CURSOR as follows:

        TYPE ref_cursor IS REF CURSOR;
    
  5. Save the package.

The code for the package is shown in Example 6-10:

Example 6-10 Creating a Package in the Database

PACKAGE "JOBSPKG" AS
    TYPE ref_cursor IS REF CURSOR;
END;

6.3.3.2 Creating a Database Function

These steps create a database function GET_JOBS that uses a REF CURSOR to retrieve a result set of jobs from the Jobs table.

  1. In the Database Navigator, again expand the necessary nodes to view the objects in the HR database. Right-click Functions and select New Function from the shortcut menu.

  2. In the Create PL/SQL Function dialog, enter GET_JOBS as the name. Click OK. The definition for the GET_JOBS function displays in the Source Editor

  3. In the first line of the function definition, substitute JobsPkg.ref_cursor as the return value, in place of VARCHAR2.

  4. After the AS keyword, enter the following:

     jobs_cursor JobsPkg.ref_cursor;
    
  5. In the BEGIN block enter the following code to replace the current content:

      OPEN jobs_cursor FOR
      SELECT job_id, job_title FROM jobs;
      RETURN jobs_cursor;
    
  6. Save the function

The code for the function is shown in Example 6-11.

Example 6-11 Creating a Stored Function

FUNCTION    "GET_JOBS"
RETURN JobsPkg.ref_cursor
AS jobs_cursor JobsPkg.ref_cursor;
BEGIN
  OPEN jobs_cursor FOR
  SELECT job_id, job_title FROM jobs;
  RETURN jobs_cursor;
END;

6.3.3.3 Calling the REF CURSOR from a Method

These steps create a Java method, getJobs, in the DataHandler class that calls the GET_JOBS function to retrieve the result set.

  1. Double-click DataHandler.java to open it in the Source Editor if it is not already open.

  2. Enter the method declaration.

    public ResultSet getJobs() throws SQLException {
     
    }
    
  3. Within the method body, connect to the database.

      getDBConnection();
    
  4. Following the connection, declare a new variable, jobquery:

      String jobquery = "begin ? := get_jobs; end;";
    
  5. Create a CallableStatement using the prepareCall method:

      CallableStatement callStmt = conn.prepareCall(jobquery);
    
  6. Register the type of the OUT parameter, using an Oracle-specific type.

      callStmt.registerOutParameter(1, OracleTypes.CURSOR);
    
  7. When you specify that you want to use an Oracle-specific type, JDeveloper displays a message asking you to use Alt+Enter to import oracle.jdbc.OracleTypes. Press Alt+Enter, and then select OracleTypes (oracle.jdbc) from the list that appears.

  8. Run the statement and return the result set.

      callStmt.execute();
      rset = (ResultSet)callStmt.getObject(1);
    
  9. Enclose the code entered so far in a try block.

  10. Add a catch block to catch any exceptions, and call your logException method as well.

    catch ( SQLException ex ) {
      logException( ex );
    }
    
  11. After the close of the catch block, return the result set.

    return rset;
    
  12. Make the file to check for syntax errors.

The code for the getJobs method is as follows:

  public ResultSet getJobs() throws SQLException {
  try {
    getDBConnection();
    String jobquery = "begin ? := get_jobs; end;";
    CallableStatement callStmt = conn.prepareCall(jobquery);
    callStmt.registerOutParameter(1, OracleTypes.CURSOR);
    callStmt.execute();
    rset = (ResultSet)callStmt.getObject(1);
  } catch ( SQLException ex ) {
  logException( ex );
  }
  return rset;
  }

6.3.3.4 Displaying a Dynamically Generated List

To create the drop down list displaying the list of job IDs and job titles in the Insert page, you hard-coded the job IDs and job titles. In the following steps, you replace this with a dynamically-generated list provided by the REF CURSOR created in the previous section.

  1. Double-click insert.jsp in the Application Navigator to open it in the Visual Editor, if it is not already open.

  2. Drag a Page Directive onto the page to the right of the useBean tag. In the Insert Page Directive dialog box, enter java as the Language, and in the Import field, browse to select java.sql.ResultSet. Click OK.

  3. Drag a scriptlet onto the page next to the Page Directive. In the Insert Scriptlet dialog box, add the following code to execute the getJobs method and return a result set containing a list of jobs.

    ResultSet rset = empsbean.getJobs();
    
  4. Select the ListBox component in the page, and click Scriptlet in the JSP Component Palette. (You need not drag and drop the scriptlet onto the page in this case.) The Insert Scriptlet dialog box appears.

  5. Enter the following code into the Insert Scriptlet dialog box. Click OK.

      while (rset.next ())
       {
        out.println("<option value=" + rset.getString("job_id") + ">" + 
        rset.getString("job_title") + "</option> "  );
       }
    
  6. Remove the hard-coded values as follows.

    With the ListBox component still selected, in the Structure window scroll to Job field. Examine the list of hard-coded options below the select keyword. Delete each of the options, ensuring that you retain the scriptlet.

    Figure 6-3 Structure View of Dropdown ListBox Options

    Description of Figure 6-3 follows
    Description of "Figure 6-3 Structure View of Dropdown ListBox Options"

  7. Save the page.

Now run the application, click to insert a new employee and use the list to display a list of available jobs. Figure 6-4 shows the dynamic jobs list in the browser.

Figure 6-4 Dynamically Generated List in Browser

Description of Figure 6-4 follows
Description of "Figure 6-4 Dynamically Generated List in Browser"