Oracle Magazine Issue Archive
TECHNOLOGY: Ask Tom
Java Tips, JDBC, PL/SQL Stored Procedures, and Custom PromptsBy Tom Kyte
Working with Java is simpler now that it's a native part of the Oracle database. Here are some interesting questions I've received about using Java. I want to return a result set from a function. I saw your example on how to return it as a ref-cursor, but I need to return it as an array and then retrieve the array in Java. Do you have an example of this? To return an array instead of a result set from a stored procedure, you'll need to use a nested table type in SQL to represent an array for the stored procedure and then use some Oracle extensions to handle the array in the Java code. To start with, create database objects such as: create or replace type SimpleArray as table of varchar2(30) / You end up with an array of varchar2(30)—like an array of strings, and a function that returns that array type to the caller. The Java code to retrieve and process this array would then look like Listing 1. Code Listing 1:Java code to retrieve and process an array
import java.sql.*;
import oracle.jdbc.driver.*;
import oracle.sql.*;
class Array
{
public static void main(String args[]) throws Exception
{
DriverManager.registerDriver(new
oracle.jdbc.driver.OracleDriver());
Connection conn = DriverManager.getConnection
("jdbc:oracle:thin:@aria:1521:ora8i", "scott", "tiger");
OracleCallableStatement stmt =
(OracleCallableStatement)conn.prepareCall
( "begin ? := getSimpleArray; end;" );
// The name we use below, SIMPLEARRAY, matches the name of
our
// type we defined in SQL above
stmt.registerOutParameter( 1, OracleTypes.ARRAY,
"SIMPLEARRAY" );
stmt.executeUpdate();
// Now we can get the array object back and print out some
meta data about it
ARRAY simpleArray = stmt.getARRAY(1);
System.out.println ("Array is of type " +
simpleArray.getSQLTypeName());
System.out.println ("Array element is of type code " +
simpleArray.getBaseType());
System.out.println ("Array is of length " +
simpleArray.length());
// Lastly, we'll print out the entire contents of our array
String[] values = (String[])simpleArray.getArray();
for( int i = 0; i < values.length; i++ )
System.out.println( "row " + i + " = '" + values[i] +
"'" );
stmt.close();
conn.close();
}
}
When executed, this routine will print: $ java Array Array is of type SCOTT.SIMPLEARRAY Array element is of type code 1 Array is of length 10 row 0 = 'entry 1' ... row 9 = 'entry 10'
Getting the Time Down to the MillisecondOur application needs to get a date value including millisecond in PL/SQL packages, but the date datatype in Oracle doesn't support millisecond. Do you have some ideas to solve this kind of problem? This is a frequently asked question. Because the Oracle date datatype can measure time only down to seconds and Java date-time datatypes have much finer granularity, you can write a small Java stored procedure that gets the time with the scale you want. The stored procedure can look like this:
ops$tkyte@DEV8I.WORLD> CREATE or replace
JAVA SOURCE
2 NAMED "MyTimestamp"
3 AS
4 import java.lang.String;
5 import java.sql.Timestamp;
6
7 public class MyTimestamp
8 {
9 public static String getTimestamp()
10 {
11 return (new
12 Timestamp(System.currentTimeMill is())). toString();
13 }
14 };
15 /
Java created.
ops$tkyte@DEV8I.WORLD> create or replace function my_timestamp return varchar2 2 AS LANGUAGE JAVA 3 NAME 'MyTimestamp.getTimestamp() return java.lang.String'; 4 / To tell SQL how to interface with this Java routine and that we are ready to go: ops$tkyte@DEV8I.WORLD> select my_timestamp, to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') 2 from dual 3 / You cannot store this time in an Oracle date type , but you can store it in a string
Getting a Result Set Without Using OracleTypes.CURSORI tried to retrieve a refcursor from a stored procedure, using OracleTypes. CURSOR, and it works. But to do this I have to load the oracle.jdbc.driver, which is not agreeable. I need to use java.sql and retrieve multiple rows, using a stored procedure. Is there a way to retrieve multiple records by calling a stored procedure from a Java program without using the OracleTypes. CURSOR and not loading the oracle.jdbc.driver? Basically you need a method to get a result set in JDBC without using any Oracle extensions. Instead of calling a stored procedure to get a query result, use a query to call a stored procedure, and that procedure then becomes the result set. Here is a small example. You must create an object type in SQL to represent your result set type. For example, if I wanted to be able to return a result set with X and Y of type number
ops$tkyte@DEV8I.WORLD> create or replace type myScalarType 2 as object ( x int, y int ) 3 / Now I can create a function that returns that type. ops$tkyte@DEV8I.WORLD> create or replace 2 function demo_proc2( p_rows_to_make_up in number ) 3 return myTableType 4 as 5 l_data myTableType := myTableType(); 6 begin 7 for i in 1 .. p_rows_to_make_up 8 loop 9 l_data.extend; 10 l_data(i) := myScalarType( i, i ); 11 end loop; 12 return l_data; 13 end; 14 / Function created. Next, I select * from the stored procedure: ops$tkyte@DEV8I.WORLD> select * 2 from the ( select cast( demo_proc2(5) as mytableType ) 3 from dual ); In this fashion, Java never calls a stored procedure but instead invokes the above Select statement, which calls the stored procedure and gives you a result set. This "trick" of selecting from a stored procedure has many other useful applications as well. For another use of this feature, see "In List Question with Bind Variables". Determining the Time Zone Setting of a ServerHow do I figure out the time zone setting currently used by my server? Using a tiny bit of Java, this is rather easy. It could look like this:
ops$tkyte@8i> create or replace and compile
java source named "TZ"
2 as
3 import java.net.*;
4 import java.io.*;
5 import java.util.*;
6 import java.text.*;
7 public class TZ
8 {
9 static public void java_get_timezone( String[] p_timezone )
10 {
11 Date d = new Date();
12 DateFormat df2 = new SimpleDateFormat( "z" );
13
14 df2.setTimeZone(TimeZone.getDefault () );
15 p_timezone[0] = df2.format(d);
16 }
17 }
18 /
You now have a procedure get_timezone
ops$tkyte@8i> declare 2 tz varchar(25); 3 begin 4 get_timezone( tz ); 5 dbms_output.put_line( tz ); 6 end; 7 / EDT On time, every time! Sleeping for Less Than a SecondThe C command of sleep() has a parameter of less than one second. Does the Oracle database have an equivalent? The builtin dbms_lock.sleep
create or replace procedure micro_sleep( p_sleep in number ) as language java name 'java.lang.Thread.sleep( long )'; / All I did here was map to the existing sleep Java function. I didn't actually write any Java at all, I just called an existing Java routine. Here is an example:
My Personal SQL PromptI notice in Oracle Magazine that you have your SQL prompt set to display "user@instance". Please tell me how you accomplish this. I can get it to display the username OR the instance name but not both. Thanks! The answer is in my login.sql
column global_name new_value gname set termout off select lower(user) || '@' || global_name global_name from global_name; set termout on set sqlprompt '&gname> '
global_name new_value gname
variable.
&gname
global_name
global_name
select lower(user) || '@' || instance global_name from v$thread From a Stored Procedure
How do I read files from a certain directory with PL/SQL without knowing the exact name? My program must interface with another system that puts files in a directory on the server. UTL_FILE only reads a file when you know the name of the file, but I don't know the name in advance. Is it possible to use wildcards (eg. '*') in the name of the file? The platform is Windows NT and we have the Jserver option. Oracle7.3 added the UTL_FILE
The interface I developed uses a global temporary table which will "lose" its rows every time you commit. You call a stored procedure by providing a DIRECTORY to scan. I put a list of all of the files that are in that directory into a temp table. If you want to "filter" the files (eg: only interested in *.txt files), use "select * from dir_list where filename like '%.txt'" to do so. The implementation is:
ops$tkyte@8i> GRANT JAVAUSERPRIV to ops$tkyte
2 /
That grant must be given to the owner of the procedure. This grant allows them access to the file system.
ops$tkyte@8i> create global temporary table DIR_LIST
2 ( filename varchar2(255) )
3 on commit delete rows
4 /
This is the temporary table where we will place results. I chose a temporary table over returning an array of objects simply for ease of coding. This implementation also gives us greater flexibility as I can sort and use the where clause on the temporary table easily. For the Java code, se e Listing 2. Code Listing 2:
I used SQLJ over JDBC in this example because it just made it so much easier (its almost always easier to use SQLJ when coding Java stored procedures—no connection hassles, less lines of code). Here's how it works:
ops$tkyte@8i> exec get_dir_list( '/tmp' );
|