Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 3 of 4
| Scenario | Database Capabilities | Description |
|---|---|---|
| 1 | Stored procedures cannot return values, but may return output parameters. | Input and output parameters alike must be set/registered in the CallableStatement prior to executing the query.
|
| 2 | Stored procedures can return values, but only primitives (such as INTEGER error codes).Stored procedures may return output parameters of any kind. |
Unless the desired return value of a stored procedure is of the specific type(s) that may be returned, all parameters must
be set/registered in the CallableStatement prior to executing the query.
|
| 3 | Stored procedures may return values of any type. | ResultSets may be returned directly or as output parameters from a stored procedure call. All input parameters must be set before executing
the query.
|
| 4 | Stored procedures may return (multiple) implicit ResultSets without using output parameters or return values.
|
(Multiple) ResultSets may be returned implicitly from the stored procedure call. No output parameters are required.
|
Table 4. Different calling methods for various database capabilities
Below is the database pseudocode to match the above scenarios. A detailed description of each scenario follows in the next section.
| Scenario | Database pseudocode |
|---|---|
| 1 |
CREATE PROCEDURE <procedureName> (theResult CURSOR OUTPUT, <otherParameters>) AS |
| 2 |
CREATE PROCEDURE <procedureName> (theResult CURSOR OUTPUT, <otherParameters>) AS |
| 3 |
CREATE PROCEDURE <procedureName> (<inputParameters>)RETURNING CURSOR AS |
| 4 |
CREATE PROCEDURE <procedureName> (<inputParameters>) AS |
Table 5. Database pseudocode for the various scenarios in Table 4
The JDBC calling structure (and the corresponding Java server application implementation) is actually quite dependent upon
the database engine's capabilities. Should you want multiple ResultSet objects returned from a stored procedure, the situation gets more complex. In the next issue of JavaWorld, I will suggest a general Java application design that should lessen the database's impact on the application when receiving
multiple ResultSets from a call to a stored procedure. At the moment, however, consider developing a Java application that talks to a database
using our four different capability scenarios. For the time being, you need only concern yourself with output ResultSet objects.
The code sample below is an example of a JDBC implementation of our first scenario.
// Database connection supposedly // already made. Connection conn; CallableStatement aStatement = null;
try { // Create the database callable statement. // For example, assume that the "<Some_SQL_Procedure_call>" // is something like "{call calculate_living_costs(?)}" // where the '?' denotes an output parameter that should // be interpreted as a java.sql.ResultSet aStatement = conn.prepareCall("<Some_SQL_Procedure_call>"); // Register a primitive output parameter // as a .... WHAT? // // Remember, no static final parameter that would indicate a ResultSet // or database CURSOR type exists in the java.sql.Types class. // The only standard option is to use the ResultSet.getObject method // to extract the CURSOR, then hope that it can be cast into a // java.sql.ResultSet. // // Thus, you have to consult the database JDBC driver documentation // to see how an output CURSOR/ResultSet should be registered. // // In most cases, the type info parameter sent in to the // registerOutParameter method is a type native to the // driver. The result: Non-standard type behavior... // // *Messy* // aStatement.registerOutParameter(1, MyJDBCDriverTypes.RESULTSET_TYPE); // Call the procedure in the database aStatement.execute(); // Handle the output // // Unfortunately, there is no method getXXX that returns a // java.sql.ResultSet object in the java.sql.CallableStatement // interface. // // Thus, you have to consult the database JDBC driver documentation // to see what method you would call to interpret the outgoing // database CURSOR as a java.sql.ResultSet. // // Hopefully, you could extract a java Object, which could be // cast into a ResultSet. // ResultSet valueReceived = ((<SpecificDriverClass>) aStatement).getResultSet(1);
// Handle the received ResultSet while(valueReceived.next()) { // ... } } catch(Exception ex) { // Log an error message System.err.println("Database communication error: " + ex); }
The most obvious problem with this approach is that the code lacks database independence. This is because the two calls that
register the outgoing ResultSet parameter and extract the data itself use JDBC driver-specific code, since the java.sql.Types and java.sql.CallableStatement lack direct support for handling ResultSets as output parameters.
Some JDBC drivers have solved the problem by treating a ResultSet object as an object of type java.lang.Object. Thus, the outgoing ResultSet may be handled as an object that is cast into a java.sql.ResultSet before actually being used to extract data. This particular way of handling ResultSets suggests that adding the method getResultSet(int index) in java.sql.CallableStatement and the constant java.sql.Types.RESULTSET could improve the JDBC API.
As an alternative, the ResultSet could be extracted with the Object getObject(int index) method, and later cast to a ResultSet. Check your driver documentation to see if this is permitted and possible.
Essentially, the same problems that pertain to our first scenario also pertain to the second. Thus, in order to receive a
ResultSet from a CallableStatement using a database driver that cannot handle CURSOR-type return values, driver-specific method calls (and, optionally, type specifications) are required:
// Database connection supposedly // already made. Connection conn; CallableStatement aStatement = null;
try { // Create the database callable statement. // For example, assume that the "<Some_SQL_Procedure_call>" // is something like "{? = call calculate_living_costs(?)}" // where the '?' denotes an output parameter that should // be interpreted as a java.sql.ResultSet aStatement = conn.prepareCall("<Some_SQL_Procedure_call>"); // Register a primitive output parameter // as a .... WHAT? // // Remember, no static final parameter that would indicate a ResultSet // or database CURSOR type exists in the java.sql.Types class. // // Thus, you have to consult the database JDBC driver documentation // to see how an output CURSOR/ResultSet should be registered. // // In most cases, the type info parameter sent in to the // registerOutParameter method is a type native to the // driver. The result: Non-standard type behavior... // // *Messy* // aStatement.registerOutParameter(2, MyDriverClass.RESULTSET_TYPE); aStatement.registerOutParameter(1, Types.<errorStatusType>); // Call the procedure in the database aStatement.execute(); // Handle the output // // Unfortunately, there is no method getXXX that returns a // java.sql.ResultSet object in the java.sql.CallableStatement // interface. // // Thus, you have to consult the database JDBC driver documentation // to see what method you would call to interpret the outgoing // database CURSOR as a java.sql.ResultSet. // // *Messy* // ResultSet valueReceived = ((<SpecificDriverClass>) aStatement).getResultSet(2); int errorStatus = aStatement.get<errorStatusType>(1); // Handle the status code of the call if(errorStatus == <completelyMad>) { // Bail out throw new SQLException("Database call broken with status: " + errorStatus); } // Handle the received ResultSet while(valueReceived.next()) { // ... } } catch(Exception ex) { // Log an error message System.err.println("Database communication error: " + ex); }
The third scenario is convenient, and relies fully on JDBC-standard method calls. However, few modern relational databases
can handle the syntax given in it ({? = call calculate_living_costs()}). It still has an advantage over scenarios 1 and 2 because the Java code is database independent. Thus, for portability reasons
scenario 3 (described below) is so far the best usage scenario. Scenario 4 is handled in the same way as scenario 3 for our
purposes; note that one sometimes has to register the output ResultSet as a parameter. Check the driver documentation.
Server-side Java: Read the whole series -archived on JavaWorld