In this chapter, we’ll look at Oracle SQL data manipulation using SQLJ and then best practices with SQLJ.
SQL and PL/SQL data are manipulated in SQLJ through SQLJ host variables/expressions, result expressions, and iterator accessors. This section explains the SQLJ mechanisms to manipulate SQL data, using frequent references to the corresponding sections in Chapter 8.
Because Oracle SQLJ leverages the Oracle JDBC drivers and its wrapper classes, the mapping of SQL data types in SQLJ is almost identical to JDBC mapping, as summarized in Table 3.2. However, there are additional SQLJ-specific mappings. See Table 12.1, which summarizes the SQLJ type mapping.
Table 12.1. SQLJ Type Mapping
Oracle SQL Data Types | Standard Java and ISO SQLJ Mapping | Oracle SQLJ Extended Mapping |
---|---|---|
| java.lang.String,
|
|
Globalization LONG, | n/a |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| ||
LONG RAW | sqlj.runtime.BinaryStream | |
LONG | sqlj.runtime.CharacterStream sqlj.runtime.AsciiStream (Deprecated; use CharacterStream.) sqlj.runtime.UnicodeStream (Deprecated; use CharacterStream.) | |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Notes:
oracle.sql.NCHAR
, oracle.sql.NCLOB
, and oracle.sql.NString
are distributed with the SQLJ
runtime to represent the NCHAR
form of oracle.sql.CHAR
, oracle.sql.CLOB
, and java.lang.String
.
See the JPublisher Type Map in Part III of this book.
See JPublisher: SQL URI types, also known as data links, are mapped to java.net.URL
.
Mapping of PL/SQL BOOLEAN
to SQL NUMBER
and Java Boolean is defined in the default JPublisher type-map.
Java classes implementing the oracle.sql.ORAData
interface
Also, here is a description of the EMP
table in the SCOTT
schema.
SQL> desc emp Name Null? Type ----------------------------------------- -------- --------------------- EMPNO NOT NULL NUMBER(4) ENAME VARCHAR2(10) JOB VARCHAR2(9) MGR NUMBER(4) HIREDATE DATE SAL NUMBER(7,2) COMM NUMBER(7,2) DEPTNO NUMBER(2) SQL>
The Oracle SQLJ implementation leverages Oracle JDBC support for column type and size definitions.
When column definition is enabled, the Oracle SQLJ automatically registers OUT
column types and sizes as follows:
During translation (Oracle-specific code generation), SQLJ connects to a specified database schema to determine types and sizes of columns being retrieved.
During ISO standard SQLJ code generation, customization of source code translation, or customization of an existing profile, the column defaults become part of the SQLJ profile.
At runtime, the SQLJ runtime will use the column information to register the column types and sizes with the JDBC driver, using defineColumnType()
method.
For Oracle-specific code generation, use the SQLJ translator -optcols
option. For ISO standard code generation, use either the translator option or the Oracle customizer option (-P-Coptcols
on the SQLJ command line).
Set the user, password, and URL for the database connection using either the user
, password
, and url
options of the SQLJ translator or the -P-user
, -P-password
, and -P-url
options of the customizer on the SQLJ command line.
If you are not using the default OracleDriver
class, then set the JDBC driver class using -P-driver
on the SQLJ command line.
The discussion in Chapter 8 about the use of Java reference classes instead of primitive classes for handling null
values also applies to SQLJ host and result expressions/variables.
// Using a Double variable to pass a null value to // the database int empno = 7499; Double sal = null; #sql { UPDATE emp SET sal = :sal WHERE empno = :empno };
A sqlj.runtime.SQLNullException
is thrown when a null
value is retrieved into a host Java primitive type. JDBC retrieves NULL
as 0
or false,
while SQLJ retrieves SQL NULL
as Java null
, which allows testing null consistently (e.g., if ( host_var != null)) or if (iter.accessor_name != null)
), as illustrated in
Example 12.1. testNull.sql
import java.sql.*; import sqlj.runtime.ref.*; import oracle.sqlj.runtime.*; import sqlj.runtime.SQLNullException; public class testNull { #sql static public iterator NamedIterRef (Double sal, String ename); #sql static public iterator NamedIterPrim (double sal, String ename); public static void main(String[] args) throws java.sql.SQLException { try { Oracle.connect(testNull.class, "connect.properties"); /* * Retrieve a NULL value into a Java reference typed accessor */ NamedIterRef niterref; System.out.println("Retrieving null value with Java reference type"); #sql niterref = {select NULL as sal, ename from emp where empno = 7788 }; if (niterref.next()) { System.out.println ("Employee " + niterref.ename() + " earns " + niterref.sal()); } niterref.close(); /* * Retrieve NULL value into Java primitive type accessor */ NamedIterPrim niterprim; System.out.println ("Retrieving null value with Java primitive type, must throw an exception"); #sql niterprim = {select NULL as sal, ename from emp where empno = 7788 }; if (niterprim.next()) { System.out.println("Employee: " + niterprim.ename() + " earns .."); try { System.out.println (" earns:" + niterprim.sal()); } catch (SQLNullException e) { System.out.println (" ... well, null! Exception caught!"); } } niterprim.close(); } catch (java.sql.SQLException sqlex) { sqlex.printStackTrace(); System.out.println(sqlex); } } } $ sqlj testNull.sqlj $ java -Doracle.net.tns_admin=$TNS_ADMIN testNull Retrieving null value with Java reference type Employee SCOTT earns null Retrieving null value with Java primitive type, must throw an exception Employee: SCOTT earns .. ... well, null! Exception caught! $
Refer to Chapter 8 for a description of the SQL CHAR
and VARCHAR2
types. As Table 12.1 indicates, SQL CHAR
and VARCHAR2
columns can be mapped, among other possibilities, to and from Java String
and oracle.sql.CHAR.
// Example #1 int num = 1331; String name = "EMPLOYE"; #sql [ctx] { insert into emp (empno, ename, hiredate, sal) values (:i, :(name+"_"+num), sysdate, 3500 ) }; // Example #2 oracle.sql.CHAR char_in = new CHAR("This is a CHAR", null); oracle.sql.CHAR char_out = null; #sql { select :char_in INTO :char_out FROM dual };
Similar to JDBC, SQLJ does not support Java char
types. The following code fragment, which declares the host variable ename
as Java char
, will fail and a “Java type char for column ename is illegal
” exception will be thown.
char ename; #sql { SELECT ename INTO :ename FROM emp WHERE ename ='KING' };
Instead, the following code fragment, which maps a VARCHAR2
column (i.e., ename
) to a Java String
(the host variable is declared as Java String
) will succeed.
String ename; #sql { SELECT ename INTO :ename FROM emp WHERE ename ='KING' };
SQLJ furnishes the -fixedchar
option for CHAR
comparisons in a WHERE
clause. When set to true, SQLJ
behaves like the JDBC setFixedCHAR()
method; during WHERE
clause comparisons. This option works with both Oracle-specific and ISO standard code generations.
% sqlj -fixedchar MyProgram.sqlj ...
Notice that similar to JDBC, SQLJ treats zero-length strings (i.e., “”) as null
.
The oracle.sql.NString
class is a wrapper for java.lang.String
, distributed with the SQLJ runtime, which ensures that the national language character form of use is registered with the JDBC driver. It furnishes the following methods:
NString(String):
Creates an NString
instance from an existing String
instance.
String toString():
Returns the underlying String
instance; it enables you to use the NString
instance in String
concatenation expressions (such as "a"+b
, where b
is a string).
String getString():
Returns the underlying String
instance, similarly to toString().
However, in order to use the String
host variable to bind to NCHAR
columns, you must translate the SQLJ file with the -ncharconv
SQLJ translator option. Starting with Oracle Database 10g Release 2, Oracle SQLJ furnishes the -ncharconv
option, which instructs the translator[1] to use the SetFormOfUse
method to bind to NCHAR
columns to String
host variables.
% sqlj –ncharconv=true myNCHAR.sqlj
Then you can use the String
variables to bind NCHAR
columns, as follows:
... String v_a = "uFF5E"; String v_nb = "uFF5E"; #sql {INSERT INTO Tbl1 (ColA, NColB) VALUES (:v_a, :v_nb)}; ...
Refer to Chapter 8 for a description of SQL NUMBER
. All numeric types in Oracle Database 10g are stored as NUMBER.
Per Table 12.1, SQLJ can map SQL NUMBER
to oracle.sql.NUMBER, Short, Integer, Long, short, int, long
, and so on. However, mapping to types other than oracle.sql.NUMBER,
you may lose the precision information during the mapping.
// Mapping SQL NUMBER to oracle.sql.NUMBER oracle.sql.NUMBER com; oracle.sql.NUMBER enum = new oracle.sql.NUMBER(7902); #sql { SELECT COMM INTO :com FROM EMP WHERE EMPNO = :enum }; int rowNum = 2232 oracle.sql.NUMBER numb = new oracle.sql.NUMBER(rowNum/ 1000.0)); #sql [ctx] { INSERT INTO TAB VALUES (:numb)}; // Mapping SQL NUMBER to java.lang.Integer java.lang.Integer sal; #sql { select SAL into :sal from emp WHERE ENAME = 'SMITH' };
Refer to Chapter 8 for a description of SQL BINARY_FLOAT
and BINARY_DOUBLE
. These types can be mapped, respectively, to oracle.sql.BINARY_FLOAT
and oracle.sql.BINARY_DOUBLE.
Assume a binary_tab table with SQL BINARY_FLOAT
and BINARY_DOUBLE
columns. The following code fragment maps an oracle.sql.BINARY_FLOAT
value and an oracle.sql.BINARY_FLOAT
value to the corresponding SQL columns.
BINARY_FLOAT bf = new BINARY_FLOAT((float)789.669); BINARY_DOUBLE bd = new BINARY_DOUBLE((double)897.9999); #sql ( insert into binary_tab values(:bf, :bd)};
Refer to Chapter 8 for a description of SQL RAW data types. Similar to JDBC, the Oracle SQLJ can map SQL RAW
to/from oracle.sql.RAW
and Java bytes[]
.
import oracle.sql.*; ... byte[] bytearr = { 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61 }; RAW oraw = new RAW(bytearr); // Update a SQL RAW column withto oracle.sql.RAW data #sql { update TypesTab set xraw = :oraw); // Retrieve a SQL RAW column as oracle.sq.RAW #sql oraw = {SELECT xraw FROM TypesTab};
Refer to the corresponding JDBC section in Chapter 8 for a description of SQL LONG
and SQL LONG RAW
data. Similar to JDBC, SQLJ may map SQL LONG
to oracle.sql.RAW
or java.lang.String,
and maps SQL LONG RAW
to oracle.sql.RAW,
or byte[].
Basic Example:
byte[] buf = { 00, 01, 02, 03, 04, 05, 00, 01, 02, 03, 04, 05, 00, 01, 02, 03, 04, 05, 00, 01, 02, 03, 04, 05, 00, 01, 02, 03, 04, 05, 00, 01, 02, 03, 04, 05 }; #sql {INSET INTO raw_tab VALUES (:buf)};
However, as with JDBC, LONG
and LONG RAW
data types are by default (or most of the time) manipulated in SQLJ, in streaming mode, which is the topic of the next section.
SQLJ supports binary and character streams through the BinaryStream
and CharacterStream
classes.
The sqlj.runtime.BinaryStream
class allows mapping and streaming of binary streams, including LONG RAW
and RAW
data in SQLJ. It is a wrapper for an InputStream
, which cannot be passed directly as a parameter to an executable SQL operation because of the need to specify its length and its interpretation; therefore, an instance of BinaryStream
is passed instead.
public class sqlj.runtime.BinaryStream extends sqlj.runtime.StreamWrapper { public sqlj.runtime.BinaryStream(java.io.InputStream); public sqlj.runtime.BinaryStream(java.io.InputStream,int); public java.io.InputStream getInputStream(); public int getLength(); public void setLength(int); }
The StreamWrapper
class extends java.io.FilterInputStream
and provides the following methods:
InputStream getInputStream()
:
Returns the underlying java.io.InputStream
object.
void setLength(int length)
:
Sets the length attribute of wrapped InputStream
to the stream. Must be set before sending a SQLJ stream to the database because it controls the number of bytes read from the stream passed as an input parameter to an executable SQL operation.
int getLength()
: Returns the length
in bytes of the wrapped InputStream
(i.e., the length
attribute of a SQLJ stream), only if the length has been explicitly set using the setLength()
method (the length
attribute is not set automatically).
The java.io.FilterInputStream
class is a subclass of the java.io.InputStream,
which furnishes the following methods to the SQLJ BinaryStream
class:
Reads the next byte of data from the input stream; returns -1
when it reaches the end of the stream.
int read (byte b[])
:
Reads up to b.length
bytes of data from the input into the specified b[]
byte array.
int read (byte b[], int off, int len)
:
Reads up to len
bytes of data from the input stream, starting at the offset, off
, and writing the data into the specified b[]
byte array.
long skip (long n)
:
Skips over and discards n
bytes from the input stream; it returns the number of bytes actually skipped.
void close()
: Closes the stream and releases any associated resources.
The sqlj.runtime.CharacterStream
class allows the manipulation of character streams in SQLJ (i.e., mapping of LONG and VARCHAR2
data). It is a wrapper for java.io.FilterReader
and consequently for java.io.Reader
object, which cannot be passed directly as an input parameter because of the need to specify its length; therefore, an instance of CharacterStream
object is passed instead.
public class sqlj.runtime.CharacterStream extends java.io.FilterReader { public sqlj.runtime.CharacterStream(java.io.Reader); public sqlj.runtime.CharacterStream(java.io.Reader,int); public int getLength(); public java.io.Reader getReader(); public void setLength(int); }
The java.io.Reader
class furnishes the following methods to the SQLJ CharacterStream
class:
int read ()
: Reads the next character of data from the input stream; returns -1
when it reaches the end of the stream.
int read (char cbuf[])
: Reads character data from the input stream into the the specified cbuf[]
character array.
int read (char cbuf[], int off, int len)
: Reads up to len
characters of data from the input stream, starting at the offset, off
, into the specified b[]
character array.
long skip (long n)
: Skips over and discards n
characters from the input stream; it returns the number of character actually skipped.
void close()
: Closes the stream and releases any associated resources.
Let’s see the SQLJ streams in action.
The following code sample illustrates binary SQLJ stream manipulation and performs the following operations:
Insert data into a RAW
column: A BinaryStream
is created from an InputSream
, which is created from a FileInputStream
, which is populated from a file.
Use a named iterator and a positional iterator to retrieve String
and RAW
column data from a table; then an InputStream
is created from the RAW
column and printed. (See Listing 12.2.)
bstream.dat =========== $ cat bstream.dat 010203040506070809 $ myBStream.sql ============ rem connect scott/tiger; drop table BStreamTab ; create table BStreamTab ( col1 number, rawcol raw(128)) ; exit
Example 12.2. myBStream.sqlj
import java.sql.*; import oracle.sqlj.runtime.*; import sqlj.runtime.*; import java.io.*; import sqlj.runtime.ref.DefaultContext; public class myBStream { #sql static public iterator NamedIter (String col1, BinaryStream rawcol); #sql static public iterator PosIter (int, BinaryStream); public static void main(String[] args) throws java.sql.SQLException { NamedIter nIter; PosIter pIter; try { Oracle.connect(testNull.class, "connect.properties"); // Insert a row into a Binary Stream Table System.out.println ("Inserting data from bin stream file into a bin stream tab ..."); File bsf = new File("bstream.dat"); int size = (int)bsf.length(); InputStream myfile = new FileInputStream(bsf); BinaryStream stream = new BinaryStream(myfile, size); #sql { insert into BStreamTab values (:size, :stream) }; #sql { commit }; System.out.println(" .... Done!"); System.out.println("Retrieving data from bin stream tab ..."); // Using the Named Iterator #sql nIter = { select * from BStreamTab }; while (nIter.next()) { System.out.println ("col1: " + nIter.col1()); printstream (nIter.rawcol().getInputStream()); } int len1 = 0; BinaryStream bs1 = null; // Using the positional Iterator #sql pIter = { select * from BStreamTab }; while ( true ) { #sql { fetch :pIter into :len1, :bs1 }; if ( pIter.endFetch()) break; System.out.println( "size = " + len1 ); printstream( bs1.getInputStream() ); } } catch (FileNotFoundException ex) { ex.printStackTrace (); } catch (SQLException ex) { ex.printStackTrace (); } } static void printstream (InputStream s) { StringBuffer outbuf = new StringBuffer(); try { int count; while ((count = s.read ()) != -1) outbuf.append((char)count); System.out.println (outbuf.toString()); } catch (IOException e) { e.printStackTrace (); } } } $ sqlplus scott/tiger @myBStream $ sqlj myBStream.sqlj $ java -Doracle.net.tns_admin=$TNS_ADMIN myBStream Inserting data from bin stream file into a bin stream tab ... .... Done! Retreving data from bin stream tab ... col1: 19 010203040506070809 size = 19 010203040506070809 $
The Oracle-specific code generation allows SQLJ stream objects to be used as OUT
or INOUT
host variables in a stored procedure or function call, and also as the return value from a stored function call.
Assume the following stored procedure, which takes a LONG
data type column as input parameter and returns it as a VARCHAR2
data type:
create or replace procedure CharStrProc( long_col in long, vc2_col out varchar2) as begin vc2_col := long_col; end;
The following code fragment invokes the procedure:
// Note: a ByteArrayInputStream contains an // internal buffer that // contains bytes that may be read from the stream CharacterStream chstr = new CharacterStream ( new ByteArrayInputStream("Character String".getBytes())); String vc2 = (new String("")); chstr.setLength(20); #sql { call CharStrProc(:in chstr, :out vc2); System.out.printl("VC2 is " + vc2);
Assume the following stored function, which takes a LONG
as input parameter and returns a VARCHAR2
data:
create or replace function CharStrFunc( long_col in long) return varchar2 as vc2_col varchar2(32767); begin vc2_col := long_col; return (vc2_col); end;
The following code fragment invokes the function:
CharacterStream chstr = new CharacterStream ( new ByteArrayInputStream("Character String".getBytes())); String vc2 = (new String("")); chstr.setLength(20); #sql vc2 = { values(CharStrFunc(:in chstr)) }; System.out.printl("VC2 is " + vc2);
I recommend reading the discussion on SQL DATE
support in Oracle JDBC in Chapter 8 before proceeding. Per Table 12.1, SQL DATE
can be mapped to java.sql.Date, java.sql.Time, java.sql.Timestamp, java.lang.String,
and oracle.sql.DATE
.
The following code fragment retrieves SQL DATE
into a java.sql.Date
host variable:
// java.sql.Date hireDat= null; String ename= null; #sql pc = { select hiredate, ename from emp order by empno}; while (true) { #sql { fetch :pc into :hireDat, :ename }; if (pc.endFetch()) break; System.out.println("Employee " + ename + " was hired on " + hireDat); }
The following code fragment retrieves a SQL DATE
column into an oracle.sql.DATE
host variable:
import oracle.sql.DATE; ... DATE date=null; #sql date={VALUES(Sysdate) };
The following code fragment inserts an oracle.sql.DATE
value into a SQL DATE
column:
// oracle.sql.DATE d_in = new DATE(new Timestamp(0L)); #sql { insert INTO Tab VALUES(:d_in)};
Refer to the timestamp discussion in Chapter 8. Assuming a TIMESTAMP_TAB
table with the various timestamp type columns (i.e., C_TIMESTAMP
, C_TIMESTAMP, C_TIMESTAMP
), the following code fragment (a reuse of the JDBC timestamp-related code fragment) shows the mapping of SQL TMESTAMP
data types to java.sql.Timestamp, java.sql.Timezone,
and oracle.sql.TIMESTAMPLTZ
in SQLJ:
import java.sql.Timestamp; import java.util.Date; import oracle.sql.*; // String my_date = "2006-01-06 12:23:47.66"; oracle.sql.TIMESTAMPLTZ my_tsltz = null; GregorianCalendar my_gcal = null; Timestamp my_tss = Timestamp.valueOf(my_date); TimeZone my_tz = TimeZone.getDefault(); my_tz.setID("US/Pacific"); my_gcal = new GregorianCalendar(my_tz); my_tsltz = new oracle.sql.TIMESTAMPLTZ(conn, my_tss, my_cal); my_tstz = new oracle.sql.TIMESTAMPTZ(conn, my_tss, my_cal); my_ts = new oracle.sql.TIMESTAMP(my_tss); #sql { update timestamp_tab set c_timestamp = :my_ts, c_timestamptz = :my_tstz, c_timestampltz = my_tsltz))};
Refer to the JDBC description of this SQL data type in Chapter 8. Similar to JDBC, SQLJ can map the SQL INTERVALYM/INTERVALDS
data types to string or oracle.sql.INTERVALDS/oracle.sql.INTERVALYM
, as shown in the following code fragment:
import oracle.sql.*; // INTERVALDS ids = new INTERVALDS ("15 08:12:42.0"); #sql {INSERT INTO idstab VALUES (:ids) };
Refer to Chapter 8 for a description of SQL LOB data types, including BLOBs, CLOBs,
and BFILEs.
The Oracle SQLJ can map SQL BLOB
, CLOB,
and BFILE
as:
IN
, OUT
, or INOUT
host variables in executable SQLJ statements and in INTO
-lists
Return values from stored function calls
Column types in iterator declarations
There are primarily three ways to manipulate LOB data: using the DBMS_LOB
package; using the standard JDBC LOB API (java.sql.Blob, java.sql.Clob) and Oracle JDBC LOB wrapper classes (oracle.sql.BLOB, oracle.sql.CLOB, oracle.sql.BFILE
); and using SQLJ streaming.
Assume the following table with a BLOB
and a CLOB
column type:
create table XOB_tab (vc2 varchar2 (30), bcol blob, ccol clob);
The DBMS_LOB
package contains functions and procedures to manipulate LOBs; however, these incur systematic roundtrip(s) to the RDBMS. The following code fragments illustrate the steps, procedures, and functions to use. Steps 1 to 3 are common to all LOB types; step 4 retrieves a LOB; step 5 inserts a CLOB; and step 6 opens a BFILE.
See the PL/SQL documentation for more details on the package and its methods.
Declare iterators:
#sql public static iterator NLOBIter (String vc2, BLOB bcol, CLOB ccol); #sql public static iterator PLOBIter (String, BLOB, CLOB);
Declare LOB variable:
BLOB blobLoc = null;
Inserts an empty LOB, to initialize the LOB locator:
When LOB
data is smaller than 4 K, it is stored inline (within the table column); otherwise, only the locator is stored within the table column. In all cases, you need to first initialize the LOB by using the EMPTY_CLOB()
and EMPTY_BLOB()
methods.
#sql { INSERT into XOB_Tab (vc2, bcol, ccol) VALUEs ('one', empty_blob(), epty_clob())};
Retrieve the locator(s) and then the LOB data:
// Declare Iterator and execute the query NLOBIter nbiter; #sql niter = { select * from XOB_tab }; // Process result in iterator while (niter.next ()) { // Get the BLOB LOcator blobLoc = niter.b(); // Get the size of the BLOB long blobLength; #sql blobLength = { VALUES(dbms_lob.getLength(:blobLoc)) }; // loop until we retrieve all data long i = 0; int chunk = 10; while (i < blobLength) { long readbuff = chunk; byte [] bytesread = null; // retrieve a chunk of LOB data from the database #sql { call dbms_lob.read(:blobLoc, :inout readbuf, :i+1, :out bytesread) }; // fill the buffer StringBuffer buffer = new StringBuffer(); int j; for (j = 0; j < readbuf; j++) { buffer.append(bytesread [j] + " "); } }
Insert data into LOB columns:
// This time let's use CLOB methods long i = 0; long chunk = 10; long length = 40; // INSERT CLOB Data while (i < length) { String chunkStr = "CLOB Data " + i; #sql { call dbms_lob.write(:inout clobLoc, :chunk, :i+1, :chunkStr) }; i += chunk; if (length - i < chunk) chunk = length - i; }
The following code fragment opens a BFILE
for read access (BFILEs are read-only files). Refer to Chapter 8 for a description of directory alias.
BFILE bfile; String dirAlias, fname; #sql { CALL dbms_lob.filegetname(:bfile, :out dirAlias, :out fname) }; // Print the file name System.out.println("fname: " + dirAlias + "/" + name); boolean bool; #sql bool = { VALUES(dbms_lob.fileisopen(:bfile)) }; if (!bool) { #sql { CALL dbms_lob.fileopen(:inout bfile) }; } // at this stage, bfile contains the reference of an openned // BFILE object ;
JDBC 2.0 specifies standard java.sql.Blob
and java.sql.Clob
API for manipulating LOBs. The oracle.sql.CLOB, oracle.sql.BLOB,
and oracle.sql.BFILE
wrapper classes furnish methods such as putBytes(), getBytes(), putChars(), getChars()
, and so on for manipulating LOB locators and LOB data.
First steps:
#sql { create table XOB_Tab( vc2 VARCHAR2, bcol BLOB, ccol CLOB) }; // Insert an empty LOB to initialize the locator #sql { insert into XOB_Tab values( 'one', null,null) }; // Declare Standard JDBC 2.0 Locators and retrieve these java.sql.Blob blobLoc; java.sql.Clob clobLoc; #sql { select bcol, ccol into :blobLoc, :clobLoc from XOB_Tab }; // Declare oracle.sql Locators and retrieve these oracle.sql.BLOB oblobLoc; oracle.sql.CLOB ocloLoc; #sql { select bcol, ccol into :oblobLoc, :oclobLoc from XOB_Tab };
At this stage, the LOB data can be manipulated in chunks; the chunk size is determined by the RDBMS and must be retrieved from the locator, as follows:
int chunksize = blobLoc.getChunkSize();
The data for insertion into the BLOB
or CLOB
must be read from a file, as follows:
// File and FileInputStream File blobfile = new File("<filename>"); FileInputStream fis = new FileInputSTream(blobfile); // create a byte array or char array buffer byte [] bytearr = new byte[chunksize]; // for BLOBs char [] chararr = new char[chunksize]; // for CLOBs // read the content of the file into the byte/char // array long cursor = 1; // track the current position in the // LOB int readsize; // number of bytes/char read from file long blobsize = oblobLoc.length(); //get length in // bytes while (readsize = fis.read(bytearr)) != -1 { // write bytes directly into the BLOB oblobLoc.putBytes(cursor, bytearr); cursor += readsize; }
Close resources and commit changes:
The SQLJ streaming classes (i.e., sqlj.runtime.BinaryStream
and sqlj.runtime.CharacterStream
) can be used to stream the LOB content directly to the database (or retrieve the content directly). In the previous example, we used putBytes()
or putChar()
to write LOB content to the database. In this case, the buffer must be written to the database, using the BinaryStream
and CharacterStream
approach instead (refer to the previous SQLJ stream code sample). There is no need or opportunity to commit or roll back changes when data is streamed directly into the database.
BLOB
, CLOB
, and BFILE
host variables can be assigned the result of a stored function call.
CLOB clobLoc; #sql clobLoc = { VALUES(return_clob(...)) };
Host variables of the BLOB
, CLOB
, and BFILE
type can appear in the INTO-
list of a SELECT INTO
executable statement. Assume the previously created table XOB_Tab
, with the following rows:
INSERT INTO XOB_Tab VALUES ('one', '010101010101010101010101010101', 'onetwothreefour'), INSERT INTO XOB_tab VALUES ('two', '020202020202020202020202020202', 'twothreefourfivesix'),
The following code fragment uses a BLOB
and a CLOB
as host variables that receive data from the table defined, using a SELECT INTO
statement:
... BLOB blobLoc; CLOB clobLoc; #sql { SELECT t1.b, t2.c INTO :blobLoc, :clobLoc FROM XOB_TAb t1, XBO_Tab t2 WHERE t1.vc2='one' AND t2.vc2='two' }; #sql { INSERT INTO XOB_Tab VALUES('three', :blobLoc, :clobLoc) }; ...
The BLOB
, CLOB
, and BFILE
types can be used as column types for SQLJ positional and named iterators. Such iterators can be populated as a result of compatible executable SQLJ operations.
#sql iterator NamedLOBIter(CLOB ccol); #sql iterator PositionLOBIter(BLOB); #sql iterator NamedFILEIter(BFILE bfile);
#sql iterator NamedLOBIter(CLOB c); ... NamLOBIter nliter; #sql nliter = { SELECT ccol FROM XOB_Tab }; if (nliter.next()) CLOB clob1 = nliter.c(); if (iter.next()) CLOB clob2 = nliter.c(); nliter.close(); //
BLOB
, CLOB
, and BFILE
host variables can be used with positional iterators and appear in the INTO
-list of the FETCH INTO
statements.
#sql iterator PositionLOBIter(BLOB); PosLOBIter pliter; BLOB blobLoc = null; #sql pliter = { SELECT bcol FROM XOB_tab }; for (long rowNum = 1; ; rowNum++) { #sql { FETCH :pliter INTO :blobLoc }; if (pliter.endFetch()) break; // write to Blob }
As described in Chapter 8, the Oracle SQL ROWID
uniquely identifies each row in a database table. The Oracle SQLJ maps SQL ROWID
to oracle.sql.ROWID
. Variables of the oracle.sql.ROWID
type can be used in Oracle SQLJ applications, as follows:
IN
, OUT
, or INOUT
host variables in SQLJ executable statements and in INTO
-lists
Return value from a stored function call
Column types in iterator declarations
The following code fragments illustrate these cases:
#sql public static iterator NamedRowidIter(String ename, OracleRowid rowid); #sql public static iterator PositionedRowidIter(String, OracleRowid); NamedRowidIter iter; OracleRowid rowid; int raise = 600; #sql { select rowid into :rowid from emp where ename='KENNETH' }; #sql { update emp set sal = sal + :raise WHERE rowid = :rowid }; #sql { begin select rowid into :out rowid from emp where ename='GARETH'; end; }; // Stored Function returning ROWID CREATE OR REPLACE FUNCTION retrowid ( name VARCHAR2) RETURN ROWID IS rowidvar ROWID; BEGIN SELECT rowid INTO rowidvar FROM emp WHERE ename = name; RETURN rowidvar; END retrowid;
Given the RETROWID
stored function, the following code fragment illustrates how to use a strored function, which returns a ROWID
:
ROWID myrowid; sql myrowid = { values(retrowid('GABRIELLE')) };
The upcoming JDBC 4.0 specification[2] is expected to furnish a standard java.sql.ROWID
type.
As described in Chapter 8, the Oracle SQL OPAQUE
types are used internally by Oracle (their internal representation is not exposed) to implement other types. SQL OPAQUE
can be mapped in Java to oracle.sql OPAQUE
or to a custom class implementing the oracle.sql.ORAData
interface (covered later).
The most well-known example of an OPAQUE
type implementation is the SQL XMLType
(i.e., SYS.XMLTYPE
) to represent and natively store XML documents (XMLType
can be used as a column type in a table or view). It has a SQL API with built-in member functions to create, query, extract, and index XML data stored in an Oracle Database 10g.
Oracle SQLJ maps SYS.XMLType
to oracle.xdb.XMLType
; however, the JDBC-OCI drivers are required.
Assume a table with an XMLType
column, created as follows:
create xmltype_tbl (xmltype_col SYS.XMLType); insert into xmltype_tbl values(SYS.XMLType('<name>tom</name>')); insert into xmltype_tbl values(SYS.XMLType('<name>jon</name>'));
It can be manipulated using the following code fragments:
import oracle.xdb.XMLType; ... // #sql iter={select xmltype_col from xmltype_tbl;} while(iter.next()) { System.out.println(iter.xmltype_col().getStringVal()); } // while (iter.next()) { System.out.println(iter.xmltype_col.getClobVal()); }
XMLType
can also be used for parameters in stored procedures, and as return values. Alternatively, you can leverage the PL/SQL API for XML in the XDB documentation[3] and embed XMLType
-related PL/SQL blocks within SQLJ.
As mentioned in Chapter 8, the SQL objects can be mapped to Java using either a weak type-mapping approach (i.e., automatically using java.sql.Struct
or a oracle.sql.STRUCT
) or strong type-mapping approach (i.e., using the custom Java classes that implement SQLData
or ORAData
).
Refer to Chapter 8 for an explanation of type-maps. SQLJ supports type-maps through connection contexts and iterators. Reusing the JDBC type map example in Chapter 8, assume you want to map the SQL type Address_t
to the class AddressObj.
A type map can be associated with the connection context class, as follows:
#sql public static context TypeMapCtx with (typeMap="AddressObj");
The generated TypeMapCtx
class will define a public static final String typeMap
attribute that will be initialized to the value AddressObj.
A type map can also be associated with the iterator class in an iterator declaration:
#sql public static iterator TypeMapIterator with (typeMap="AddressObj") (Person pers, Address addr);
For Oracle-specific code generation, the iterator and connection context declarations must be of the same type-maps.
SQLJ applications can perform custom mapping and processing of user-defined types (i.e., Oracle objects, references, or collections), using the following weakly typed classes: java.sql.Struct
or oracle.sql.STRUCT
for objects, java.sql.Ref
or oracle.sql.REF
for object references, and java.sql.Array
or oracle.sql.ARRAY
for collections. See a description of java.sql.Struct
and oracle.sql.STRUCT
classes and their methods in Chapter 8.
These classes can be used for:
Iterator columns:
#sql public static iterator NamdStructIter ( STRUCT mySTRUCT ); #sql public static iterator PosdStructIter ( STRUCT );
Input host expressions:
STRUCT Struct = null; #sql { CALL myAddress ( :IN Struct) };
Output host expressions in INTO
-list:
PosdStructIter iter; #sql { FETCH :iter INTO :struct };
However, when objects or collections are written or read to and from instances of these classes, SQLJ cannot perform type checking; also, because the underlying user-defined SQL type name (i.e., Address_t
) is not known by the Oracle JDBC driver, these classes cannot be used in the following host expressions:
IN
parameter if null:
STRUCT Struct = null; #sql { insert into addressObjtab values(:IN Struct) }; // Wrong
OUT
or INOUT
parameter in a stored procedure or function call:
STRUCT Struct = new STRUCT(strdesc, conn, attrib); // See chapt. 8 #sql { call myProc1(:INOUT Struct) }; // Wrong
OUT
parameter in a stored function result expression:
#sql ret = { VALUES(myFunc(:OUT struct)) }; // Wrong
Alternatively to weak type mapping and similar to JDBC, SQLJ can perform strong type mapping of a user-defined object, using classes that implement either the standard SQLData
interface or Oracle-specific ORAData
interface. Both interfaces are described in Chapter 8.
Custom mapping of user-defined object types using SQLData
requires the following:
A Java class implementing the SQLData
interface: can be handcrafted or generated using JPublisher:
StudentObj.java =============== import java.sql.*; import java.util.PropertyResourceBundle; public class StudentObj implements SQLData { //public static final String _SQL_NAME = "STUDENT"; private String sql_type; public String studName; public int studNo; //Constructors public StudentObj() { } public StudentObj (String sql_type, String studName, int studNo) { this.sql_type = sql_type; this.studName = studName; this.studNo = studNo; } // Implement SQLData interface public String getSQLTypeName() throws SQLException { return sql_type; } public void readSQL(SQLInput stream, String typeName) throws SQLException { sql_type = typeName; studName = stream.readString(); studNo = stream.readInt(); } public void writeSQL(SQLOutput stream) throws SQLException { stream.writeString(studName); stream.writeInt(studNo); } }
A connection context and iterator of the class implementing the SQLData
interface:
#sql public static context StudCtx with (typeMap = "StudentObj"); #sql public static iterator StudIter with (typeMap = "StudentObj") (StudentObj studobj);
A resource file, which specifies the mapping as follows:
class.<class name<=STRUCT <SQL type name< $ cat StudentObj.properties // Student type map resource file class.StudentObj=STRUCT SCOTT.StudentObj $
If the SQLData
wrapper classes appear as OUT
or INOUT
parameters in SQLJ statements, then you must use the Oracle SQLJ runtime and Oracle-specific code generation or profile customization.
The related SQLJ statements must explicitly use a connection context instance of the corresponding connection context type. (See Listing 12.3 and 12.4.)
StudCtx Ctx = new StudCtx(DefaultContext.getDefaultContext()); StudIter objiter; #sql [Ctx] objiter = { select studobj from Student_table };
Example 12.3. mySqlData.sqlj
import java.sql.*; import oracle.jdbc.OracleDriver; import sqlj.runtime.*; import oracle.sqlj.runtime.*; import sqlj.runtime.ref.DefaultContext; import java.math.BigDecimal; public class mySqlData { #sql public static context StudCtx with (typeMap = "StudentObj"); #sql public static iterator StudIter with (typeMap = "StudentObj") (StudentObj studobj); public static void main(String args []) throws Exception { Oracle.connect(mySqlData.class, "connect.properties"); StudCtx Ctx = new StudCtx(DefaultContext.getDefaultContext()); try { // Clean-up previous attempts #sql [Ctx] { drop table Student_TABLE }; #sql [Ctx] { drop type Student FORCE }; } catch (SQLException e){ // ignore clean-up exception } // Create the type and populate the table System.out.println("Create Student type and table"); #sql [Ctx] {CREATE TYPE StudentObj AS OBJECT (studName VARCHAR2(50),studNo INTEGER)}; #sql [Ctx] {CREATE TABLE Student_TABLE (studobj StudentObj)}; #sql [Ctx] {INSERT INTO Student_TABLE VALUES (StudentObj('Zoe Nucci', 231))}; // Create a SQLData object StudentObj stdobj = new StudentObj("SCOTT.Student", "Bobo Lafleur", 564); // Insert the SQLData object System.out.println("Insert new student"); #sql [Ctx] { insert into Student_table values (:stdobj) }; // Retrieve the inserted object System.out.println("Rerieve the inserted student"); StudIter objiter; #sql [Ctx] objiter = { select studobj from Student_table }; while(objiter.next()) { stdobj = objiter.studobj(); System.out.println("Student Name: " + stdobj.studName + " student #: " + stdobj.studNo ); } objiter.close(); Oracle.close(); } }
Strong type mapping using SQLData
is restricted to user-defined objects only; however, Oracle’s ORAData
allows strong type mapping of object references, collections, and other SQL types.
Custom mapping of user-defined object types using ORAData
requires:
The wrapper class (in other words the Java class performing the mapping) must implement the oracle.sql.ORAData
interface, including the getFactory(), create(), toString(),
and toDatum()
methods.
The wrapper class must implement the getORADataFactory()
method, which returns an oracle.sql.ORADataFactory
object, as follows:
public static oracle.sql.ORADataFactory getORADataFactory();
The wrapper class must have a String
constant _SQL_TYPECODE
, initialized to OracleTypes.STRUCT
typecode:
public static final int _SQL_TYPECODE = OracleTypes.STRUCT;
The wrapper class must have a _SQL_NAME
initialized to the SQL name:
public static final String _SQL_NAME = "Student";
Here is a handcrafted wrapper class implementing ORAData
; as already mentioned, the lazy but smart developer can use JPublisher to generate it.
StudentORAData.java
====================
import java.sql.*;
import oracle.sql.*;
import oracle.jdbc.OracleTypes;
public class StudentORAData implements ORAData, ORADataFactory
{
static final StudentORAData _factory = new StudentORAData ();
public static final String _SQL_NAME = "STUDENTORADATA";
public static final int _SQL_TYPECODE = OracleTypes.STRUCT;
private String sql_type = "SCOTT.STUDENTORADATA";
public String studName;
public int studNo;
public static ORADataFactory getORADataFactory()
{
return _factory;
}
//Constructors
public StudentORAData() { }
public StudentORAData (String studName, int studNo) {
//this.sql_type = sql_type;
this.studName = studName;
this.studNo = studNo;
}
public Datum toDatum(Connection conn)
throws SQLException
{
StructDescriptor strdesc =
StructDescriptor.createDescriptor(sql_type, conn);
Object [] attribs = { studName, new Integer(studNo) };
return new STRUCT(strdesc, conn, attribs);
}
public ORAData create(Datum datm, int sqlType) throws SQLException
{
if (datm == null) return null;
Datum[] attribs = ((STRUCT) datm).getOracleAttributes();
return new StudentORAData ( attribs[0].stringValue (),
attribs[1].intValue ());
}
public String toString ()
{
return sql_type + " = " + studName + ", " + studNo;
}
}
$ javac StudentORAData.java
Listing 12.4 hereafter uses StudentORAData
.
Example 12.4. myORAData.sqlj
import java.sql.*; import oracle.jdbc.OracleDriver; import sqlj.runtime.*; import oracle.sqlj.runtime.*; import sqlj.runtime.ref.DefaultContext; import java.math.BigDecimal; public class myORAData { #sql public static iterator StudIter (StudentORAData studobj); public static void main(String args []) throws Exception { Oracle.connect(mySqlData.class, "connect.properties"); try { // Clean-up previous attempts System.out.println("Drop student table and type"); #sql { drop table Student_TABLE }; #sql { drop type Student FORCE }; } catch (SQLException e){ // ignore clean-up exception } // Create the type and populate the table System.out.println("Create Student type and table"); #sql {CREATE TYPE StudentORAData AS OBJECT (studName VARCHAR2(50),studNo INTEGER)}; #sql {CREATE TABLE Student_TABLE (studobj StudentORAData)}; #sql {INSERT INTO Student_TABLE VALUES (StudentORAData('Zoe Nucci', 231))}; // Create a ORAData object StudentORAData stdobj = new StudentORAData("Bobo Lafleur", 564); // Insert the ORAData object System.out.println("Insert new student"); #sql { insert into Student_table values (:stdobj) }; // Retrieve the inserted object System.out.println("Rerieve the inserted student"); StudIter objiter; #sql objiter = { select studobj from Student_table }; while(objiter.next()) { stdobj = objiter.studobj(); System.out.println("Student Name: " + stdobj.studName + " student #: " + stdobj.studNo ); } objiter.close(); Oracle.close(); } } $ java -Doracle.net.tns_admin=$TNS_ADMIN myORAData Drop student table and type Create Student type and table Insert new student Rerieve the inserted student Student Name: Zoe Nucci student #: 231 Student Name: Bobo Lafleur student #: 564 $
As described in Chapter 8, SQLJ object types are specified by ANSI SQLJ Part II
and allow creating SQL types using Java classes that implement SQLData
or ORAData
interface. Similar to JDBC in Chapter 8, SQLJ applications may use SQLJ object types.
SQL REF types are explained in the corresponding section in Chapter 8. Similar to JDBC, SQLJ can automatically map SQL REF
column types to and from the standard java.sql.Ref
or to and from the oracle.sql.REF
wrapper. Alternatively, you may use the custom mapping approach using a class that implements ORAData.
The Oracle SQLJ supports using REF:
In iterator declaration:
#sql static iterator NamedRefIter (BigDecimal objid, REF objref ); #sql static iterator PosdRefIter (BigDecimal, REF );
In Query and DML statements:
static REF myRef = null; #sql { select t.objref into :myRef from ref_obj_tab t where t.objid = 3 }; #sql { insert into ref_obj_tab values(250, :myRef) }; NamedRefIter nriter; #sql iter = { select ref(t) objref from ref_obj_tab t }; myObject myobj; #sql { select DEREF(objref) into :myObj from ref_obj_tab where ...} ;
As IN, OUT,
and INOUT
parameter of a stored procedure/function:
#sql { call InsertObjRef(303, :IN myRef ) }; REF ref = myRef; boolean bool = true; #sql bool = { values(ObjRefFunc(:INOUT ref)) };
In PL/SQL blocks:
REF myRef = null; int objnum = 1; #sql { begin select * into :OUT myRef from ref_obj_tab p where p.objid = :IN objnum; end; };
For custom mapping of object references using ORAData
, the following requirements must be met:
The class has a String
constant _SQL_TYPECODE
initialized to OracleTypes.REF:
public static final int _SQL_TYPECODE = OracleTypes.REF;
The class has a String
constant, _SQL_BASETYPE
, initialized to the SQL name of the user-defined type being referenced:
public static final String _SQL_BASETYPE ="PERSON";
See Chapter 8 for more elaborate examples that you can easily implement using SQLJ.
REF cursors contain references to database cursors. Oracle SQLJ supports using REF CURSOR
types for mapping the following:
Result expressions for stored function returns
Output host expressions for stored procedure or function output parameters
Output host expressions in INTO
-lists
Output of PL/SQL anonymous blocks
Iterator columns: you can write a REF CURSOR
object to an iterator column or ResultSet
column in an iterator, or write a REF CURSOR
object to an iterator host variable or ResultSet
host variable in an INTO
-list
The following code fragment retrieves a REF CURSOR
from an anonymous PL/SQL block:
#sql { begin INSERT INTO emp (ename, empno) VALUES (:name, :num); OPEN :out emplist FOR SELECT ename, empno FROM emp ORDER BY empno; end };
Listing 12.5 illustrates how to use the SQL CURSOR
operator for a nested SELECT
within an outer SELECT
statement, to retrieve the department name of each employee.
Example 12.5. myRefCursor.sqlj
import java.sql.*; import sqlj.runtime.*; import java.sql.ResultSet; import sqlj.runtime.ref.DefaultContext; import oracle.jdbc.driver.*; import oracle.sqlj.runtime.*; public class myRefCursor { #sql static public iterator NameRefCursIter (String ename, ResultSet deptlis); public static void main(String[] args) { System.out.println("**** ResultSet as Ref Cursor"); try { Oracle.connect(basicFunc.class, "connect.properties"); NameRefCursIter nrciter; #sql nrciter = { select ename, CURSOR (select dname from dept where deptno = emp.deptno) as deptlis from emp }; System.out.println("Loop through the Iterators, print Ename and Dname"); while (nrciter.next ()) { ResultSet deptRSet = nrciter.deptlis(); if (deptRSet.next ()) { String deptName = deptRSet.getString (1); System.out.println("Name: " + nrciter.ename() + " Dept: " + deptName); } deptRSet.close(); } nrciter.close(); } catch (SQLException e) { e.printStackTrace(); System.out.println(e); } } } $ sqlj myRefCursor.sqlj $ java -Doracle.net.tns_admin=$TNS_ADMIN myRefCursor **** ResultSet as Ref Cursor Loop through the Iterators and print Ename and Dname Name: SMITH Dept: RESEARCH Name: ALLEN Dept: SALES Name: WARD Dept: SALES Name: JONES Dept: RESEARCH Name: MARTIN Dept: SALES Name: BLAKE Dept: SALES Name: CLARK Dept: ACCOUNTING Name: SCOTT Dept: RESEARCH Name: KING Dept: ACCOUNTING Name: TURNER Dept: SALES Name: ADAMS Dept: RESEARCH Name: JAMES Dept: SALES Name: FORD Dept: RESEARCH Name: MILLER Dept: ACCOUNTING $
See Chapter 3 for a Java stored procedure returning a REF cursor.
If you want to store Java objects “as is” and retrieve them later, you may use database RAW
or BLOB
columns along with typeMap
or ORAData
. You may also serialize and deserialize the Java objects on the fly (objects by value).
The Oracle-specific code generation allows mapping a serializable Java class to RAW
or BLOB
columns. It uses a nonstandard extension to typeMap facility or adds a typecode field to the serializable class, so that the instances of the serializable class can be stored as RAW
or BLOB
.
Declare a type-map in the connection context declaration and use this type-map to specify mappings:
#sql public static context SerConCtx with (typeMap="SerMap");
The type-map resource must provide nonstandard mappings from RAW
or BLOB
columns to the serializable Java classes:
oracle-class.java_class_name=JAVA_OBJECT RAW oracle-class.java_class_name=JAVA_OBJECT BLOB
Alternatively, you can use the public static final
field _SQL_TYPECODE
to specify the mapping:
public final static int _SQL_TYPECODE = oracle.jdbc.OracleTypes.RAW; public final static int _SQL_TYPECODE = oracle.jdbc.OracleTypes.BLOB;
You can also use ORAData
to define a serializable wrapper class whose instances can be stored in RAW
or BLOB
columns. Here are the steps (from the Oracle SQLJ documentation) for achieving such mapping:
Skeleton of the class:
public class SerializableDatum implements ORAData { // Client methods for constructing and accessing the Java // object public Datum toDatum(java.sql.Connection c) throws SQLException { // Implementation of toDatum() } public static ORADataFactory getORADataFactory() { return FACTORY; } private static final ORADataFactory FACTORY = // Implementation of an ORADataFactory for // SerializableDatum // Construction of SerializableDatum from oracle.sql.RAW public static final int _SQL_TYPECODE = OracleTypes.RAW; }
Define client methods that create a SerializableDatum
object, populate a SerializableDatum
object, and retrieve data from a SerializableDatum
object:
private Object m_data; public SerializableDatum() { m_data = null; } public void setData(Object data) { m_data = data; } public Object getData() { return m_data; }
Implement a toDatum()
method that serializes data from a SerializableDatum
object to an oracle.sql.RAW
object:
// Implementation of toDatum() try { ByteArrayOutputStream os = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(m_data); oos.close(); return new RAW(os.toByteArray()); } catch (Exception e) { throw new SQLException("SerializableDatum.toDatum: "+e.toString()); }
Implement data conversion from an oracle.sql.RAW
object to a SerializableDatum
object:
// Constructing SerializableDatum from oracle.sql.RAW private SerializableDatum(RAW raw) throws SQLException { try { InputStream rawStream = new ByteArrayInputStream(raw.getBytes()); ObjectInputStream is = new ObjectInputStream(rawStream); m_data = is.readObject(); is.close(); } catch (Exception e) { throw new SQLException("SerializableDatum.create: "+e.toString()); } }
Implement an ORADataFactory
:
// Implementation of an ORADataFactory for SerializableDatum new ORADataFactory() { public ORAData create(Datum d, int sqlCode) throws SQLException { if (sqlCode != _SQL_TYPECODE) { throw new SQLException ("SerializableDatum: invalid SQL type "+sqlCode); } return (d==null) ? null : new SerializableDatum((RAW)d); } };
The following uses a SerializableDatum
instance as a host variable:
... SerializableDatum pinfo = new SerializableDatum(); pinfo.setData ( new Object[] {"Some objects", new Integer(51), new Double(1234.27) } ); String pname = "MILLER"; #sql { INSERT INTO persondata VALUES(:pname, :pinfo) }; ...
The following code fragment uses SerializableDatum
as a named iterator column:
#sql iterator PersonIter (SerializableDatum info, String name); ... PersonIter pcur; #sql pcur = { SELECT * FROM persondata WHERE info IS NOT NULL }; while (pcur.next()) { System.out.println("Name:" + pcur.name() + " Info:" + pcur.info()); } pcur.close(); ...
The following code sample describes how to exchange serialized Java objects by value, between a client-side Java/SQLJ and server-side Java/SQLJ. The various RowSet models covered in Chapter 8 are probably more elegant approaches. The serialized Java object is passed as LONG RAW
arguments to stored procedure and function using the PL/SQL interface, which limits the size of LONG RAW
arguments to 32 K bytes. This example comprises the following files: EmpObjClient.sqlj, EmployeeObj.java, EmployeeObject.sqlj, Util.java,
and empobj.sql.
EmployeeObjects.sqlj:
Contains methods for passing the Java object, which is serialized and passed back and forth through SQL layer as LONG RAW
.
-byte[] getEmployeeObj (Integer empno):
Gets an employee and all its managers by calling getEmployee()
, then serializes and returns a Java object representing the employee and all its managers as a byte array (byte[]
).
-void updateEmployeeInfo (byte[] info):
Deserializes the array containing a serialized EmployeeObj
objects and calls updateEmployee
to update the EMP table to reflect the changes to the employee and managers.
The PL/SQL Wrappers (empobj.sql
): The getEmployeeObj
and updateEmployeeObj
methods are wrapped as follows:
- function getempobj (empno in number) return long raw as language java name 'EmployeeByValue.getEmployeeObj (java.lang.Integer) return byte[]'; - procedure updateempobj (info long raw) as language java name 'EmployeeByValue.updateEmployeeObj (byte[])';
EmpObjCLient.sqlj:
the client program:
Calls the PL/SQL wrappers for entry points in EmployeeObjects
to get the serialized EmployeeObj
.
Serializes it to get the Java object instances.
Updates the instances (raise to the salaries of employee and its manager; Note: it is not recursive).
Serializes the EmployeeObj
object and sends it back to the database for effective update.
Retrieves the employee and its hierarchy again and prints their number, name, and salary.
EmployeeObject.sqlj =================== /* * Server-side SQLJ providing entrypoint to get * EmployeeObj or EmployeeObj references */ import java.sql.*; import oracle.sql.RAW; #sql iterator EmpIter (String ename, Integer mgr, int sal); public class EmployeeObject { public static byte[] getEmployeeObj (Integer empno) throws Exception { EmployeeObj empobj = getEmployee (empno); return Util.serializeObject (empobj); } public static void updateEmployeeObj (byte[] obj) throws Exception { EmployeeObj empobj = (EmployeeObj)Util.deserializeObject (obj); updateEmployee (empobj); } private static EmployeeObj getEmployee (Integer empno) throws SQLException { if (empno == null) return null; else { EmpIter it; #sql it = {select ename, sal, mgr from emp where empno = :empno}; if (!it.next ()) return null; return new EmployeeObj (empno.intValue (), it.ename (), it.sal (), getEmployee (it.mgr ())); } } private static void updateEmployee (EmployeeObj empobj) throws SQLException { if (empobj != null) { updateEmployee (empobj.manager); if (empobj.manager != null) #sql {update emp set ename = :(empobj.ename), mgr = :(empobj.manager.empno), sal = :(empobj.salary) where empno = :(empobj.empno) }; else #sql {update emp set ename = :(empobj.ename), sal = :(empobj.salary) where empno = :(empobj.empno) }; } } } EmployeeObj.java ================ /* This Class represent a Java object passed by value between the Client and the Server. */ import java.io.Serializable; public class EmployeeObj implements Serializable { public int empno; public String ename; public int salary; public EmployeeObj manager; public EmployeeObj (int empno, String ename, int salary, EmployeeObj manager){ this.empno = empno; this.ename = ename; this.salary = salary; this.manager = manager; } } Util.java ====== /* Utility class for serializing / deserializing objects */ import java.io.*; public class Util { public static byte[] serializeObject (Object obj) throws IOException { if (obj == null) return null; ByteArrayOutputStream ostream = new ByteArrayOutputStream (); ObjectOutputStream p = new ObjectOutputStream (ostream); p.writeObject (obj); p.flush (); return ostream.toByteArray (); } public static Object deserializeObject (byte[] bytes) throws IOException, ClassNotFoundException { if (bytes == null) return null; InputStream istream = new ByteArrayInputStream (bytes); return new ObjectInputStream (istream).readObject (); } } $ loadjava -resolve -user scott/tiger EmployeeObj.java EmployeeObject.sqlj Util.java
You may ignore the warnings.
empobj.sql ========== create or replace package empobj as function getempobj (empno in number) return long raw; procedure updateempobj (obj long raw); end; / create or replace package body empobj as function getempobj (empno in number) return long raw as language java name 'EmployeeObject.getEmployeeObj(java.lang.Integer) return byte[]'; procedure updateempobj (obj long raw) as language java name 'EmployeeObject.updateEmployeeObj (byte[])'; end; / show errors; exit $ sqlplus scott/tiger @empobj SQL*Plus: Release 10.2.0.1.0 - Production on Sun Jan 15 15:32:57 2006 Copyright (c) 1982, 2005, Oracle. All rights reserved. Connected to: Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production With the Partitioning and Data Mining options Package created. Package body created. No errors. Disconnected from Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production With the Partitioning and Data Mining options $ EmpObjCLient.sqlj ================= import java.sql.*; import java.io.IOException; import sqlj.runtime.ref.DefaultContext; import sqlj.runtime.BinaryStream; import oracle.sqlj.runtime.*; public class EmpObjClient { public static void main (String args[]) throws Exception { Oracle.connect(EmpObjClient.class, "connect.properties"); System.out.println ("Getting employee number 7788"); EmployeeObj emp = getEmployee (7788); printEmp (emp); System.out.println ("Raising employee number 7788 and his manager by 10%"); emp.salary += emp.salary * 0.10; emp.manager.salary += emp.manager.salary * 0.10; updateEmployee (emp); System.out.println ("Getting employee number 7788 again"); emp = getEmployee (7788); printEmp (emp); } public static void printEmp (EmployeeObj emp) { if (emp != null) { System.out.println (emp.empno + ": " + emp.ename + " salary " + emp.salary); if (emp.manager != null) { System.out.print (" managed by "); printEmp (emp.manager); } } } public static EmployeeObj getEmployee (int number) throws SQLException, IOException, ClassNotFoundException { byte[] serial_empobj = null; #sql serial_empobj = { values (empobj.getempobj (:number)) }; return (EmployeeObj)Util.deserializeObject (serial_empobj); } public static void updateEmployee (EmployeeObj emp) throws SQLException, IOException { byte[] serial_empobj = Util.serializeObject (emp); #sql { call empobj.updateempobj (:serial_empobj) }; } } $ sqlj EmpObjCLient.sqlj $ java -Doracle.net.tns_admin=$TNS_ADMIN EmpObjClient Getting employee number 7788 7788: SCOTT salary 4350 managed by 7566: JONES salary 2975 managed by 7839: KING salary 5000 Raising employee number 7788 and his manager by 10% Getting employee number 7788 again 7788: SCOTT salary 4785 managed by 7566: JONES salary 3272 managed by 7839: KING salary 5000 $
SQL VARRAY
s are described in Chapter 8. Similar to JDBC, the Oracle SQLJ maps the SQL VARRAY
to the oracle.sql.ARRAY
wrapper class (which implements the standard java.sql.Array
interface) or to the custom wrapper class.
VARRAY can be used for host variable, iterators, parameters for stored procedures and functions, and within anonymous PL/SQL blocks.
Beginning with Oracle Database 10g, the Oracle SQLJ supports array types in iterator columns. In other words, you can declare an iterator that uses java.sql.Array
or oracle.sql.ARRAY
columns.
#sql public static iterator NamedArrayIter (BigDecimal order, ARRAY parts); #sql public static iterator PositArrayIter (BigDecimal, ARRAY); static ARRAY myArray = null; #sql { select t.parts into :(myArray) from VARRAY_tab t where t.order=203}; #sql { insert into VARRAY_tab (order, parts) values (310, :(myArray) )} ;
VARRAYs
can be used as IN
parameters for stored procedures.
#sql { call insertVARRAY1(204, :IN myArray ) };
SQL 2003 specifies getArray() on RTResultSet
object, and setArray() on RTStatement
for inserting and retrieving ARRAY/MULTISET
. However, as of Oracle Database 10g Release 2 (10.2), the Oracle SQLJ does not support getURL on RTResultSet
and setURL
on RTStatement
.
For custom mapping of user-defined collections such as VARRAYs using ORAData
, the following requirements must be met:
The custom collection has a String
constant, _SQL_TYPECODE
, initialized to OracleTypes.ARRAY:
public static final int _SQL_TYPECODE = OracleTypes.ARRAY;
The custom collection class must have the constant _SQL_NAME
initialized to the SQL name of the user-defined collection:
public static final String _SQL_NAME ="ADT_VARRAY";
Nested Tables are unordered and unbound user-defined collections. See Chapter 8 for a further description and examples of nested tables. Similar to VARRAY
, Nested Tables can be mapped to java.sql.Aray, oracle.sql.VARRAY,
and custom wrapper classes.
Nested Tables can be used in iterator declaration, IN
variables in select statement, IN and OUT
parameter for stored procedures, and return values of stored functions.
#sql public static iterator NamedArrayIter(BigDecimal order, NTab_parts parts); #sql public static iterator PositionedArrayIter(BigDecimal, NTab_parts ); NTab_parts myNTab = new NTab_parts(); #sql {select t.parts into :(myNTab) from NTAb_parts_tab t where t.order=101}; NTab_parts yourNTab = new new NTab_parts(); #sql { select :myNTab into :yourNTab from dual };
Chapter 8 describes PL/SQL associative arrays. The following array types are supported in SQLJ:
Numeric types: int[]
, long[]
, float[]
, double[]
, short[]
, java.math.BigDecimal[]
, oracle.sql.NUMBER[]
Character types: java.lang.String[]
, oracle.sql.CHAR[]
The following code fragment writes indexed-by table data to the database:
int[] vals = {1,2,3}; #sql { call procin(:vals) };
The following code fragment retrieves indexed-by table data from the database:
oracle.sql.CHAR[] outvals; #sql { call procout(:OUT outvals/*[111](22)*/) };
The maximum length of the output array being retrieved is specified using the [xxx]
syntax inside the /*...*/
syntax, as shown. Also, you can optionally specify the maximum length of an array element in bytes using the (xx)
syntax, as shown.
RTResultSet
and RTStatement
do not support getURL
and setURL
methods. SQL 2003 introduced the DATALINK
data type. Variables of DATALINK
type can be retrieved from the database using the getURL
method on the RTResultSet
, and can be set using the setURL on RTStatement
.
Like the Oracle JDBC, the Oracle SQLJ does not support calling arguments or return values of the PL/SQL BOOLEAN
type or RECORD types.
As a workaround for an unsupported type, you can create wrapper procedures that process the data using supported types. See conversion technique in JPublisher (covered later).
You can wrap a stored procedure that uses PL/SQL Boolean values, and you can create a stored procedure that takes a character or number from JDBC and passes it to the original procedure as BOOLEAN
, or for an output parameter, accepts a BOOLEAN
argument from the original procedure and passes it as a CHAR
or NUMBER
to JDBC.
The following code fragment utilizes a PL/SQL wrapper procedure, WRAP_BP
, for a stored procedure, P
, that takes a BOOLEAN
as input:
PROCEDURE WRAP_BP (n NUMBER) IS BEGIN IF n=0 THEN BP(false); ELSE BP(true); END IF; END; PROCEDURE BP (b BOOLEAN) IS BEGIN ... END;
Similarly, to wrap a stored procedure that uses PL/SQL records, you can create a stored procedure that handles scalar components of the record, such as CHAR
and NUMBER
.
To wrap a stored procedure that uses PL/SQL TABLE types, you can break the data into components or perhaps use Oracle collection types.
The setFetchSize()
method of an ExecutionContext
instance sets the number of rows to be prefetched during SELECT
statement, resulting in fewer roundtrips.
DefaultContext.getDefaultContext().getExecutionContext().setF etchSize(10);
Conversely, the getFetchSize()
method of an ExecutionContext
instance returns the current prefetch size as an int
value.
SQLJ furnishes two statement caching mechanisms:
For Oracle-specific code generation, SQLJ statement caching uses the JDBC explicit caching mechanism controlled through connection methods and covered in Chapter 11 (statement caching). As of Oracle Database 10g Release 2, the statement cache size defaults to 5.
The following methods of OracleConnection
are available to SQLJ connection context instance (see Chapter 7 for more details):
void setExplicitCachingEnabled(boolean) boolean getExplicitCachingEnabled() void setImplicitCachingEnabled(boolean) boolean getImplicitCachingEnabled()
For SQLJ ISO code generation, SQLJ uses its own caching, which is controlled by the Oracle customizer stmtcache
option.
The Oracle Database 10g JDBC implements the sqlj.runtime.profile.ref.ClientDataSupport
interface, resulting in a per-connection statement cache, which is shared by all instances of a connection context class that share the same underlying connection.
You can alter the statement cache size through the Oracle customizer stmtcache
option:
-P-Cstmtcache=n (integer n)
JDBC update batching or DML batching is described in Chapter 7. In SQLJ, update batching is related to the execution context.
The setBatching()
method of the execution context enables/disables update batching:
ExecutionContext exCtx = new ExecutionContext(); exCtx.setBatching(true); // Enabled ... exCtx.setBatching(false); // Disabled – the default
The isBatching()
method of an execution context instance determines if update batching is enabled or not:
boolean batching = exCtx.isBatching();
Chapter 7 describes and discusses implicit versus explicit update batching. The executeBatch()
method explicitly executes an update batch:
int[] updateCounts = exCtx.executeBatch();
The getBatchUpdateCounts()
method of an execution context returns the update count array for an implicily executed batch:
int[] updateCounts = exCtx.getBatchUpdateCounts();
The cancel()
method of the execution context instance cancels a pending batch:
if (...)) { exCtx.cancel(); throw new SQLException("Batch canceled."); }
This concludes the coverage of SQLJ as a simpler alternative to JDBC. Our next topic is JPublisher, which can be viewed as the Swiss Army knife for manipulating Oracle SQL data types. JPublisher complements and simplifies SQLJ, JDBC, and also enables database Web services.
18.223.171.51