Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 17 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
17
Dung lượng
70,08 KB
Nội dung
getAsciiStream( ), getBinaryStream( ), and getCharacter-Stream( ); the nonstreaming get methods are getBytes( ) and getString( ). 12.5.1 Inserting or Updating a LONG Other than the use of the methods just mentioned, when inserting or updating a LONG, there are limitations on the size of a String or byte array that you can update using the setString( ) or setBytes( ) methods. These limitations are listed in Table 11-3. When you exceed these limits, you will get the following error message: "Data size bigger than max size for this type: ####." To work around these limitations, you need to use the streaming methods. 12.5.2 Selecting a LONG To retrieve data in ASCII, use the ResultSet object's getAsciiStream( ) method. You can use the getAsciiStream( ) method only if the underlying database uses the US7ASCII or WE8ISO8859P1 character set. Otherwise, you can use the getCharacterStream( ) method, which returns UCS-2-encoded characters (Unicode) regardless of the underlying database's character set. Even if you get the data as a String, the JDBC driver will stream the data for you. If you use the getBinaryStream( ) method, one of two possibilities exists. If you are using the OCI driver, and its client character set is not set to US7ASCII or WE8ISO8859P1, then a call to the getBinaryStream( ) method returns data in the UTF-8 character set. If you are using the Thin driver, and the database's character set is not set to US7ASCII or WE8ISO8859P1, then a call to the getBinaryStream( ) method also returns data in the UTF-8 character set. Otherwise, you'll get the data in the US7ASCII character set. As with the LONG RAW data type, you must call the getXXX( ) methods for each column returned by the SELECT statement in the same order in which those columns are listed in the SELECT statement. Also, when processing a streamed column, you must process the streamed data before calling another getXXX( ) method. Once you move on to another getXXX( ) method, the streamed data is no longer accessible. In addition, you can prevent the JDBC driver from streaming LONG data by using the OracleResultSet object's defineColumnType( ) method, setting the data type of a LONG column to Types.VARCHAR. This may be desirable if there are a small number of characters in the data to be stored. Now that you are an expert on using streamingdata types, let's take a look at how to call stored procedures in Chapter 13. Chapter 13. Callable Statements CallableStatement objects are used to call stored procedures. The stored procedures themselves can be written using PL/SQL or Java. If they are written in Java, they must be published to the RDBMS by creating a SQL call specification. You can see examples of this in Chapter 5. Stored procedures exist to perform data-intensive operations that cannot be accomplished using just SQL, and they perform these operations inside the database where network performance is a moot issue. For example, let's say you need to access five different tables in order to perform a complex calculation and need to store the result in a sixth table. Let's further assume that the calculation is complex enough that it cannot be performed using just SQL. If you perform the work on a client, then the client will have to retrieve all the data necessary to perform the calculation and send the result back to the database. If the number of rows that need to be retrieved by the client is large, this network transfer of data could consume an inordinate amount of elapsed time. However, if you perform the calculation as a stored procedure, the elapsed time of transmitting data across the network will be eliminated, and the resulting calculation will be much quicker. This example represents the type of situation in which stored procedures excel. As with all good things, stored procedures are sometimes taken to an extreme and are sometimes used as a panacea. For example, some developers eliminate SQL from their application altogether and use only stored procedures. This is not a good use of Oracle stored procedures, simply because selecting data from a table from your application is faster than calling a stored procedure that selects data from a table and returns it to your application. When you go through a stored procedure, you have two network round trips instead of one. Enough with my soapbox speeches! Let's get on to some real meat. In this chapter, you'll learn how to identify the parameters for, and formulate, stored procedure calls using both SQL92 and Oracle syntax. You'll learn how to create a CallableStatement object to execute stored procedures, how to set and retrieve parameter values, and how to actually execute a stored procedure. Let's get started by looking at how to identify stored procedure names and parameter types. 13.1 Understanding Stored Procedures With Oracle, stored procedure actually refers collectively to standalone stored functions, standalone procedures, packaged functions, and procedures. So when I use the term stored procedure in this chapter, please understand that I am referring generally to any of these procedure or function types. To call a stored procedure, you need to know the procedure name and know about any parameters that will be passed to the procedure. In the case of a function, you also need to know the return type. One way to get this information is to query Oracle's data dictionary views for stored procedure source code and look at the signature for the procedures you wish to use. The next section shows you how to interpret Oracle's stored procedure signatures. Following that is a section that shows you, among other things, how to query the data dictionary for procedure signatures. 13.1.1 Stored Procedure Signatures It's important to know the differences in the syntax used by stored procedures so you can code your callable statements appropriately. Oracle stored procedures are created using three different syntaxes, one for standalone functions, another for standalone procedures, and a third for functions and procedures that are part of a package. 13.1.1.1 Standalone functions The difference between a procedure and function is that a function returns a value, so it can be used as an evaluated item in an expression. The syntax to create a stored function is: CREATE [OR REPLACE] [user.]FUNCTION function_name [(parameters)] RETURN data_type {AS | IS} function_body parameters ::= parameter_declaration [,parameter_declaration .] parameter_declaration ::= parameter_name [IN | OUT | IN OUT] data_type which breaks down as: user The schema owner of the function. function_name The name of the function. RETURN data_type Specifies the SQL data type returned by the function. parameter_name The name of a parameter passed to the function. Zero or more parameters may be passed to a function. IN | OUT | IN OUT Specifies the use of a parameter. An IN parameter can be read, but you cannot write to it. An OUT parameter can be written to but not read. You can both read from and write to an IN OUT parameter. data_type The SQL data type of the parameter. For example, to create an errorless TO_NUMBER function for user SCOTT, log into Oracle with SQL*Plus as SCOTT/TIGER and execute the following PL/SQL code: create or replace function ToNumberFun ( aiv_varchar2 in varchar2 ) return number is begin return to_number( aiv_varchar2 ); exception when OTHERS then return null; end ToNumberFun; / 13.1.1.2 Standalone procedures Use the following syntax to create a standalone stored procedure. A procedure does not return a value. However, both functions and procedures can have OUT or IN OUT variables that return values. CREATE [OR REPLACE] [user.]PROCEDURE procedure_name [(parameters)] {AS | IS} procedure_body parameters ::= parameter_declaration [,parameter_declaration .] parameter_declaration ::= parameter name [IN | OUT | IN OUT] data_type See Section 13.1.1.1 for an explanation of the syntax. 13.1.1.3 Packages A package is a collection of related functions and procedures. It has a specification that defines which functions, procedures, and variables are publicly accessible. It also has a body that contains the functions and procedures defined in the specification and possibly also contains private functions, private procedures, and private variable declarations. Packages are an improvement over standalone functions and procedures, because the separation between the specification and body reduces stored procedure dependency problems. The syntax for creating a package specification is: CREATE [OR REPLACE] PACKAGE package_name AS package_specification in which package_name is the name of the package. The following syntax is used to create a package body: CREATE [OR REPLACE] PACKAGE BODY package_name AS package_body Why is this explanation of stored procedure syntax important? Because you need to understand the stored procedure syntax in order to know the name of a stored procedure (or function), which datatypes you can pass to it, and which data type it returns. 13.1.2 Describing Signatures If you want to invoke a stored procedure and double-check its name and the parameters you must pass to it, you can take one of several approaches. If you have access to SQL*Plus, you can use one of the following variations on the DESCRIBE command: desc[ribe] [schema.]function_name desc[ribe] [schema.]procedure_name desc[ribe] [schema.]package_name The following example shows the DESCRIBE command being used to display information about the ToNumberFun function owned by the user Scott: SQL> desc tonumberfun FUNCTION tonumberfun RETURNS NUMBER Argument Name Type In/Out Default? ------------------------------ ----------------------- ------ -------- AIV_VARCHAR2 VARCHAR2 IN The JDBC API defines a method, named DatabaseMetaData.getProcedureColumns( ), you can invoke to get stored procedure signature data. In practice, however, the getProcedureColumns( ) method is not all that useful. Oracle stored procedures can be overloaded, and, using getProcedureColumns( ), you can't distinguish between the different overloaded versions of the same stored procedure. So, for Oracle at least, the getProcedureColumns( ) method is useless. A third way to determine the signature for a standalone function, standalone procedure, or package is to take a peek at the source code. The program named DescribeStoredProcedures, shown in Example 13-1, does this. You pass in the name of a stored procedure on the command line, then the program queries the SYS.DBA_SOURCE view for the source code and displays that source code. If you don't have access to the DBA views, you can change the program to use SYS.ALL_SOURCE. Example 13-1. Describing a stored procedure import java.io.*; import java.sql.*; import java.text.*; public class DescribeStoredProcedures { Connection conn; public DescribeStoredProcedures( ) { try { DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver( )); conn = DriverManager.getConnection( "jdbc:oracle:thin:@dssw2k01:1521:orcl", "scott", "tiger"); } catch (SQLException e) { System.err.println(e.getMessage( )); e.printStackTrace( ); } } public static void main(String[] args) throws Exception, IOException { String storedProcedureName = null; String schemaName = null; if (args.length > 0) schemaName = args[0]; if (args.length > 1) storedProcedureName = args[1]; new DescribeStoredProcedures( ).process( schemaName, storedProcedureName); } public void process( String schemaName, String storedProcedureName) throws SQLException { int rows = 0; ResultSet rslt = null; Statement stmt = null; String previous = "~"; String schemaPattern = "%"; String procedureNamePattern = "%"; try { if (schemaName != null && !schemaName.equals("")) schemaPattern = schemaName; if (storedProcedureName != null && !storedProcedureName.equals("")) procedureNamePattern = storedProcedureName; stmt = conn.createStatement( ); rslt = stmt.executeQuery( "select type||' '||owner||'.'||name, line, text " + "from sys.dba_source " + "where type = 'FUNCTION' " + "and owner like '" + schemaPattern + "' " + "and name like '" + procedureNamePattern + "' " + "union all " + "select type||' '||owner||'.'||name, line, text " + "from sys.dba_source " + "where type = 'PROCEDURE' " + "and owner like '" + schemaPattern + "' " + "and name like '" + procedureNamePattern + "' " + "union all " + "select type||' '||owner||'.'||name, line, text " + "from sys.dba_source " + "where type = 'PACKAGE' " + "and owner like '" + schemaPattern + "' " + "and name like '" + procedureNamePattern + "' " + "union all " + "select type||' '||owner||'.'||name, line, text " + "from sys.dba_source " + "where type = 'TYPE' " + "and owner like '" + schemaPattern + "' " + "and name like '" + procedureNamePattern + "' " + "order by 1, 2"); while (rslt.next( )) { rows++; if (!rslt.getString(1).equals(previous)) { if (!previous.equals("~")) System.out.println(""); previous = rslt.getString(1); } System.out.print(rslt.getString(3)); } if (!previous.equals("~")) System.out.println(""); } catch (SQLException e) { System.err.println("SQL Error: " + e.getMessage( )); } finally { if (rslt != null) try { rslt.close( ); } catch (SQLException ignore) { } if (stmt != null) try { stmt.close( ); } catch (SQLException ignore) { } } } protected void finalize( ) throws Throwable { if (conn != null) try { conn.close( ); } catch (SQLException ignore) { } super.finalize( ); } } Here are the results of using DescribeStoredProcedures to describe the scott.ToNumberFun function: C:\>java DescribeStoredProcedures SCOTT TONUMBERFUN function ToNumberFun ( aiv_varchar2 in varchar2 ) return number is begin return to_number( aiv_varchar2 ); exception when OTHERS then return null; Of course, the best place to get information on stored procedure names and parameters is from written documentation, but we all know that from time to time, written documentation is not available. Consequently, the SQL*Plus DESCRIBE command and the DescribeStoredProcedures program can be quite handy. Once you have the signature for the stored procedure you wish to execute, there is a problem that can arise: the username that will execute the stored procedure may not have the rights to do so. The SYS.DBA and SYS.V_$ Views Every developer who plans to work with an Oracle database should have access to the SYS.DBA and SYS.V_$ views. These views show all the objects for all the schemas in a database, even if the current user does not have access to them. At the same time, they do not show any of the actual data in schemas that a developer does not have access to, so they can't be used for malicious activities. If a developer does have access to these views, she can determine whether a problem with not finding a database object is due to grants (or lack thereof) or due to the fact that an object does not actually exist in the database. She can do this by comparing the output of the SYS.ALL views, which everyone typically has access to, against their SYS.DBA counterparts. So if you don't have access to the DBA views, request it. It will save you a great deal of time to be able to query the data dictionary views yourself. 13.1.3 Granting Execute Rights If you've written a stored procedure, how do you determine who has access to it? Initially, only you, the owner of the stored procedure, and anyone who has been granted the EXECUTE ANY PROCEDURE system privilege have access to your new procedure. But how can you tell if another user has EXECUTIVE rights? Examine the SYS.ALL_TAB_PRIVS view and look at the rows from the view that contain the name of the stored procedure in the table_name column. The view and column names make doing this a bit confusing, but that's the way things work. For example, to see who has access to user SCOTT's ToNumberFun function, execute the following query: select grantee, privilege from sys.all_tab_privs where table_name = 'TONUMBERFUN' and table_schema = 'SCOTT' This gets results such as the following: GRANTEE PRIVILEGE ------------------------------ ---------------------------------------- SYSTEM EXECUTE In this case, because no users are listed in the output, you know that user SCOTT has not yet granted anyone else access to ToNumberFun. If you find that a particular username or role needs to be granted access to a stored procedure, you or your DBA can grant those rights using the following form of the GRANT statement: GRANT EXECUTE ON [schema.]stored_procedure TO {user_name | role_name} which breaks down as: schema Optionally used by a DBA to grant someone access to another user's object stored_procedure The name of a standalone function, standalone procedure, or package user_name | role_name The name of the user or role to which you are granting EXECUTE rights Given the information in this section, you can check whether a stored procedure that does not seem to exist really does not exist or if the EXECUTE privilege has just not been granted to the desired user. Now that you have some background in Oracle stored procedure signatures and in the granting of EXECUTE privileges, let's take a look at how to formulate a stored procedure call for a callable statement. 13.2 Calling Stored Procedures Calling a stored procedure from your Java program requires a five-step process: 1. Formulate a callable statement. 2. Create a CallableStatement object. 3. Register any OUT parameters. 4. Set any IN parameters. 5. Execute the callable statement. In the sections that follow, I'll describe each of the steps listed here. The function ToNumberFun, owned by the user SCOTT, will form the basis for most of the examples. 13.2.1 Formulating a Callable Statement The first step in the process of calling a stored procedure is to formulate a stored procedure call, that is, properly format a string value that will be passed to a Connection object's prepareCall( ) method in order to create a CallableStatement object. When it comes to formulating a stored procedure call, you have two syntaxes at your disposal: the SQL92 escape syntax and the Oracle syntax. In theory, because it's not vendor-specific, the SQL92 escape syntax gives you better portability. But let's be realistic. Stored procedure capabilities and syntax vary wildly from one database vendor to another. If you choose to invest in stored procedures, I'd say you're not too interested in portability. Nonetheless, let's first take a look at the SQL92 escape syntax. 13.2.1.1 SQL92 escape syntax When using the SQL92 escape syntax, the String object or string literal you pass to the Connection object's prepareCall( ) method to create a CallableStatement object takes on one of the following forms: {? = call [schema.][package.]function_name[(?,?, .)]} {call [schema.][package.]procedure_name[(?,?, .)]} which breaks down as: schema Refers to the stored procedure's owner's username or schema name package Refers to a package name and applies only when you are not calling a standalone function or procedure function_name Refers to the name of a standalone function or to the name of a function in a package procedure_name Refers to the name of a standalone procedure or to the name of a procedure in a package ? A placeholder for an IN, OUT, or IN OUT parameter, or for the return value of a function In this syntax diagram, the curly braces ({}) are part of the syntax; they do not, in this case, denote optional choices. The question mark characters (?) in the procedure or function call mark the locations of parameters that you supply later, after creating the CallableStatement object and before executing the statement. For example, consider the following signature for the ToNumberFun standalone function: create or replace function ToNumberFun ( aiv_varchar2 in varchar2 ) return number; The properly formatted callable statement string for this function would look like: { ? = call tonumberfun( ? ) } If you are calling a stored procedure rather than a stored function, omit the leading ? = characters. That's because procedures do not return a value as functions do. The following is an example of a standalone procedure: create or replace procedure ToNumberPrc ( aiv_varchar2 in varchar2, aon_number out number ) begin aon_number := to_number( aiv_varchar2 ); exception when OTHERS then aon_number := null; end ToNumberPrc; / A properly formatted callable statement string for the ToNumberPrc procedure would look like: { call tonumberprc( ?, ? ) } If the procedure or function that you are calling is part of a stored package, then you need to reference the package name when creating your callable statement. The following is a package named chapter_13 that implements a procedure and a function, both of which are named ToNumber: REM The specification create or replace package chapter_13 as function ToNumber ( aiv_varchar2 in varchar2 ) return number; procedure ToNumber ( aiv_varchar2 in varchar2, aon_number out number ); end; / REM The body create or replace package body chapter_13 as function ToNumber ( aiv_varchar2 in varchar2 ) return number is begin return to_number( aiv_varchar2 ); exception when OTHERS then return null; end ToNumber; procedure ToNumber ( aiv_varchar2 in varchar2, aon_number out number ) is begin aon_number := to_number( aiv_varchar2 ); exception when OTHERS then aon_number := null; end ToNumber; end; / The callable statement strings for the ToNumber function and procedure defined in the chapter_13 package would look like: { ? = call chapter_13.tonumber( ? ) } { call chapter_13.tonumber( ?, ? ) } Now that you understand the SQL92 escape syntax, let's take a look at Oracle's syntax for callable statements. 13.2.1.2 Oracle syntax Oracle's syntax, which is PL/SQL syntax, is very similar to SQL92 syntax: begin ?:=[schema.][package.]function_name[(?,?, .)]; end; begin [schema.][package.]procedure_name[(?,?, .)]; end; This breaks down as: schema Refers to the stored procedure owner's username or schema name [...]... shown earlier in Section 13 .2. 1.1, you can specify the following: cstmt.registerOutParameter(1, Types. DOUBLE, 2) ; You can determine the Types constant to use for a given OUT parameter by referring to Table 10 -2 In this case, I used Types. DOUBLE, because it is one of the floating point Java data types After registering OUT parameters, continue by setting any IN parameters 13 .2. 5 Setting IN Parameters... you do for the other accessor methods, and a java.sql .Types constant to specify the SQL data type that will be returned There are two applicable signatures for the registerOutParameter( ) method For VARCHAR2 and DATE parameters, use the following signature: registerOutParameter( int parameterIndex, int sqlType) throws SQLException For NUMBER data types, you need to specify the scale of the number being... shown in the previous section on SQL 92 syntax is described here: For the standalone function ToNumberFun with a signature of: function ToNumberFun ( aiv_varchar2 in varchar2 ) return number; the callable statement string is: begin ? := tonumberfun( ? ); end; For the standalone procedure ToNumberProc with a signature of: procedure ToNumberPrc ( aiv_varchar2 in varchar2, aon_number out number ) the callable... cstmt.registerOutParameter (2, Types. DOUBLE); cstmt.setString(1, aString); cstmt.execute( ); aNumber = new Double(cstmt.getDouble (2) ); if (cstmt.wasNull( )) aNumber = null; System.out.println(aString + " converted to a number = " + aNumber); cstmt = conn.prepareCall( "{ ? = call scott.chapter_13.tonumber( ? ) }"); cstmt.registerOutParameter(1, Types. DOUBLE); cstmt.setString (2, aString); cstmt.execute(... cstmt.registerOutParameter (2, Types. DOUBLE); cstmt.setString(1, aString); cstmt.execute( ); aNumber = new Double(cstmt.getDouble (2) ); if (cstmt.wasNull( )) aNumber = null; System.out.println(aString + " converted to a number = " + aNumber); // *** Oracle PL/SQL syntax *** cstmt = conn.prepareCall( "begin ? := scott.tonumberfun( ? ); end;"); cstmt.registerOutParameter(1, Types. DOUBLE); cstmt.setString (2, aString);... cstmt.registerOutParameter (2, Types. DOUBLE); cstmt.setString(1, aString); cstmt.execute( ); aNumber = new Double(cstmt.getDouble (2) ); if (cstmt.wasNull( )) aNumber = null; System.out.println(aString + " converted to a number = " + aNumber); cstmt = conn.prepareCall( "begin ? := scott.chapter_13.tonumber( ? ); end;"); cstmt.registerOutParameter(1, Types. DOUBLE); cstmt.setString (2, aString); cstmt.execute(... null; 0; 0; " 123 4567.890"; null; try { start = System.currentTimeMillis( ); // *** SQL 92 escape syntax *** // Create the callable statement cstmt = conn.prepareCall( "{ ? = call scott.tonumberfun( ? ) }"); // Register the OUT parameter cstmt.registerOutParameter(1, Types. DOUBLE, 2) ; // Set the IN parameter cstmt.setString (2, aString); // Execute the stored procedure // Using executeUpdate( ); it returns... is: begin tonumberprc( ?, ? ); end; For the function and procedure ToNumber in the chapter_13 package with the following signatures: function ToNumber ( aiv_varchar2 in varchar2 ) return number; procedure ToNumber ( aiv_varchar2 in varchar2, aon_number out number ); the callable statement strings are: begin ? := chapter_13.tonumber( ? ); end; begin chapter_13.tonumber( ?, ? ); end; Now that you know... cstmt.setString (2, aString); But what if you need to set a parameter value to NULL? There is a special setXXX( ) method for setting an IN parameter to NULL values 13 .2. 6 Handling NULL Values If you wish to set a parameter value to NULL, then you must use the setNull( ) method, which has the following signature: setNull(int parameterIndex, int sqlType) sqlType is the appropriate java.sql .Types constant... method, which has the following signature: setNull(int parameterIndex, int sqlType) sqlType is the appropriate java.sql .Types constant for the SQL data type of the parameter in question You can determine which Types constant to use by referring to Table 10-1 13 .2. 7 Executing a Stored Procedure After a CallableStatement has been created, any OUT parameters registered, and any IN parameters set, you can . AIV_VARCHAR2 VARCHAR2 IN The JDBC API defines a method, named DatabaseMetaData.getProcedureColumns( ), you can invoke to get stored procedure signature data. . driver from streaming LONG data by using the OracleResultSet object's defineColumnType( ) method, setting the data type of a LONG column to Types. VARCHAR.