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
objectYou 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 set
XXX
methods and OUT
parameters are retrieved by using the get
XXX
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.setString("myparameter","bar"); // 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 set
XXX
methods and OUT
variable values can be retrieved by using get
XXX
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 SQL92 escape syntax. The following PL/SQL calls would work with any Oracle JDBC driver:
Example 6-3 Calling Stored Procedures
// SQL92 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 Connection 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.
Select the Connections tab to view the Connection Navigator.
Expand the database connection node (by default called DBConnection1), and the HR node to see the objects in the
HR
database.Right-click Procedures, and select New PL/SQL Procedure.
-
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.
-
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
) -
After the
BEGIN
statement, replace the line that readsNULL
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 theDataHandler.java
class. -
Add the procedure name in the
END
statement:END insert_employee;
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.
Select the Applications tab to display the Application Navigator.
If the
DataHandler.java
file is not already open in the Java Source Editor, double-click it to open it.-
Import the
CallableStatement
interface as follows:import java.sql.CallableStatement;
-
After the
addEmployee
method, add the declaration for theaddEmployeeSP
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
. -
Inside the method, add a
try
block, and inside that, connect to the database.try {
getDBConnection();
} -
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 offirst_name
,last_name
, and so on expected by the stored procedure. -
Create the
CallableStatement
:CallableStatement callstmt = conn.prepareCall(sqlString);
-
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); -
Add a trace message, and run the callable statement.
System.out.println("\nInserting with stored procedure: " +
sqlString);
callstmt.execute(); -
Add a return message:
return "success";
-
After the
try
block, add acatch
block to trap any errors. Call thelogException
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";
} 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.
Open
insert.jsp
in the Visual Editor, if it is not already open.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. Enterempsbean
as the ID, browse to selecthr.DataHandler
as the Class, and set the Scope tosession
. With the UseBean still selected on the page, set the style of this line toNone
instead of Heading 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.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'.
Press Enter at the end of the current line to create a new line.
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.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'.
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 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.
Open
insert_action.jsp
in the Visual Editor, if it is not already open.-
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");
-
Below that, still in the Scriptlet Properties dialog box, replace the existing
empsbean.addEmployee
line with the following lines of code to select theaddEmployeeSP
method or the pure JDBCaddEmployee
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()); 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 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:
A
CallableStatement
object is created by using theprepareCall
method of the connection class.The callable statement implements a PL/SQL procedure that returns a
REF CURSOR
.As always, the output parameter of the callable statement must be registered to define its type. Use the type code
OracleTypes.CURSOR
for aREF CURSOR
.The callable statement is run, returning the
REF CURSOR
.The
CallableStatement
object is cast toOracleCallableStatement
to use thegetCursor
method, which is an Oracle extension to the standard JDBC application programming interface (API), and returns theREF CURSOR
into aResultSet
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.
Select the Connections tab to view it in the Navigator.
Expand the Database node, the DBConnection1 node, and the HR node, to view the list of database objects. Scroll down to Packages. Right-click Packages and select New PL/SQL Package.
In the Create PL/SQL Package dialog, enter
JOBSPKG
as the name. Click OK. The package definition is displayed in the Source Editor.-
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;
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.
In the Connections Navigator, again expand the necessary nodes to view the objects in the
HR
database. Right-click Functions and select New PL/SQL Function from the shortcut menu.In the Create PL/SQL Function dialog, enter
GET_JOBS
as the name. Click OK. The definition for theGET_JOBS
function displays in the Source EditorIn the first line of the function definition, substitute
JobsPkg.ref_cursor
as the return value, in place ofVARCHAR2
.-
After the
AS
keyword, enter the following:jobs_cursor JobsPkg.ref_cursor;
-
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; 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.
Double-click
DataHandler.java
to open it in the Source Editor if it is not already open.-
Enter the method declaration.
public ResultSet getJobs() throws SQLException { }
-
Within the method body, connect to the database.
getDBConnection();
-
Following the connection, declare a new variable,
jobquery
:String jobquery = "begin ? := get_jobs; end;";
-
Create a CallableStatement using the
prepareCall
method:CallableStatement callStmt = conn.prepareCall(jobquery);
-
Register the type of the
OUT
parameter, using an Oracle-specific type.callStmt.registerOutParameter(1, OracleTypes.CURSOR);
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.-
Run the statement and return the result set.
callStmt.execute();
rset = (ResultSet)callStmt.getObject(1); Enclose the code entered so far in a
try
block.-
Add a catch block to catch any exceptions, and call your logException method as well.
catch ( SQLException ex ) {
logException( ex );
} -
After the close of the
catch
block, return the result set.return rset;
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.
Double-click
insert.jsp
in the Application Navigator to open it in the Visual Editor, if it is not already open.Drag a Page Directive onto the page to the right of the
useBean
tag. In the Insert Page Directive dialog box, enterJava
as the Language, and in the Import field, browse to select java.sql.ResultSet. Click OK.-
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();
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.
-
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> " );
} -
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 Structure View of Dropdown ListBox Options" 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.