Chapter 12. SQLJ Data Access and Best Practices

In this chapter, we’ll look at Oracle SQL data manipulation using SQLJ and then best practices with SQLJ.

Manipulating Oracle SQL and PL/SQL Data Types 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.

Oracle SQLJ Type-Mapping Summary

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

CHAR, CHARACTER, LONG, STRING, VARCHAR, VARCHAR2

java.lang.String,java.sql.Date,

java.sql.Time,java.sql.Times

tamp, java.lang.Byte,

java.lang.Short,

java.lang.Integer,

java.lang.Long,

java.sql.Float,

java.lang.Double,

java.math.BigDecimal, byte,

short, int, long, float,

double

oracle.sql.CHAR

Globalization LONG, NCHAR, NVARCHAR2

n/a

oracle.sql.NCHAR (see note 1)

oracle.sqlj.runtime.Nchar CharacterStream

oracle.sqlj.runtime.Nchar AsciiStream (Deprecated; use NcharCharacterStream)

oracle.sqlj.runtime.Nchar UnicodeStream (Deprecated; use NcharCharacterStream)

NUMBER, DEC, DECIMAL, NUMERIC

java.lang.Byte,

java.lang.Short,

oracle.sql.NUMBER

DOUBLE PRECISION, FLOAT

double, java.lang.Double

oracle.sql.NUMBER

SMALLINT

short, int, java.lang.Integer

oracle.sql.NUMBER

REAL

float, java.sql.Float

oracle.sql.NUMBER

DATE

java.sql.Date,

java.sql.Time,

java.sql.Timestamp,

java.lang.String

oracle.sql.DATE

TIMESTAMP

TIMESTAMP WITH TZ

TIMESTAMP WITH

LOCAL TZ

java.sql.Date,

java.sql.Time,

java.sql.Timestamp, byte[]

oracle.sql.DATE, oracle.sql.TIMESTAMP

oracle.sql.TIMESTAMPTZ

oracle.sql.TIMESTAMPLTZ

RAW, LONG RAW

byte[]

oracle.sql.RAW

INTERVAL YEAR TO MONTH

INTERVAL DAY TO SECOND

String (see note 2)

oracle.sql.INTERVALDS oracle.sql.INTERVALYM

(see note 2)

URITYPE

DBURITYPE

XDBURITYPE

HTTPURITYPE

java.net.URL (see note 3)

 

ROWID

java.sql.String

oracle.sql.ROWID, oracle.sql.CHAR

BOOLEAN

boolean (see note 4)

boolean (see note 4)

CLOB LOCATOR

java.sql.Clob

oracle.sql.CLOB

BLOB LOCATOR

java.sql.Blob

oracle.sql.BLOB

BFILE LOCATOR

n/a

oracle.sql.BFILE

NCLOB LOCATOR

n/a

oracle.sql.NCLOB (note 1)

SQLJ Streams

  

LONG RAW

sqlj.runtime.BinaryStream

 

LONG

sqlj.runtime.CharacterStream

sqlj.runtime.AsciiStream

(Deprecated; use CharacterStream.)

sqlj.runtime.UnicodeStream

(Deprecated; use CharacterStream.)

 
   

User-defined objects types

java.sql.Struct, java.sql.SqlData

oracle.sql.STRUCT, oracle.sql.ORAData

User-defined collection

java.sql.Array

oracle.sql.ARRAY, oracle.sql.ORAData

OPAQUE types

Generated or predefined class (note 5)

oracle.sql.OPAQUE

RECORD types

Through mapping to SQL object type (note 5)

Through mapping to SQL object type (note 5)

Nested table, VARRAY

java.sql.Array

oracle.sql.ARRAY, oracle.sql.ORAData

Reference to SQL object type

java.sql.Ref

oracle.sql.REF, oracle.sql.SQLRef, oracle.sql.ORAData

REF CURSOR

java.sql.ResultSet

oracle.jdbc.OracleResultSet

Indexed-by tables

Through mapping to SQL collection (see note 2)

Through mapping to SQ collection (see note 2)

Scalar Indexed-by tables (numeric or character)

Through mapping to java array (see note 2)

Through mapping to java array (see note 2)

User-defined subtypes

Same as base type

Same as base type

Query Results Objects CURSOR

SQLJ iterator objects

 

Notes:

  1. 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.

  2. See the JPublisher Type Map in Part III of this book.

  3. See JPublisher: SQL URI types, also known as data links, are mapped to java.net.URL.

  4. Mapping of PL/SQL BOOLEAN to SQL NUMBER and Java Boolean is defined in the default JPublisher type-map.

  5. 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>

