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:
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:
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 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.setStringAtName(X, "value"); // Oracle extension
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.
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:
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.
Select the DatabaseNavigatorName tab to view the Database Navigator.
Expand the database connection node (by default called Connection1) to see the objects in the HR
database.
Right-click Procedures, and select New 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 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.
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;
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 Application 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 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
.
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 of first_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 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"; }
Save DataHandler.java
.
The complete method is shown in Example 6-7.
Note:
If you have not added thelogException()
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"; } }
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. 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.
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
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 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());
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
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:
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
:
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.
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 the prepareCall
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 a REF CURSOR
.
The callable statement is run, returning the REF CURSOR
.
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.
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.
The following steps create a new package in the database to hold a REF CURSOR
declaration.
Select the DatabaseNavigatorName tab to view it in the Navigator.
Expand the Connection1 node to view the list of database objects. Scroll down to Packages. Right-click Packages and select New 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:
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 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.
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
In the first line of the function definition, substitute JobsPkg.ref_cursor
as the return value, in place of VARCHAR2
.
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.
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; }
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, enter java
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
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