Column Definitions

The Oracle SQLJ implementation leverages Oracle JDBC support for column type and size definitions.

Automatic Registration of Column Types and Sizes

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.

Enabling Column Definition

  • 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.

Manipulating SQL Null Data with SQLJ

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!
$

Manipulating Character Data Types with SQLJ

CHAR, CHAR(n), CHAR(n BYTE), CHAR(n CHAR) VARCHAR2 (n), VARCHAR2(n BYTE), VARCHAR2 (n CHAR)

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.

NCHAR, NCHAR(n), NVARCHAR2 (n)

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)};
...

Oracle SQLJ Support for Number Data Types

NUMBER, NUMBER(p), NUMBER(p, s)

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' };

BINARY_FLOAT and BINARY_DOUBLE

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)};

SQLJ Streams, LONG, and RAW Data Types

SQL RAW(s)

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};

LONG, LONG RAW

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 Streams

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:

  • int read ():

    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.

Using SQLJ Streams to Send and Retrieve Data to and from the Database

The following code sample illustrates binary SQLJ stream manipulation and performs the following operations:

  1. 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.

  2. 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
    $

SQLJ Stream Objects as Procedures Output Parameters and Function Return Values

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);

SQLJ Support for SQL Datetime Data Types

DATE

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)};

TIMESTAMP, TIMESTAMPTZ, and TIMESTAMPLTZ

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))};

INTERVAL YEAR TO MONTH and INTERVAL DAY TO SECOND

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) };

SQLJ Support for SQL LOB Data Types

Overview

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);

Using the DBMS_LOB Package

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.

  1. Declare iterators:

    #sql public static iterator NLOBIter (String vc2, BLOB
    bcol, CLOB ccol);
    #sql public static iterator PLOBIter (String, BLOB,
    CLOB);
  2. Declare LOB variable:

    BLOB blobLoc = null;
  3. 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())};
  4. 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] + " ");
            }
         }
  5. 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;
         }
  6. 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 ;

Using Standard JDBC LOB API and oracle.sql LOB Extensions

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.

  1. 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 };
  2. 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();
  3. 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;
     }
  4. Close resources and commit changes:

Using SQLJ Streaming

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.

LOB and BFILE Stored Function Results

BLOB, CLOB, and BFILE host variables can be assigned the result of a stored function call.

CLOB clobLoc;
#sql clobLoc = { VALUES(return_clob(...)) };

LOB and BFILE Host Variables ( SELECT INTO Targets)

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) };
...

LOBs and BFILEs in Iterator Declarations

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);

LOB and BFILE HostVariables as Named Iterator

#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();
//

LOB and BFILE Host Variables as Positional Iterator (FETCH INTO Targets)

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
}

SQLJ Support for Oracle SQL ROWID

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.

SQLJ Support for OPAQUE Types

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).

XMLType

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.

SQLJ Support for SQL Object Types and SQL References Types

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).

Type Map

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.

Note

The public static final field _SQL_TYPECODE can be used as an alternative way to specify the mapping, as follows:

public static final int _SQL_TYPECODE = OracleTypes.STRUCT;

Weak Type Mapping: Struct and STRUCT

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 );

    Note

    Attributes in a STRUCT object do not have names.

  • 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

Strong Type Mapping: SQLData and ORAData

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);

    Reminder

    Oracle-specific code generation requires the same type-map for the iterator and the connection context.

  • 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
$

SQLJ Object Types

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

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

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.

Serialized Java Objects

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).

Serializing Java Objects Using typeMAP

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;

Serializing Java Objects Using ORAData

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:

  1. 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;
    }
  2. 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;
    }
  3. 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()); }
  4. 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()); }
    }
  5. 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();
...

Serializing Java Objects by Value

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
$

SQLJ Support for User-Defined SQL Collections

VARRAYs

SQL VARRAYs 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

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 };

PL/SQL Associative Array

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.

Unsupported Types

DATALINK

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.

Named Parameters

JDBC 3.0 named parameters are not currently supported by Oracle SQLJ.

PL/SQL BOOLEAN and PL/SQL RECORD

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.

SQLJ Best Practices

Row Prefetch

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.

Statement Caching

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)

A cache size of 0 disables statement caching.

Update Batching

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.");
    }

Conclusion

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.



[1] For codegen=iso, this option is passed to the Oracle SQLJ run time, which internally uses SetFormOfUse at run time.

[2] First public draft specification available at the time of this writing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.171.51