Chapter 8. SQL Data Access and Manipulation

In the previous chapter, we covered the essentials of JDBC programming, including connect string URL, data sources, connections, and statements. In this chapter, we’ll look at the JDBC API for accessing and manipulating SQL data, including the key metadata, the built-in SQL data types, LOBs, user-defined collections, Oracle object data types, XML type, Result Sets (scrollable, updatable), and rowsets (JdbcRowSet, CachedRowSet, WebRowSet, JoinRowSet and FilteredRowSet).

Key Metadata in JDBC

JDBC metadata is an umbrella name for a set of metadata objects that provide information about the database entities, including the database itself (DatabaseMetaData); the parameters of a PreparedStatement (ParamaterMetaData); the ResultSet (ResultSetMetaData); the number, name, and type of each column; and so on. Let’s look at the main standard JDBC metadata as well as Oracle extensions.

DatabaseMetaData: OracleDatabaseMetaData

The DatabaseMetaData object contains more than 100 methods, which allow you to dynamically (i.e., at runtime) find out the environment of the database your JDBC application is running against, as well as the supported features. The oracle.jdbc.OracleDatabaseMetaData class implements the standard java.sql.DatabaseMetaData interface. You need first to get the DatabaseMetaData object from the connection object and then invoke its methods. The method names are self-explanatory; each method returns either a metadata or whether or not the database supports such feature/capability.

DatabaseMetaData dbmd = conn.getMetaData ();

System.out.println("Database Name? " +
                      dbmd.getDatabaseProductName() );

System.out.println("Database Version? " +
                      dbmd.getDatabaseProductVersion() );

System.out.println("Default Transaction Isolation?  " +
                      dbmd.getDefaultTransactionIsolation() );

System.out.println("Supports Savepoints?  " +
                     dbmd.supportsSavepoints());

System.out.println("Driver Name? " +
                      dbmd.getDriverName() );
System.out.println("Driver Major Version? " +
                       dbmd.getDriverMajorVersion() );
System.out.println("DriverVersion? " +
                     dbmd.getDriverVersion() );
System.out.println("Driver Minor Version? " +
                     dbmd.getDriverMinorVersion() );
...

See the java.sql.DatabaseMetaData javadoc for a complete list of standard methods.

In addition, this class also furnishes Oracle’s JDBC specific extensions; a few of these are highlighted as follows:

OracleDatabaseMetaData odbmd = new
oracle.jdbc.OracleDatabaseMetaData((OracleConnection)conn);

System.out.println("Supports Statement Pooling() = " +
                     odbmd.supportsStatementPooling());
System.out.println ("locatorsUpdateCopy() = " +
                     odbmd.locatorsUpdateCopy());
System.out.println("Supports Auto GeneratedKeys is : " +
                     odbmd.supportsGetGeneratedKeys());
...

See the oracle.jdbc.OracleDatabaseMetaData javadoc for a complete list of methods.

ResultSetMetaData: OracleResultSetMetaData

The Result Set metadata interface allows you to dynamically (i.e., at runtime) find the number of columns in a result set (covered later), the table’s catalog name, the name of each column, its SQL type, whether it is NULLable, and so on. The standard java.sql.ResultSetMetaData interface furnishes the following methods: getCatalogName, getColumnClassName, getColumnCount, getColumnDisplaySize, getColumnLabel, getColumnName, getColumnType, getColumnTypeName, getPrecision, getScale, getSchemaName, getTableName, isAutoIncrement, isCaseSensitive, isCurrency, isDefinitelyWritable, isNullable, isReadOnly, isSearchable, isSigned, isWritable.

The oracle.jdbc.OracleResultSetMetaData interface extends the standard java.sql.ResultSetMetaData interface and furnishes the isNCHAR method, which returns true if the column is of type NCHAR/NVARCHAR/ NCLOB, and false otherwise.

The Oracle JDBC implements the OracleResultSetMetaData interface but the getSchemaName() and getTableName() methods. The ResultSetMetaData object is returned upon the invocation of the getMetaData() method on a ResultSet object, and then its methods can be invoked as follows:

pstmt =
    conn.prepareStatement("SELECT ename, empid, job FROM
emp");
rs = (OracleResultSet)pstmt.executeQuery ();

ResultSetMetaData rsmd = rs.getMetaData ();

rsmtdt.getColumnCount();
rsmtdt.getColumnType (i);
rsmd.getColumnTypeName (i);
rsmtdt.getPrecision(i);
...
rsmtdt.isAutoIncrement(i);
rsmtdt.isNullable(i);

Listing 8.1 retrieves the ResultSetMetaData object and then prints the total column count, and for each column prints the column name, the column type, and the column value (with provision for dealing with NCHAR, NVARCHAR, and NCLOB data types).

Example 8.1. Result7MtDt.java

import java.sql.*;
import oracle.jdbc.*;
import oracle.sql.*;
import oracle.jdbc.pool.OracleDataSource;
import java.io.*;

public class Result7MtDt
{
  public static void main (String args [])throws SQLException
  {
    try
    {
    // Create an OracleDataSource
    OracleDataSource ods = new OracleDataSource();
    // Set the URL (user name, and password)
    String url =
             "jdbc:oracle:thin:scott/tiger@//localhost:1521/orcl";
    ods.setURL(url);

    // Retrieve a connection
    Connection conn = ods.getConnection();
    // Create a Statement
    Statement stmt = conn.createStatement();
    // Get a Result Set object
    ResultSet rs =
        stmt.executeQuery("select * from EMP Where ename = 'SMITH'");
    while(rs.next()){
      ResultSetMetaData mtdt = rs.getMetaData ();
     int count = mtdt.getColumnCount ();
     System.out.println("Column Count = " + count);
     for (int i=1; i<=count; i++){
             String colName = mtdt.getColumnName(i);
          String colType = mtdt.getColumnTypeName(i);
          System.out.println("*** Col. name = " + colName +
                                  ", col type = " + colType);
          printCol(mtdt,rs,i);
          }
         }

     rs.close();
     stmt.close();
     conn.close();
    } catch (SQLException sqle) {
   System.out.println(sqle.getMessage());
   sqle.printStackTrace();
    }
 }// end main

 private static void
           printCol(ResultSetMetaData mtdt, ResultSet rs, int col)
 {
  try
  {
   String str = rs.getString(col);
   if(!((OracleResultSetMetaData)mtdt).isNCHAR(col))
   {
     //The column is not of type NCHAR/NVARCHAR/NCLOB
     System.out.println("Col. length = "+ str.length() +
                           ",rs.getString(" + col +")=" + str);
    }
    else
    {
      //The column is of type NCHAR/NVARCHAR/NCLOB
      char[] bytarr = str.toCharArray();
      if (bytarr == null)
        System.out.println("<Null char array!>");
      else
      {
       int n = bytarr.length;
         for (int i = 0; i < n; i++)
         System.out.println("\u" +
                       Integer.toHexString(bytarr [i] & 0xFFFF));
     }
    }
 } catch (Exception e) {} //Note NULL column exception ignored
} //end printCol

} // end Result7MtDt

C:>javac Result7MtDt.java

C:>Java Result7MtDt

Column Count = 8
*** Col. name = EMPNO, col type = NUMBER
Col. length = 4,rs.getString(1)=7369
*** Col. name = ENAME, col type = VARCHAR2
Col. length = 5,rs.getString(2)=SMITH
*** Col. name = JOB, col type = VARCHAR2
Col. length = 5,rs.getString(3)=CLERK
*** Col. name = MGR, col type = NUMBER
Col. length = 4,rs.getString(4)=7902
*** Col. name = HIREDATE, col type = DATE
Col. length = 21,rs.getString(5)=1980-12-17 00:00:00.0
*** Col. name = SAL, col type = NUMBER
Col. length = 3,rs.getString(6)=800
*** Col. name = COMM, col type = NUMBER
*** Col. name = DEPTNO, col type = NUMBER
Col. length = 2,rs.getString(8)=20

ParameterMetaData

ParameterMetaData is a new interface in JDBC 3.0, which allows you to find the types and properties of the parameters in a PreparedStatement object. In Oracle Database 10g Release 2, Oracle Database 10g Release 10.1.0.3, and Oracle 9i Release 9.2.0.6 the JDBC drivers furnish a limited implementation of the standard java.sql.ParameterMetaData interface for JDK 1.4.x and above; only getParameterCount() is supported, while most of the other methods, such as getParameterType() and getParameterClassName(), currently throw an “Unsupported Operation” exception. An application running on a pre-JDK 1.4 JVM must use the oracle.jdbc.OracleParameterMetaData.interface, which is functionally identical to the java.sql.ParameterMetaData.

Listing 8.2 gets a PreparedStatement object, then a ParameterMetadata from it, and finally invokes the getParameterCount() method on the ParameterMetaData object.

Example 8.2. ParamMtDt.java

import java.sql.*;
import oracle.jdbc.*;
import oracle.sql.*;
import oracle.jdbc.pool.OracleDataSource;
import java.io.*;
/*
 * 1 get the PreparedStatement object
 * 2 get the getParameterMetaData
 * 3 Invoke getParameterCount() on the parametermetadata
 *
 */

public class ParamMtDt
{
  public static void main (String args [])throws SQLException
  {
   ParameterMetaData pmd=null;
   ResultSetMetaData rsmd=null;
   String stmt = "insert into EMP values (?,?,?,?,?,?,?,?)";
   // Create an OracleDataSource
   OracleDataSource ods = new OracleDataSource();
   // Set the URL (user name, and password)
   // String url = "jdbc:oracle:thin:scott/tiger@//localhost:5521/
orcl";
   String url = "jdbc:oracle:thin:scott/tiger@tns_alias";

   ods.setURL(url);
   // Retrieve a connection
        Connection conn = ods.getConnection();
   // get the PreparedStatement objects
   PreparedStatement pstmt = conn.prepareStatement(stmt);
   try
   {
      pmd = pstmt.getParameterMetaData(); // get ParameterMetaData
     // get the Number of Parameters
     System.out.println(" Number of Parameters: " +
        pmd.getParameterCount());
  } catch(SQLException sqle){
       System.out.println("SQL Exception" + sqle.getMessage());
       sqle.printStackTrace();
  }

  pstmt.close();

 }
}

$ javac ParamMtDt.java
$ java -Doracle.net.tns_admin=$TNS_ADMIN ParamMtDt

Number of Parameters: 8

StructMetaData

The java.sql.Struct and oracle.sql.STRUCT are covered later in section 8.2. The oracle.jdbc.StructMetaData interface lets you get the metadata of structured column types (i.e., user-defined types) through the following methods:

  • getAttributeJavaName(int column): Gets a JAVA_STRUCT attribute’s external name

  • getOracleColumnClassName(int column): Returns the fully qualified name of the datum class

  • isInherited(int column): Indicates whether the attribute is inherited from its supertype

  • getLocalColumnCount(): Gets the number of local attributes (i.e., not inherited from supertype)

    In addition, StructMetaData inherits the following methods from the java.sql.ResultSetMetaData interface: getCatalogName, getColumnClassName, getColumnCount, getColumnDisplaySize, getColumnLabel, getColumnName, getColumnType, getColumnTypeName, getPrecision, getScale, getSchemaName, getTableName, isAutoIn- crement, isCaseSensitive, isCurrency, isDefinitelyWritable, isNullable, isReadOnly, isSearchable, isSigned, isWritable.

It also inherits the isNCHAR() method from the oracle.jdbc.OracleResultSetMetaData interface.

Assume a user-defined type STUDENT:

create type student as object (id number, name varchar2(20),
height float, dob date, picture BLOB, address1 address_t,
address2 ref address_t)

A JDBC program can retrieve the following metadata:

StructDescriptor strdesc =
StructDescriptor.createDescriptor("STUDENT", conn);
StructMetaData strmd = (StructMetaData)strdesc.getMetaData();

int count = strmd.getLocalColumnCount();
for(int i = 1; i < count; i++) {
  strmd.getColumnType (i);
  strmd.getColumnTypeName (i);
  strmd.getOracleColumnClassName (i);
  strmd.isSearchable (i);
}

Manipulating Oracle Data Types with JDBC

This section discusses the methods for storing and persisting Java types, standard JDBC types, Oracle JDBC types, as well as methods for manipulating SQL types, PL/SQL types, user-defined object types, user-defined collections, and references in Java.

The oracle.sql.* package contains the oracle.sql.XXX wrapper classes (i.e., the oracle.sql.CHAR wraps the SQL CHAR type). The oracle.jdbc.* package contains the OracleStatement, OraclePreparedStatement, OracleCallableStatement, and OracleResultSet classes, which furnish the corresponding getXXX(), setXXX(), and updateXXX(), as well as the standard setxxx(), getxxx(), and updatexxx() methods defined in the standard Statement, PreparedStatement, CallableStatement, and ResultSet interfaces. Underneath the API, as shown in Figure 8.1, the Oracle JDBC drivers use the following internal conversion mechanisms for storing Java data and retrieving SQL data:

  • A set of binders for converting[1] the Java parameters into SQL types. Underneath the setXXX and updateXXX methods, these binders store the target data type and the payload (i.e., the UCS2 bytes) into staging JDBC buffers; then upon the execution of the statement, the staging buffer is sent to the server (once for all parameters). The operation succeeds if the server can convert the payload into the target data type, but otherwise it fails.

  • A set of accessors for converting the SQL types into Java/JDBC types. Underneath the getXXX methods, these accessors know how to turn Java byte arrays (i.e., bytes[]) and/or Java char arrays (i.e., char []) into values.

  • A set of character set converters for converting UCS2 bytes (the standard Java character set) into the database character set; for JDBC-OCI, this step is perfomed in the OCI layer.

  • For data that can be streamed over the wire, such as LOBs, LONG, and LONG RAW types, the stream binding approach bypasses the JDBC staging buffers.

Conversion Mechanisms

Figure 8.1. Conversion Mechanisms

In 10g, these conversion functions have been rewritten (as part of the entire rearchitecture of the drivers) and account largely for the huge performance improvement.

Example:

Assume a table T1 (x number, y varchar2[120], z clob), and the following prepared statement:

pstmt = conn.prepareStatement( "insert into T1 values( ?, ?, ?
)" );
The following setXXX methods perform  the binding as described
above.
pstmt.setInt( 1, 123 );
pstmt.setString( 2, "abcdefg" );
pstmt.setString( 3, "wxyz" );
pstmt.execute();

This code snippet utilizes direct binding, described in section 7.3.

The oracle.sql package contains the following Java classes that wrap the Oracle database SQL data types: NUMBER, CHAR, DATE, TIMESTAMP, TIMESTAMPTZ, TIMESTAMPLTZ, STRUCT, REF, ARRAY, BLOB, CLOB, BFILE, RAW, ROWID, and OPAQUE.[2]

These wrapper classes have the following features in common:

  • To be subclasses of the oracle.sql.Datum class and inherit the following methods: asciiStreamValue, bigDecimalValue, binaryStreamValue, booleanValue, byteValue, characterStreamValue, dateValue, doubleValue, equals, floatValue, getBytes, getLength, getStream, intValue, longValue, setBytes, setShareBytes, shareBytes, stringValue, timestampValue, timeValue, etc.

  • A set of constructors (not for all types) that take either the raw bytes or a Java type as input; the getBytes method returns the raw bytes as received from the database.

  • Methods to convert SQL data into Java bytes (UCS2 character set)

  • Direct access to the data in SQL format, which avoids the conversion in Java format

  • Math operations on the data in SQL format, which avoids the loss of precision that can occur during the conversion between Java and SQL formats

  • To implement the java.sql.Struct interface (i.e., getAttributes(), getSQLTypeName() methods)

Table 3.2 (duplicated here) illustrates the mapping between SQL or PL/ SQL types and standard Java types or Oracle JDBC types.

SQL Types, and PL/SQL Types

Oracle JDBC Mapping[*]

Standard Java Mapping

CHAR, CHARACTER, LONG, STRING, VARCHAR, VARCHAR2

Oracle.sql.CHAR

java.lang.String,

java.sql.Date,

java.sql.Time,java.sql

.Timestamp,

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

NUMBER

Oracle.sql.NUMBER

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

DATE

Oracle.sql.DATE

java.sql.Date,

java.sql.Time,

java.sql.Timestamp,jav

a.lang.String

NCHAR, NVARCHAR2

Oracle.sql.NCHAR (note 1)

n/a

RAW, LONG RAW

oracle.sql.RAW

byte[]

BINARY_INTEGER,

NATURAL, NATURALN,

PLS_INTEGER, POSITIVE,

POSITIVEN, SIGNTYPE,

INT, INTEGER

oracle.sql.NUMBER

int, java.lang.Integer

DEC, DECIMAL, NUMBER,

NUMERIC

oracle.sql.NUMBER

java.math.BigDecimal

DOUBLE PRECISION,

FLOAT

oracle.sql.NUMBER

Double,

java.lang.Double

SMALLINT

oracle.sql.NUMBER

short, Int

REAL

oracle.sql.NUMBER

Float, Float

TIMESTAMP

TIMESTAMP WITH TZ

TIMESTAMP WITH LOCAL

TZ

oracle.sql.DATE,

oracle.sql.TIMESTAMP

oracle.sql.TIMESTAMPTZ

oracle.sql.TIMESTAMPLTZ

java.sql.Date,

java.sql.Time,

java.sql.Timestamp,

byte[]

INTERVAL YEAR TO MONTH

INTERVAL DAY TO SECOND

String (note 2)

oracle.sql.INTERVALDS

oracle.sql.INTERVALYM

String (note 2)

URITYPE

DBURITYPE

XDBURITYPE

HTTPURITYPE

java.net.URL (note 3)

java.net.URL (note 3)

ROWID

oracle.sql.ROWID,

oracle.sql.CHAR

java.sql.String

BOOLEAN

boolean (note 4)

boolean (note 4)

CLOB LOCATOR

oracle.sql.CLOB

java.sql.Clob

BLOB LOCATOR

oracle.sql.BLOB

java.sql.Blob

BFILE LOCATOR

oracle.sql.BFILE

n/a

NCLOB LOCATOR

oracle.sql.NCLOB (note 1)

n/a

User-defined objects types

oracle.sql.STRUCT, oracle.sql.ORAData

java.sql.Struct, java.sql.SqlData

User-defined collection

oracle.sql.ARRAY, oracle.sql.ORAData

java.sql.Array

OPAQUE types

oracle.sql.OPAQUE

Generated or predefined class (note 5)

RECORD types

Through mapping to SQL object type (note 5)

Through mapping to SQL object type (note 5)

Nested table, VARRAY

oracle.sql.ARRAY, oracle.sql.ORAData

java.sql.Array

Reference to SQL object type

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

java.sql.Ref

REF CURSOR

oracle.jdbc.OracleResultSet

java.sql.ResultSet

Indexed-by tables

Through mapping to SQL collection (note 6)

Through mapping to SQL collection (note 6)

Scalar Indexed-by tables (numeric or character)

Through mapping to java array (note 7)

Through mapping to java array (note 7)

User-defined subtypes

Same as base type

Same as base type

[*] The oracle.sql.* datatypes let you store and retrieve data without losing information/precision.

  1. oracle.sql.NCHAR, oracle.sql.NCLOB, and oracle.sql.NString are not part of JDBC but are distributed with the JPublisher runtime (see Part IV) to represent the NCHAR form of oracle.sql.CHAR, oracle.sql.CLOB, and java.lang.String.

  2. See JPublisher “JPublisher User Type Map and Default Type Map” in Part IV.

  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.

  6. See JPublisher “Type Mapping Support for PL/SQL RECORD and Indexed-by Table Types” in Part IV of this book.

  7. See JPublisher “Type Mapping Support for Scalar Indexed-by Tables” in Part IV of this book.

Tables 8.1 and 8.2 summarize the getXXX(), getxxx(), setXXX(), and setxxx() methods.

Table 8.1. Return Types of getXXX Methods

Method

Return Type (method signature)

Type of Returned Object

getArray

java.sql.Array

oracle.sql.ARRAY

getARRAY

oracle.sql.ARRAY

oracle.sql.ARRAY

getAsciiStream

java.io.InputStream

java.io.InputStream

getBfile

oracle.sql.BFILE

oracle.sql.BFILE

getBFILE

oracle.sql.BFILE

oracle.sql.BFILE

getBigDecimal

java.math.BigDecimal

java.math.BigDecimal

getBinaryStream

java.io.InputStream

java.io.InputStream

getBlob

java.sql.Blob

oracle.sql.BLOB

getBLOB

oracle.sql.BLOB

oracle.sql.BLOB

getBoolean

boolean

boolean

getByte

byte

byte

getBytes

byte[]

byte[]

getCHAR

oracle.sql.CHAR

oracle.sql.CHAR

getCharacterStream

java.io.Reader

java.io.Reader

getClob

java.sql.Clob

oracle.sql.CLOB

getCLOB

oracle.sql.CLOB

oracle.sql.CLOB

getDate

java.sql.Date

java.sql.Date

getDATE

oracle.sql.DATE

oracle.sql.DATE

getDouble

double

double

getFloat

float

float

getInt

int

int

getINTERVALDS

oracle.sql.INTERVALDS

oracle.sql.INTERVALDS

getINTERVALYM

oracle.sql.INTERVALYM

oracle.sql.INTERVALYM

getLong

long

long

getNUMBER

oracle.sql.NUMBER

oracle.sql.NUMBER

getOracleObject

oracle.sql.Datum

subclasses of oracle.sql.Datum

getRAW

oracle.sql.RAW

oracle.sql.RAW

getRef

java.sql.Ref

oracle.sql.REF

getREF

oracle.sql.REF

oracle.sql.REF

getROWID

oracle.sql.ROWID

oracle.sql.ROWID

getShort

short

short

getString

String

String

getSTRUCT

oracle.sql.STRUCT

oracle.sql.STRUCT.

getTime

java.sql.Time

java.sql.Time

getTimestamp

java.sql.Timestamp

java.sql.Timestamp

getTIMESTAMP

oracle.sql.TIMESTAMP

oracle.sql.TIMESTAMP

getTIMESTAMPTZ

oracle.sql.TIMESTAMPTZ

oracle.sql.TIMESTAMPTZ

getTIMESTAMPLTZ

oracle.sql.TIMESTAMPLTZ

oracle.sql.TIMESTAMPLTZ

getUnicodeStream

java.io.InputStream

java.io.InputStream

getURL

java.net.URL

java.net.URL

Table 8.2. Input Types for setXXX Methods

Method

Input Parameter Type

setArray

java.sql.Array

setARRAY

oracle.sql.ARRAY

setAsciiStream

java.io.InputStream

setBfile

oracle.sql.BFILE

setBFILE

oracle.sql.BFILE

setBigDecimal

BigDecimal

setBlob

java.sql.Blob

setBinaryStream

java.io.InputStream

setBinaryDouble

double or oracle.sql.BINARY_DOUBLE

setBinaryFloat

float or oracle.sql.BINARY_FLOAT

setBLOB

oracle.sql.BLOB

setBoolean

boolean

SetByte

byte

setBytes

byte[]

setCHAR

oracle.sql.CHAR

setCharacterStream

java.io.Reader

setClob

java.sql.Clob

setCLOB

oracle.sql.CLOB

setDate

java.sql.Date

setDATE

oracle.sql.DATE

setDouble

double

setFixedCHAR

java.lang.String

setFloat

float

setInt

int

setINTERVALDS

oracle.sql.INTERVALDS

setINTERVALYM

oracle.sql.INTERVALYM

setLong

long

setNUMBER

oracle.sql.NUMBER

setRAW

oracle.sql.RAW

setRef

java.sql.Ref

setREF

oracle.sql.REF

setROWID

oracle.sql.ROWID

setShort

short

setString

String

setSTRUCT

oracle.sql.STRUCT

setTime

java.sql.Time

setTimestamp

java.sql.Timestamp

setTIMESTAMP

oracle.sql.TIMESTAMP

setTIMESTAMPTZ

oracle.sql.TIMESTAMPTZ

setTIMESTAMPLTZ

oracle.sql.TIMESTAMPLTZ

setUnicodeStream

java.io.InputStream

setURL

java.net.URL

Manipulating SQL Null Data

When no value is assigned to a column during Insert/Update, the Oracle database automatically and by default assigns a NULL value, unless the target column has been declared NOT NULL; in this case, a value must be assigned to it. As summarized in Table 3.3, primitive Java types such as boolean, byte, short, int, long, float, and double cannot represent or retrieve SQL NULL columns. However, the corresponding reference Java types, also called wrapper classes, including java.lang.Boolean, java.lang.Byte, java.lang.short, java.lang.Long, java.lang.Float, and java.lang.Double, must be used to map table columns that may return SQL NULL. These wrapper classes furnish getters and setters methods (see the java.lang package in any Java book for more details). As illustrated in the following code snippet, the combination of getters methods and wasNull() method of ResultSets allows you to determine if the retrieved value was Null and deal with it.

int deptno = rset.getInt("deptno");
if (rset.wasNull()) {
      ... // Deal with null value
}

Conversely, the standard PreparedStatement method setNull () and OraclePreparedStatement setNullAtName() methods can be used to set a parameter to SQL NULL.

Signatures:

  • setNull(int index, int sqlType);

  • setNull(int index, int sqlType, String sql_name);

Examples:

  • psmt.setNull(1, OracleTypes.FIXED_CHAR);

  • pstmt.setNull(1, java.sql.Types.VARCHAR);

  • pstmt.setNull(1, OracleTypes.NUMERIC);

  • pstmt.setNull(1, OracleTypes.STRUCT, "ADDRESS");

Manipulating Character Data Types

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

SQL CHAR types are fixed-length character data of maximum 2,000 bytes or characters and minimum 1 byte or character. If you insert a value that is shorter than the specified column length, Oracle will blank-pad to match the length. The Oracle JDBC furnishes the oracle.sql.CHAR wrapper class and the corresponding getCHAR(), setCHAR(), setCHARAtName, and updateCHAR() methods in OraclePreparedStatement, OracleCallableStatement, and OracleResultSet classes. The oracle.sql.CHAR class furnishes getString(), toString(), and getStringWithReplacement() methods for converting character data into Java strings.

As Table 3.2 indicates, a CHAR column can also be mapped to and from the following standard Java types using the corresponding setxxx(), updatexxx(), and getxxx() methods defined in PreparedStatement, CallableStatement, and ResultSet interfaces: java.lang.String, java.sql.Date, java.sql.Time, java.sql.Timestamp, 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, and double.

The following code snippet excerpted from the complete code sample in Chapter 3 illustrates retrieving and updating CHAR columns:

  /*
   * Update SQL CHAR column with oracle.sql.CHAR
   */
    OraclePreparedStatement ops = (OraclePreparedStatement)
      conn.prepareStatement("UPDATE TypesTab SET xchar = ?");
    ops.setCHAR (1, c);
    ops.execute();
    ops.close();


  /*
   * Retrieve SQL CHAR column as oracle.sq.CHAR
   */
    OracleStatement ostmt = (OracleStatement)
conn.createStatement();
    String ochar = null;
    OracleResultSet ors =
     (OracleResultSet) ostmt.executeQuery("SELECT xchar FROM
TypesTab");
    while (ors.next())
    {
      ochar = ors.getString(1);
    }
    ostmt.close();
    // construct an oracle.sql.CHAR object
    return new CHAR(ochar, null);   

Because of the automatic blank-padding of CHAR columns, setCHAR() might not give the right result when comparing a CHAR column with character data in a WHERE clause; use the setFixedCHAR() method of the OraclePreparedStatement object instead.

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

The SQL VARCHAR2 data type defines a variable-length (nonblank-padded) character data type. The size, which is between 1 and 4,000, must be specified. There is no oracle.sql.VARCHAR2 class; as Table 3.2 indicates, the Oracle JDBC maps SQL VARCHAR2 to and from oracle.sql.CHAR, java.lang.String, java.sql.Date, java.sql.Time, java.sql.Timestamp, 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, and double.

Apart from setFixedCHAR, the same methods as CHAR (i.e., setString, getString, etc.) apply for manipulating VARCHAR.

VARCHAR2 (as well as CHAR) columns can also be streamed using getAsciiStream by redefining it as a LONGVARCHAR using defineColumnType of OracleStatement. In fact, the data is fetched into a staging buffer, from which getAsciiStream returns an InputStream.

Assume a database with a UTF-8 character set, a VARCHAR2(4000) column, and the following code snippet:

char[] ch = new char[4000];
// fill up ch[1..4000] = 'a'
setString (1, new String (ch));
  • Pre-10g JDBC-Thin sends the actual byte length after conversion (i.e., 4,000 bytes) to the server and lets you bind up to 4000 ASCII characters (single-byte characters) against such a column.

  • 10g JDBC-Thin sends the predicted max length after conversion (i.e., 4,000 ×3 = 12,000 bytes) to the server; as a result, the server throws ORA-1461 because you cannot insert 12,000 bytes in VARCHAR2(4000).

  • Using 10g JDBC and setting the retain_v9_bind_behavior_string flag to true tells JDBC that the byte length after conversion will always be less than 4,000. JDBC sends 4,000 to the server, thus simulating the Oracle 9i JDBC behavior for ASCII binding to a UTF-8 database.

NCHAR, NCHAR(n), NVARCHAR2 (n)

NCHAR specifies a fixed-length Unicode data type; the actual maximum column length (2,000 bytes) is determined by the width (in bytes) of the national character set, defined at database creation.

NVARCHAR specifies a variable-length Unicode data type; the maximum size (less or equal to 4,000 bytes) must be specified.

JDBC does not currently offer standard APIs to deal with NCHAR/NVARCHAR data types in a way different from CHAR. The application must unwrap the connection to get an Oracle Connection (if not already) in order to map to and from oracle.sql.NCHAR.

The OracleConnection provides an isNCHAR method for checking if the column is of type NCHAR, and the OraclePreparedStatement object furnishes the setFormOfUse method to direct JDBC to treat the column as either SQL NCHAR or SQL CHAR.

pstmt.setFormOfUse(2, FORM_NCHAR); // NCHAR column
pstmt.setString(2, UnicodeString); 

If the system property oracle.jdbc.defaultNChar or the connection property defaultNChar is set to true (default is false ), then there is no need to specify setFormOfUse. JDBC treats all character columns as being national language (the database will convert all CHAR data into NCHAR/ NVARCHAR2).

Notes:

  • Pre-10.2 JDBC does not support NCHAR literal (n’...’) containing Unicode characters that cannot be represented in the database character set.

  • Using 10.2 JDBC against 10.2 RDBMS, NCHAR literals (n'...') are converted to Unicode literals (u'...'); non-ASCII characters are converted to their corresponding Unicode escape sequence.

  • Using 10.2 JDBC against pre-10.2 RDBMS, NCHAR literals (n’...’) are not converted and generate undetermined content for characters that cannot be represented in the database character set.

The upcoming JDBC 4.0 specification is said to provide standard support for NCHAR, NVARCHAR, LONGNVARCHAR, and NCLOB data types.

Oracle JDBC Support for Number Data Types

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

SQL NUMBER columns store positive and negative fixed numbers with absolute values from 1.0 × 10-130 to 1.0 × 10126 (but not including 1.0 ×10126 itself). A SQL NUMBER data types represent numbers with precision p between 1 and 38 and scale s between -84 and 127.

Examples:

  • A NUMBER with p = 4 stores values between –9999 and +9999.

  • A NUMBER with p = 5 stored values between –99999 and +99999.

The Oracle JDBC furnishes the oracle.sql.NUMBER wrapper class and the corresponding getNUMBER(), setNUMBER(), setNUMBERAtName, and updateNUMBER() methods in OraclePreparedStatement, OracleCallableStatement, and OracleResultSet classes. In fact, the oracle.sql.NUMBER is a wrapper class for all ANSI SQL92 data types, including INTEGER, SMALLINT, NUMERIC, DECIMAL, FLOAT, DOUBLE, and REAL.

Depending on the Java type that you use to receive the data, you may lose the precision information, oracle.sql.NUMBER preserves the precision.

Per Table 3.2, a SQL NUMBER can also be mapped to and from the following standard Java types using the corresponding setxxx(), updatexxx(), and getxxx() methods defined in PreparedStatement, CallableStatement, and ResultSet interfaces: 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, and double.

Here is a code snippet excerpted from Listing 3.5:

  /*
   * Update SQL NUMBER column with java.lang.Integer
   */
     PreparedStatement ps =
        conn.prepareStatement("UPDATE TypesTab SET num = ?");
     int n = x.intValue();
     ps.setInt (1, n);
     ps.execute();
     ps.close();

  /*
   * Retrieve SQL NUMBER column as int
   */
     Statement stmt = conn.createStatement();
     int n = 0;
    ResultSet rs = stmt.executeQuery("SELECT num FROM
TypesTab");
    while (rs.next())
    {
      n = rs.getInt(1);
    }
    stmt.close();
    return n;

Here is a code snippet excerpted from Listing 3.6:

/*
 * Map oracle.sql.NUMBER to SQL NUMBER
 */
   OraclePreparedStatement ops = (OraclePreparedStatement)
       conn.prepareStatement("UPDATE TypesTab SET num = ?");
   ops.setNUMBER (1, n);
   ops.execute();
   ops.close();


 /*
  * Map SQL NUMBER column to oracle.sq.NUMBER
  */
    OracleStatement ostmt = (OracleStatement)
conn.createStatement();
    NUMBER onb = new NUMBER();
    OracleResultSet ors =
     (OracleResultSet) ostmt.executeQuery("SELECT num FROM
TypesTab");
    while (ors.next())
    {
      onb = ors.getNUMBER(1);
    }
    ostmt.close();
    return onb;

BINARY_FLOAT and BINARY_DOUBLE

A SQL BINARY_FLOAT is a 32-bit, single-precision, floating-point number data type; its values vary between 1.17549E-38F and 3.40282E+38F. A SQL BINARY_DOUBLE is a 64-bit, double-precision, floating-point number data type; its values vary between 2.22507485850720E-308 and 1.79769313486231E+308.

Since Oracle Database 10g Release 1, the JDBC drivers support the SQL BINARY_FLOAT and SQL BINARY_DOUBLE data types, respectively, through the oracle.sql.BINARY_FLOAT and oracle.sql.BINARY_DOUBLE classes. The JDBC mapping is compatible with IEEE754 float and IEEE754 double, but with the following restrictions:

  • –0 is coerced to +0.

  • Comparison with NaN (i.e., Not a Number) is not supported.

  • All NaN values are coerced to either BINARY_FLOAT_NAN or BINARY_DOUBLE_NAN.

  • Nondefault rounding modes are not supported.

  • Nondefault exception handling modes are not supported.

The following methods have been added to the OraclePreparedStatement and OracleCallableStatement objects: setBinary-Float(int, float), setBinaryDouble(int, double), setBinaryFloat(String, float), and setBinaryDouble(String, double).

BINARY_FLOAT bf = new BINARY_FLOAT((float)789.669);
BINARY_DOUBLE bd = new BINARY_DOUBLE((double)897.9999);

OraclePreparedStatement opstmt = (OraclePreparedStatement)
  oconn.prepareStatement("insert into binary_tab
values(?,?)");

pstmt.setBinaryFloat (1, bf);
pstmt.setBinaryDouble (2, bd);
pstmt.executeUpdate();

pstmt.registerReturnParameter(1, OracleTypes.BINARY_FLOAT);
pstmt.registerReturnParameter(2, OracleTypes.BINARY_DOUBLE);

OraclePreparedStatement pstmt = (OraclePreparedStatement)
oconn.prepareStatement("SELECT bfcol, bdcol FROM binary_tab
where ...);
ResultSet rs = pstmt.executeQuery ();

float bfcol= 0;
double bdcol = 0;
while (rs.next()) {
col2 = rs.getFloat (2);
col3 = rs.getDouble (3);

JDBC Support for Long and Raw Data Types

SQL RAW(s)

The SQL RAW data type stores raw binary data such as graphics, sound, documents, or arrays of binary data. The variable size, between 1 and 2,000, must be specified. Oracle recommends to use SQL BLOB instead (you can view the RAW data type as a small-sized BLOB but without the locator). The Oracle JDBC maps SQL RAW to and from oracle.sql.RAW and Java bytes[]. SQL RAW can be manipulated using the setRAW, setRAWAtName, getRAW, and updateRAW methods of OraclePreparedStatement, OracleCallableStatement, and OracleResultSet classes. The oracle.sql.RAW class furnishes the newRAW method for creating instances of RAW data type compatible with Oracle Database 10g, and oldRAW constructors for creating instances of RAW data type compatible with pre-10 g releases. The following code snippet excerpted from Listing 3.8 illustrates the mapping of a RAW column to oracle.sql.RAW:

/*
 * Update a SQL RAW column with oracle.sql.RAW data
 */
   byte[] bytearr = new byte[600]; // Max size of SQL RAW data
    RAW oraw = new RAW(bytearr);
   OraclePreparedStatement ops = (OraclePreparedStatement)
       conn.prepareStatement("UPDATE TypesTab SET xraw = ?");
    ops.setRAW (1, oraw);
    ops.execute();
    ops.close();

/*
 * Retrieve a SQL RAW column as oracle.sq.RAW
 */
    OracleStatement ostmt = (OracleStatement) conn.createStatement();
    // construct an oracle.sql.RAW object

    OracleResultSet ors =
     (OracleResultSet) ostmt.executeQuery("SELECT xraw FROM
TypesTab");
    while (ors.next())
    {
     oraw = ors.getRAW(1);
    }
    ostmt.close();
   return oraw;

RAW columns can also be redefined as LONGVARBINARY using defineColumnType of OracleStatement, then streamed using getBinaryStream. In fact, the data is fetched into a staging buffer, from which getBinaryStream returns an InputStream.

LONG and LONG RAW

LONG columns store variable-length character strings containing up to 2 gigabytes –1, (i.e., 231–1) bytes. LONG columns have many of the characteristics of VARCHAR2 columns. A LONG RAW data type is a variable-length raw binary data, up to 2 gigabytes data, and the storage is allocated dynamically.

LONG and LONG RAW data types are maintained for backward compatibility only; Oracle recommends to use VARCHAR2 or LOBs instead. Still, the Oracle JDBC maps LONG to oracle.sql.RAW and java.lang.String, while LONG RAW data is mapped to oracle.sql.RAW and byte[].

LONG and LONG RAW columns can be manipulated using one of the following three approaches:

  1. The setRAW, setRAWAtName, getRAW, and updateRAW methods

    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 };
    
     OraclePreparedStatement opstmt =
    (OraclePreparedStatement)
      oconn.prepareStatement("insert into raw_tab
    values(?)");
    ps.setRAW (1, new RAW(buf));
    ps.executeUpdate();
  2. The setString, setBytes, and getBytes methods perform “direct binding” (i.e., the entire content is fetched into a staging buffer), which is fast, allows statement batching, but is restricted to 32,766 characters/bytes. In Oracle JDBC 10g Release 2, for data larger than 32,766, these methods switch automatically (under the covers) to streaming mode (i.e., setCharacterStream or setBinaryStream, described hereafter):

    ResultSet rs = stmt.executeQuery ("SELECT RAWCol FROM
    raw_ab";);
     ResultSetMetaData rsm = rs.getMetaData();
     int index = rsm.getColumnCount();
     while (rs.next()) {
          for (int i = 1; i <= index; i++) {
          rs.getBytes(i)); // do something about this output
     }
     rs.close ();
  3. LONG and LONG RAW data types are by default (or most of the time) manipulated in “streaming” mode using setCharacterStream, setAsciiStream, setBinaryStream, getBinaryStream, getCharacterStream, and getUnicodeStream methods. These methods return the bytes of the data as a stream (i.e., InputStream object) of RAW bytes, ASCII bytes, or Unicode bytes (with the UTF-16 encoding). In fact, the data is fetched into a staging buffer, from which it is streamed. In addition, such stream binding is slower than the direct binding (i.e., setString, getString) and does not support statement batching.

// Insert  a LONG column
File if = new File("Ascii.dat");
InputStream is = new FileInputStream(if);
PreparedStatement pstmt =
      conn.prepareStatement("INSERT INTO LONGTAB VALUES (?)");
pstmt.setAsciiStream(1, is, (int)if.length());
pstmt.execute();
// retrieve a LONG Column
ResultSet rs =
      stmt.executeQuery("SELECT LongCol FROM TAB");
 if (rs.next())
 {
   InputStream longdata = rs.getAsciiStream(1);
   FileOutputStream fos = new FileOutputStream("Long.log");
   // Read from the stream and write to log file
   int count;
   while ((count = longdata.read()) != -1) fos.write(c);
    ...
   fos.close ();
  }

// Insert a LONG RAW column
OraclePreparedStatement ps = (OraclePreparedStatement)
      conn.prepareStatement ("insert into LONGRAWTAB values (?)");
byte[] bytearr = new byte[200]; // byte array
InputStream is = new ByteArrayInputStream (bytearr);
ps.setBinaryStream (1, is, 200);
pstmt.executeUpdate ();
is.close ();

// Retrieve  a LONG RAW column
ResultSet rs =
  stmt.executeQuery ("SELECT LongRawCol FROM Tab");
if (rs.next ())
 {
   InputStream in = ((OracleResultSet)rset).getBinaryStream (1);
   //process input stream as above (retrieve LONG)
    ...
 }

Note

When retrieving a LONG data with getBinaryStream, the returned data stream, depending on the driver type (i.e., JDBC-Thin or JDBCOCI), the client character set, or the server character set, is summarized in Table 8.3.

Table 8.3. Returned Data Stream for LONG column

Driver Type

JDBC Client Character Set

Database Character Set

Returned Data Stream

Thin

Not regarded

US7ASCII or WE8ISO8859P1

US7ASCII

Thin

Not regarded

Other

UTF-8

OCI

US7ASCII or WE8ISO8859P1

Not regarded

US7ASCII

OCI

Other

Not regarded

UTF-8

In Oracle JDBC 10g Release 2, for data smaller than 32,766 bytes, the getXXXStream methods switch automatically to setString or setBytes. You may also explicitly disable the automatic streaming of LONG and LONG RAW columns by redefining these respectively as VARCHAR2 and VARBINARY using the defineColumnType() method of OracleStatement, and then manipulating it using setString/setBytes.

JDBC Support for SQL Datetime Data Types

DATE

The SQL DATE data type stores date and time information, including century, year, month, date, hour, minute, and second; it is equivalent to SQL99 TIMESTAMP[0], meaning without the fractional part of second. The Oracle JDBC supports SQL DATE through the oracle.sql.DATE wrapper class. SQL DATE data types can be manipulated using the setDATE, setDATEAtName, getDATE, and updateDATE methods of OraclePreparedStatement, OracleCallableStatement, and OracleResultSet classes. In addition, DATE data types can also be mapped to standard java.sql.Date, java.sql.Time, java.sql.Timestamp, and java.lang.String; however, doing so may lead to precision loss (i.e., the time component of java.sql.Date is zeroed, and the behavior differs throughout releases), as explained later.

The following code snippets, excerpted from Listing 3.2, illustrates the mapping of SQL DATE to java.sql.Date, and vice versa:

/*
 * Update SQL DATE column with java.sql.Date
 */
   PreparedStatement ps =
       conn.prepareStatement("UPDATE TypesTab SET xdat = ?");
    ps.setDate (1, x);
    ps.execute();
    ps.close();

/*
 * Retrieve SQL DATE column as java.sql.Date
 */
    Statement stmt = conn.createStatement();
    Date dat = null;
    ResultSet rs = stmt.executeQuery("SELECT xdat FROM
TypesTab");
    while (rs.next())
    {
      dat = rs.getDate(1);
    }
    stmt.close();
    return dat.toString();
  

The following code snippet, excerpted from Listing 3.3, shows mapping oracle.sql.DATE to SQL DATE:

/*
 * Update SQL DATE with oracle.sql.DATE
 */
   OraclePreparedStatement ops = (OraclePreparedStatement)
       conn.prepareStatement("UPDATE TypesTab SET xdat = ?");
    ops.setDATE (1, x);
    ops.execute();
    ops.close();
 /*
  * Retrieve SQL DATE column as oracle.sq.DATE
  */
    OracleStatement ostmt = (OracleStatement)
conn.createStatement();
    DATE dat = new DATE();
    OracleResultSet ors =
     (OracleResultSet) ostmt.executeQuery("SELECT xdat FROM
TypesTab");
    while (ors.next())
    {
      dat = ors.getDATE(1);
    }
    ostmt.close();
    return dat; 

TIMESTAMP, TIMESTAMPTZ, TIMESTAMPLTZ

Starting with Oracle 9i Release 2, the Oracle database furnishes the SQL99 TIMESTAMP data type (i.e., precision up to nanosecond). The SQL TIMESTAMP data type is an extension of the DATE data type, since it stores year, month, day, hour, minute, second, and fractional seconds; these fractional seconds are not stored by the DATE data type.

TIMESTAMP'1997-01-31 09:26:50.124'

The Oracle JDBC supports SQL TIMESTAMP through the oracle.sql.TIMESTAMP class and can be manipulated using the setTIMESTAMP, setTIMESTAMPAtName, getTIMESTAMP, and updateTIMESTAMP methods of OraclePreparedStatement, OracleCallableStatement, and OracleResultSet classes. In addition, the TIMESTAMP data can also be mapped to oracle.sql.DATE, java.sql.Date, java.sql.Time, java.sql.Timestamp, java.lang.String, and byte[]. In these cases, the corresponding setxxx, getxxx, and updatexxx methods of the PreparedStatement, CallableStatement, and ResultSet interfaces can be used.

  • Mapping a SQL TIMESTAMP column into java.sql.Date, using setDate(), will lose the time precision, since java.sql.Date only stores up to milliseconds.

  • 8.1.7 JDBC: Retrieving a SQL DATE column using getObject() returns a java.sql.Timestamp.

  • In Oracle 9i JDBC, retrieving a SQL DATE column using getObject() returns either java.sql.Date (9.0.1) or java.sql.Timestamp (9.2).

  • In Oracle 10g JDBC, retrieving a SQL DATE column using getObject() returns a java.sql.Date.

  • JDBC applications can preserve the behavior they are accustomed to (i.e., retrieve java.sql.Timestamp or java.sql.Date) using the oracle.jdbc.V8Compatible flag (setting to true returns java.sql.Timestamp).

    properties.put ("oracle.jdbc.V8Compatible", "true");

The TIMESTAMPTZ data type (i.e., TIMESTAMP WITH TIME ZONE data type ()) is a TIMESTAMP data type “with a time zone offset, which is the difference (in hours and minutes) between the local time and the UTC (Coordinated Universal Time.)”

TIMESTAMP '1997-01-31 09:26:56.66 +02:00'
TIMESTAMP '1999-04-15 8:00:00 -8:00'
TIMESTAMP '1999-04-15 8:00:00 US/Pacific'

The Oracle JDBC supports SQL TIMESTAMPTZ through the oracle.sql.TIMESTAMPTZ class and corresponding setTIMESTAMPTZ, setTIMESTAMPTZATName, getTIMESTAMPTZ, updateTIMESTAMPTZ in OraclePreparedStatement, OracleCallableStatement, and OracleResultSet classes. Similar to TIMESTAMP, it can also be mapped to oracle.sql.DATE, java.sql.Date, java.sql.Time, java.sql.Timestamp, java.lang.String, and byte[], with the corresponding manipulation methods.

The TIMESTAMPLTZ data type is a TIMESTAMP WITH TIME ZONE data type; however, “the data stored in the database is normalized to the database time zone, and the time zone offset is not stored as part of the column data.”

The Oracle JDBC supports TIMESTAMPLTZ through the oracle.sql.TIMESTAMPLTZ class and corresponding setTIMESTAMPLTZ, set-TIMESTAMPLTZATName, getTIMESTAMPLTZ, and updateTIMESTAMPLTZ in OraclePreparedStatement, OracleCallableStatement, and OracleResultSet classes..

Similar to TIMESTAMPTZ, it can also be mapped to oracle.sql.DATE, java.sql.Date, java.sql.Time, java.sql.Timestamp, java.lang.String, and byte[].

// Assume a TIMESTAMP_TAB with the various Timestamp types
// as columns
String my_date = "2005-12-04 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);
String query = "update timestamp_tab set c1 = ?, c2 = ?, c3 =
?";
OraclePreparedStatement ops =
 (OraclePreparedStatement)  con.prepareStatement(sql);
ops.setTIMESTAMPLTZ(1, my_ts);
ops.setTIMESTAMPLTZ(2, my_tstz);
ops.setTIMESTAMPLTZ(3, my_tsltz);

ops.executeUpdate ();

INTERVAL YEAR TO MONTH and INTERVAL DAY TO SECOND

The SQL INTERVALYM data type (i.e., INTERVAL YEAR TO MONTH) stores a period of time in YEAR and MONTH; it is used to measure time differences. The Oracle JDBC supports this data type through the oracle.sql.INTERVALYM class and the setINTERVALYM, setINTERVALYMATName, getINTERVALYM, and updateINTERVALYM methods.

The SQL INTERVALDS (i.e., INTERVAL DAY TO SECOND) stores a period of time in days, hours, minutes, and seconds; it is used to measure time differences. The Oracle JDBC supports this data type through the oracle.sql.INTERVALDS class, and the setINTERVALDS, setINTERVALDSATName, getINTERVALDS, and updateINTERVALDS methods.

INTERVALDS ids = new INTERVALDS ("15 08:12:42.0");
OraclePreparedStatement ops = (OraclePreparedStatement)
    oconn.prepareStatement("INSERT INTO idstab VALUES (?)");
ops.setINTERVALDS (1, ds);
...
opst.executeUpdate ();
ops = (OraclePreparedStatement) conn.prepareStatement
        ("SELECT ids1 FROM idstab");
OracleResultSet ors = pstmt.executeQuery ();
ids = new INTERVALDS;
while (rs.next ()) {
   col2 = rs.getINTERVALDS (1);
}

JDBC Support for LOB Datatypes

Overview

Large Object data types (LOBs) is an umbrella name for the Binary LOB (BLOB), the Character LOB (CLOB), the Unicode LOB (NCLOB), and the Binary File (BFILE) data types. These are used for storing large and unstructured data such as text, image, video, and spatial data. The BLOB, CLOB, and NCLOB data types can store unlimited data size (the physical storage is the limit). CLOB data is in the database character set, which may be single or multibytes, while NCLOB data is in the national language character set. LOB data types larger than 4 K are not stored within the table column; in all cases, the column contains a locator, which points to the actual LOB data (inside or outside of the database). See the Oracle Database SQL Reference documentation for more details on the features and restrictions of SQL LOB data types. How does JDBC support LOB manipulation?

  1. Using LOB API

    The LOB API designates standard and Oracle’s proprietary LOB methods furnished by the locator and PreparedStatement (OraclePreparedStatement), CallableStatement (OracleCallableStatement), and ResultSet (OracleResultSet); it allows random read and/or write (if SELECT ... FOR UPDATE ) access. You can start anywhere in the LOB, read as much data as you want, and then start again at a different offset. Assume a CLOB containing a table of contents or index; you can read a chunk, then select another chunk based on the previously retrieved data, and update the LOB, if you had selected the column with the FOR UPDATE clause.

    LOB manipulation through the LOB API is done in two steps:

    Step 1: Retrieve, insert, and update the LOB locator using the standard setBlob, setClob, getBlob, getClob, updateBlob, updateClob, and the proprietary setBLOB, setBLOBATName, setBFILE, seBFILEATName, setBfile, setBfileAtName, setBloabATName, setCLOB, setCLOBATName, setClobAtName, getBFILE, getBLOB, getCLOB, updateBFILE, updateBfile, updateBLOB, and updateCLOB methods of the OraclePreparedStatement, OracleCallableStatement, and OracleResultSet classes. The LOB wrapper classes (i.e., oracle.sql.BLOB, oracle.sql.CLOB, oracle.sql.BFILE, oracle.sql.NCLOB) do not have public constructors; therefore, in order to create a new LOB, you must use the createTemporary, getEmptyCLOB, or getEmptyBLOB methods.

     ResultSet rs = stmt.executeQuery("SELECT
    blobcol,bfilcol clobcol from XobTab";
    
     While (rs.next())
     {
       BLOB BlobLoc = ((OracleResultSet)rs).getBLOB(1);
       CLOB ClobLoc = ((OracleResultSet)rs).getCLOB(2);
       BFILE BfileLoc = ((OracleResultSet)rs).getBFILE(3);
      }

    Alternatively, the getOracleObject method can be used to retrieve an oracle.sql.Datum object, which can then be cast appropriately to CLOB, BLOB, or BFILE.

    BLOB BlobLoc = (BLOB)
    ((OracleResultSet)rs).getOracleObject (1);

    Step 2: Manipulate the LOB content

    • To read the LOB content:

      • Stream the content out, using getBinaryStream, getString, getCharacterStream, and getAsciiStream.

        byte[] bytarr = new byte[20]; // 20 bytes chunk
        BLOB LOBLoc = ps.getBLOB (1);
        InputStream is = LOBLoc.getBinaryStream();
        while ((length = stream.read(bytarr)) != -1)
        {
             // process the chunk
               bytarr = new byte[10];
         }
         is.close();
      • Use getString, getSubString, and getBytes methods.

        String ClobData = ClobLoc.getSubString(1,12);

    • To write, update, and append the LOB content:

      • Use putBytes, setString

           BLOB tempBlob = BLOB.createTemporary (conn, false,
                               BLOB.DURATION_SESSION);
            byte[] bytarr = { ... };
            tempLob.putBytes (1, bytarr);
            PreparedStatement pstmt = conn.prepareStatement (
                "insert into XobTab values (?)";
            ((OraclePreparedStatement)ps).setBLOB (1, tempLob);
            ps.execute ();
            ps.close ();
      • Streaming the data in by invoking : getBinaryOutputStream, getAsciiOutputStream(), or getCharacterOutputStream methods on the locator; then write on the output stream.

        OutputStream os = LobLoc.getBinaryOutputStream();
        FileInputStream is = new FileInputStream(...);
        int bufsiz = LobLoc.getBufferSize();
        byte[] bytarr = new byte[bufsiz];
        int count = -1;
        while((count = is.read(bytarr)) != -1) {
          os.write(bytarr, 0, len);
        }
        is.close();
        os.close();

      Notes:

    • Explictly demarcating LOB operations by open() and close() allows reading and writing the LOB content without firing associated triggers; however, upon close(), the trigger is fired.

    • In pre-10g releases, the JDBC-Thin driver uses the PL/SQL DBMS_LOB package under the covers for manipulating LOB data in the RDBMS. Starting with 10g releases, the JDBC-Thin is on par with JDBC-OCI and uses the more efficient native LOB functions for most operations (certain operations such as hasPattern and isSubLob are still done via the dbms_lob package, though).

  2. Using Optimized LOB Access

    In Oracle Database 10g, for LOBs of size less than 2 gigabytes (i.e., 2,147,483,648 bytes),[3] which is a limit imposed by Java int on array sizes, the manipulation has been simplified by removing the need to explicitly manage the locator. In fact, the appropriate getBLOB, setBLOB, getCLOB, setCLOB, getBFILE, and so on are invoked under the covers.

    • The standard getBytes, getBinaryStream, getString, getCharacterStream, getAsciiStream setBytes, setBinaryStream, setString, setCharacterStream, and setAsciiStream methods of PreparedStatement, ResultSet, and CallableStatement have been extended to take BLOB, CLOB, and BFILE columns as parameters. Note that in Oracle JDBC 10g Release 2, getBytes now returns the LOB data instead of the unusable bytes of the LOB locator.

    • Oracle JDBC 10g Release 2 implements the standard JDBC 3.0 setBytes, setString, setBinaryStream, setCharacterStream, setAsciiStream, and truncate methods using ojdbc14.jar; for classes12.jar with JDK 1.2/1.3, the LOB variable must be cast to java.sql.Blob or java.sql.Clob. As a result, the behavior of setString, setCharacterStream, setAsciiStream, setBytes, and setBinaryStream methods has changed between 10g Release 1 and Release 2, as described later (and discussed in Chapter 7).

      • setBytes switches to setBinaryStream for SQL operations (i.e., PreparedStatement) on data larger than 2,000 bytes.

      • setBytes switches to setBinaryStream for PL/SQL operations (i.e., CallableStatement) on data larger than 2,000 bytes and to setBytesForBlob for data larger than 32,512 bytes.

      • setString switches to setCharacterStream for SQL operations on data larger than 32,766 characters.

      • setString switches to setStringForClob for string data larger than 32,512 bytes in the database character set or national character set, depending on whether setFormOfUse has been used for NCLOB parameters.

        See Tables 7.4 and 7.5 for more details.

    • In Oracle JDBC 10g Release 1, for CLOB data larger than 32,765 bytes, setting the connection property SetBigStringUseClob to true causes the standard setString method (of PreparedStatement) to switch to Oracle’s setStringForClob (of OraclePreparedStatement), under the covers.

      props.put("SetBigStringTryClob", "true");
      ...
      ps.setString(1, str);
      ps.executeUpdate();

      In Oracle JDBC 10g Release 2, this property is no longer neccessary for large data, the driver automatically (and under the covers) switches setString to setStringForClob; otherwise, it uses setCharacterStream.

  3. Using LONG Streaming for LOBs (10g JDBC only)

    If you don’t need the flexibility offered by the LOB locator (and corresponding APIs), you can use defineColumnType(nn, Types.LONGVARBINARY) or defineColumn- Type(nn,Types.LONGVARCHAR) to redefine the LOB as LONG or LONG RAW and then stream the LOB column as if it were a LONG orLONG RAW (i.e.,the data is fetched into a staging buffer, from which getXXXStream returns an InputStream) column. However, unlike the traditional LOB streaming and LOB locator APIs, “LONG streaming” always starts at the beginning of the LOB, allows only one single access, and can read as much data as desired.

    // Claim that these columns are of LONG and LONG RAW
    types
    (OracleStatement)stmt.defineColumnType(1,
    Types.LONGVARBINARY);
    (OracleStatement)stmt.defineColumnType(2,
    Types.LONGVARCHAR);
     ...
    ResultSet rs = stmt.executeQuery("select LOBCOL from
    XOBTab");
    
    // Retrieving a Clob with getString");
        ocrs.setCommand ("SELECT lobcol FROM XOBTab");
        ocrs.execute ();
        while (ocrs.next ())
        {
           bytarr = stmt.getString (1));
        }
        ocrs.close ();
    // Retrieving a Clob with getString");
    
        ocrs.execute ();
        while (ocrs.next ())
        {
          ocrs.getBytes (2).length);
    }

    Table 8.4 summarizes the various methods offered by the various LOB interfaces/mechanisms.

Table 8.4. LOB Interfaces/Mechanisms and Methods

 

BLOB

CLOB/NCLOB

BFILE

Locators

oracle.sql.BLOB

java.sql.Blob

oracle.sql.CLOB

java.sql.Clob

oracle.sql.BFILE

Locator Methods

getBinaryStream

setBinaryStream

getBytes

setBytes

getChunkSize

opwn, close

position, truncate

getAsciiStream

setAsciiStream

getCharacterStream

setCharacterStream

getChars

setString

putChars

getSubString

getChunkSize

open, close

position, truncate

getBinaryStream

setBinaryStream

getBytes

openFile

closeFile

getDirAlias

isFileOpen

PrepdStmt Methods

setBlob

setBLOB

setBloabAtName

setBLOBAtName

setOracleObject

setClob

setCLOB

setClobAtName

setCLOBAtName

setOracleObject

setBFile

setBFILE

setBfileAtName

setBFILEAtNAme

setOracleObject

Additional CallblStmt Methods

getBLOB

getBinaryStream

setBytesForBlob

getCLOB

getAsciiStream

getCharacterStream

setStringForClob

getBFILE

getObject

getOracleObject

ResultSet Methods

getObject

getOracleObject

updateBLOB

updateBytes

upddateBinaryStream

getObject

getOracleObject

updateCLOB

updateAsciiStream

updateCharacterStream

getObject

getOracleObject

Temporary LOB methods

createTemporary

isTemporary

freeTemporary

createTemporary

isTemporary

freeTemporary

 

Temporary LOB

Temporary LOBs (BLOB, CLOB, NCLOB) are created either implicitly or explicitly in temporary tablespace (as opposed to regular tablespace) for storing transient data. In Oracle JDC 10g Release 2, the setBytesForBlob and setStringForClob methods implicitly create temporary LOBs, which are automatically freed when the statement is executed or closed before execution.

You can explicitly create a temporary LOB with the createTemporary(Connection, boolean, int) method, which is defined in both the oracle.sql.BLOB and oracle.sql.CLOB classes.

  • createTemporary(java.sql.Connection conn, boolean cache, int _duration);

    The duration takes either DURATION_SESSION or DURATION_CALL values defined in the oracle.sql.BLOB or oracle.sql.CLOB classes, although currently, the only usable value is DURATION_SESSION.

    CLOB tempClob =
         CLOB.createTemporary(conn, true, CLOB.DURATION_SESSION);
      java.io.Writer iow = tempClob.getCharacterOutputStream();
      iow.write(s3);
      iow.flush();
      iow.close();

    For NCLOB, the createTemporary method has a slightly different signature:

  • createTemporary (Connection conn, boolean cache, int duration, short form);

    where the short form must take one of the following values:

    oracle.jdbc.OraclePreparedStatement.FORM_NCHAR
    oracle.jdbc.OraclePreparedStatement.FORM_CHAR

    For very large LOBs, createTemporary may be slower than the traditional LOB operations (i.e., creating the LOB Locator, retrieving it, and filling the content).

    The freeTemporary() method frees a temporary LOB:

  • freeTemporary(BLOB temp_lob)

The isTemporary method returns true if the LOB was created by calling the createTemporary method.

Examples of BLOBs Manipulation

Reading BLOBs

The getBinaryStream method of oracle.sql.BLOB retrieves the entire content of a BLOB as an input stream (java.io.InputStream) from the locator.

  // Retrieve blob data as binary output stream
   InputStream ibs= myBlob.getBinaryStream();
   byte[] barr = new byte[20];
   int count = 0;
   while ((length = ibs.read(barr)) != -1)
    {
     for (int i=0; i<length; i++)
     // process barr[i]
    }
    ibs.close();

Writing BLOBs

The setBinaryStream method of oracle.sql.BLOB retrieves the BLOB as an output stream (java.io.OutputStream ) to be written back to the BLOB.

PreparedStatement pstmt =
  conn.prepareStatement ("INTO XobTab (ID, blob_col)
VALUES(?, ?)");

 ps.setNUMBER (1, id);

 File binaryFile = new File("c:\BLOBTest.jpg");
 long flength = binaryFile.length();
 FileInputStream fis = new FileInputStream(binaryFile);
 byte[] bytes = new byte[(int)flength];
 fis.read(bytes);
 ps.setBinaryStream(2,new
ByteArrayInputStream(bytes),bytes.length);
 fis.close();
 ps.execute();
 ps.close();

See Listing 3.10 for a basic example of Java stored procedure manipulating BLOB, and the Oracle interMedia case study in Chapter 17 for an advanced use of BFILE.

Examples of CLOB Manipulation

Reading CLOB Data

The getAsciiStream method of oracle.sql.CLOB retrieves the entire content of a CLOB as an input stream (java.io.InputStream).

Inputstream is = myClob.getAsciiStream();
byte[] acarr = new byte [20];
int count = is.read(acarr, 0, 20);

The getSubString method retrieves a subset of the CLOB as a character string (java.lang.String).

String mySubstr = myClob.getSubString(1,10)).toCharArray());

The getCharacterStream method returns a Unicode input stream in a java.io.Reader object.

// Assume myClob contains the CLOB Locator
// Read from Clob as a character stream
   Reader istream = myClob.getCharacterStream();
   char[] buf = new char[20];
   int count = 0;
   while ((count = istream.read(buf)) != -1)
   {
        for (int i=0; i<count; i++)
        // process chararr[i]);
   }
   istream.close();

Writing CLOB Data

The setAsciiStream method of oracle.sql.CLOB retrieves the CLOB as an output stream (java.io.OutputStream) to be written back to the CLOB.

InputStream is = new FileInputStream ( fis );
int flength = (int)fis.length ();
ps.setAsciiStream (1, fis, flength);

With JDBC 3.0, the standard java.sql.Clob interface furnishes the setCharacterStream method, which returns a Unicode output stream in a java.io.Writer object.

OraclePreparedStatement ps =
    (OraclePreparedStatement) conn.prepareStatement
       ("INTO XobTab (clob_col1) VALUES(?)");
    java.io.StringReader sr = new StringReader (myString);
      ps.setCharacterStream (2, sr, 1);
      ps.executeUpdate ()

// Explicit use of setStringForClob

String ClobString = "sdsdfsdfsdsdsdfsdsdsfsdfsdf";
pst.setStringForClob (3, ClobString);
pst.executeUpdate ();

// Using setString
   OraclePreparedStatement ps = (OraclePreparedStatement)
      conn.prepareStatement ("insert into ClobTab values
(?)");
String ClobString = "sdsdfsdfsdsdsdfsdsdsfsdfsdf";
      ps.setString (3, ClobString);
      pst.executeUpdate ();

See Listing 3.9 for a basic example of Java stored procedure manipulating CLOB, and the Oracle inter Media case study in Chapter 17 for an advanced use of CLOB.

Examples of BFILE Manipulation

Prerequisites

In order to manipulate an external file through the Oracle SQL (and JDBC):

  1. The schema must have the Oracle Database ANY DIRECTORY privilege, which is required to create an alias, or the external directory containing the external file.

  2. Create the alias using the following SQL DDL:

    create or replace directory bfiledir as 'C:TEMP';

    or the following JDBC statement

    stmt.execute ("CREATE DIRECTORY bfiledir AS 'C:
    Temp'");
  3. Insert the name of the external file, as follows:

    insert into BFILETab values (bfilename('BFILEDIR',
    'Filename'));

Reading BFILE Data

  • Retrieve the locator:

    OracleResultSet rs = (OracleResultSet)
    stmt.executeQuery("SELECT Bfile_col FROM BFILETab WHERE ID =
    ...");
    
    BFILE bfil = null;
    while (rs.next())
    {
    bfil = rs.getBFILE(1);
    }
    stmt.close();
    return bfil;
  • Read the file content, using the getBinaryStream() method retrieves the file as an input stream:

    myBfile.openFile();
    long count = myBfile.length();
    int lump = 20;
    InputStream is = myBfile.getBinaryStream();
    byte[] buf = new byte[lump];
    while ((count = is.read(buf)) != -1)
    {
     for (int i=0; i<length; i++)
     // process buffer[i]
    }
    is.close();
    myBfile.closeFile()

Creating BFILE Data

  • Create the Locator:

    Statement stmt = conn.createStatement();
        OraclePreparedStatement ps = (OraclePreparedStatement)
         conn.prepareStatement
               ("INSERT INTO XobTab (ID, Bfile_col) VALUES(?,
    ?)");
        ps.setNUMBER (1, id[0]);
        ps.setBFILE (2, bf[0]);
        ps.execute ();
        ps.close();
  • That’s all—BFILEs are read-only existing files/media.

See Listing 3.11 for a basic example of Java stored procedure manipulating BFILE, and the Oracle interMedia case study in Chapter 17 for an advanced BFILE use case.

JDBC Support for ROWID

The ROWID is a pseudocolumn of database rows. The oracle.sql.ROWID is the wrapper class for the Oracle proprietary ROWID data type. The OraclePreparedStatement, Oracle CallableStatement, and OracleResultSet classes furnish the getROWID, setROWID, setROWIDAtName, and updateROWID methods. However, applications cannot change it. As explained in Chapter 7, using the JDBC 3.0 Retrieval of Auto-Generated Key, the newly generated ROWID can be retrieved using stmt.getGeneratedKeys().

stmt = (OracleStatement)conn.createStatement();
stmt.execute ("insert into <Table> values (.,.,.)
                     OracleStatement.RETURN_GENERATED_KEYS);

rset = (OracleResultSet)stmt.getGeneratedKeys();
while (rset.next())
    {
       ROWID r = rset.getROWID(1);
    }

JDBC 4.0 is expected to provide support for a standard java.sql.Rowid type and make this a supported parameter for PreparedStatement, CallableStatement, ResultSet, and DatabaseMetaData.

JDBC Support for OPAQUE Type

OPAQUE

The SQL OPAQUE type acts as a black box, which hosts custom binary data types. The oracle.sql.OPAQUE class wraps the SQL OPAQUE data type, and the drivers retrieve the bytes from the server, but the consumer must turn the bytes into a meaningful type (e.g., XMLType). It is used mainly internally by Oracle to support complex built-in types.

The oracle.sql.OPAQUE extends the oracle.sql.DatumWithConnection class and furnishes the following methods: getBytesValue(), getDescriptor(), getJavaSqlConnection(), getMap(), getSQLTypeName(), getValue(), isConvertibleTo(), and toJdbc(). TheOraclePreparedStatement, OracleResultSet furnish thesetOPAQUE(), setOPAQUEAtNAme(), and getOPAQUE() methods.

See the following XMLType code fragments that use the OPAQUE type.

OPAQUE Descriptor

The oracle.sql.OpaqueDescriptor class furnishes the following methods for describing the OPAQUE data type: createDescriptor(), desc-Type(), getMaxLength(), getTypeCode(), hasFixedSize(), hasUnboundedSize(), isModeledInC(), and so on.

See the Oracle JDBC javadoc for more details on the signatures.

JDBC Support for XMLType

XML DB and XML Type

The Oracle XML Database (also known as XDB) furnishes a native XML-Type data type for managing XML documents directly in the database. As well explained in the Oracle XML DB Developer’s Guide and white paper,[4]XDB stores XML in XMLType columns and/or tables, using either a structured or an unstructured format. In summary:

  • In the unstructured format, the XML document is stored in a LOB. The structure of the XML document is maintained byte for byte; however, it must be retrieved entirely, updated, and written back. Xpath operations are expensive, and SQL constraints are not enforced.

  • In the structured format, the XML document is stored as an XML Object. This allows b-tree indexing, query rewrite, in-place and piece-wise updates, and optimized memory and storage, but the original document formatting is lost. Xpath operations are fast and cheap. SQL constraints are enforced.

XDB furnishes a comprehensive Java API, including a Java DOM API and JDBC support for XMLType. Full coverage of XML DB will require an entire book, but the use case in Chapter 17 is an example of a complete open-source framework leveraging XML DB.

The upcoming JDBC 4.0 specification is expected to define a standard XMLType data type.

JDBC Support for XMLType

JDBC supports the SQL XMLType data type through the oracle.xdb.XML-Type wrapper class in xdb.jar (located under $ORACLE_HOME/rdbms/ jlib). It extends oracle.sql.OPAQUE and furnishes a constructor XML-Type(), which can build an XMLType object from BLOB, CLOB, String, OPAQUE Descriptor,[5]InputStream, andDOM documents, using the corresponding signature. It also furnishes the following APIs: createXML(), close(), createContext(), existsNode(), extract(), getClobVal(), getBlobVal(), getConnType(), getDocument(), getDocumentFragment(), getDOM(), getErrorHandle(), getInputStream(), getNameSpace(), getNumberVal(), getRootElement(), getSChemaURL(), getServiceHandle(), getStringVal(), isFragment(), isSchema-Based(), isSChemaValid(), transform(), andWriteToOutput-Stream().

XMLType is based on the OPAQUE type. All Oracle JDBC driver types are capable of transporting OPAQUE data types (hence XMLType) to and from the database; however, because most or all SQL XMLTypes operations are implemented in C and available in the C libraries, only JDBC-OCI can access the C layer of the Oracle client, similarly the server-side type 2 driver can access the C layer of the RDBMS, and understand XMLType and its various storage formats. See Part IV for a driver-independent workaround.

Code fragments:

  1. Prerequisites:

    add xdb.jar to CLASSPATH

    // Set the URL, using TNS Alias with JDBC-OCI
    String url = "jdbc:oracle:oci:scott/tiger@inst1";
      ods.setURL(url);
    // Retrieve a connection
    Connection conn = ods.getConnection();
    
    // import the XMLTYype class
    import oracle.xdb.XMLType;
                ...
  2. Retrieve XMLType data from OPAQUE type:

      OraclePreparedStatement pstmt =
    (OraclePreparedStatement)
        conn.prepareStatement("select x.xmlDoc from XMLtab
    x");
    
      OracleResultSet ors = (OracleResultSet)
    pstmt.executeQuery();
      while(ors.next())
      {
        // get the XMLType
        XMLType xmldat =
    XMLType.createXML(ors.getOPAQUE(1));
        // retrieve the XMLDocument
        Document xmldoc = (Document)xmldat.getDOM();
        // or, alternatively
        // Retrieve the XML as a String
         String xmlString = xmldat.getStringVal();
      }
  3. Retrieve the XML document as a CLOB:

    OraclePreparedStatement pstmt =
        (OraclePreparedStatement) conn.prepareStatement
          ("select x.xmlDoc.getClobVal() xmlDoc from XMLtab
    x");
    
    OracleResultSet ors = (OracleResultSet)
    pstmt.executeQuery();
        while(ors.next())
        {
          // get the XMLType as a CLOB
          oracle.sql.CLOB xmldat = ors.getCLOB(1);
        }
  4. Update XMLType data:

    OraclePreparedStatement pstmt =
        (OraclePreparedStatement) conn.prepareStatement
          ("update XMLtab set xmlDoc = XMLType(?) ");
    
         // approach #1 using setString
    
          String xmlString =
              "<PO><PONO>200</PONO><PNAME>PO_2</PNAME>l</PO>";
          // bind the string..
          pstmt.setString(1,xmlString);
          pstmt.execute();
    
          // aproach #2 using setObject
          String xmlString =
             "<po><PONO>200</PONO><PNAME>PO_2</PNAME></po>";
          XMLType xmldat = XMLType.createXML(conn,
     xmlString);
          // bind the string..
     pstmt.setObject(1,xmldat);
          pstmt.execute();

See more code fragments and DOM document manipulation techniques in the online XDB documentation.[6]

JDBC Support for SQL Object Types and References Types

The Object types designate user-defined SQL types and SQLJ Object types created using the “CREATE TYPE ...” SQL syntax. See the Oracle Database SQL Reference 10g Release 2 and the Oracle Database Application Developer’s Guide: Object-Relational Features 10g Release 2.

The SQL Objects can be mapped to Java, either automatically by the driver using java.sql.Struct or oracle.sql.STRUCT approach—also called weak type mapping—or using the custom Java classes approach (i.e., SQLData or ORAData)—also called strong type mapping. The SQLJ Object type is a special Object type, built using a Java class in the database, and is covered later in this section.

Type Map

As we have seen, built-in SQL types are mapped to Java through the classes furnished by the oracle.sql.* package. For user-defined SQL types, a type-map object (java.util.Map) allows defining the mapping between a SQL user-defined type and the associated Java class. A type-map object is a class (i.e., java.util.Hashtable) that implements a java.util.Map interface and is associated with each Connection object.

The getTypeMap() andsetTypeMap() methods of the java.sql.Connection and oracle.jdbc.OracleConnection interfaces allow “installing” and retrieving the TypeMap object associated with a Connection object.

java.util.Map map = con.getTypeMap();

The following code snippet maps the SQL type Address_t to the class AddressObj:

map.put(”Address_t”,Class.forName(”AddressObj”));
oconn.setTypeMap((Map)map);

When retrieving data from the result set, you use the default type-map of the connection object, unless you specify a type-map, as follows:

rs.getObject(int columnIndex, Map map);
rs.getObject(String colName, Map map);

A type-map is required for the getObject(), getAttribute(), getARRAY(), andgetValue() methods ofthe ResultSet, CallableStatement, Struct, java.sql.Array, andoracle.sql.REF interfaces. However, when there is none specified for the method in question, the default connection type-map is checked, and, if undefined, the JDBC driver materializes the Oracle object as an instance of oracle.sql.STRUCT.

Weak Type Mapping: Struct and STRUCT

The java.sql.Struct interface defines methods for custom mapping and processing of a user-defined type (i.e., Object type) and its attributes. It furnishes the following methods:

  • getAttributes() or getAttributes(map): Materializes the attributes of a Struct (or Oracle STRUCT) as an array of java.lang.Object (i.e., java.lang.Object[]) using either the default connection type-map or the one furnished in the parameter, as if getObject is invoked on each attribute, and then returns a Java array containing the result.

  • getSQLTypeName: Returns the fully qualified name of the SQL object type that this Struct (or Oracle STRUCT) represents (i.e., SCOTT.ADDRESS_T).

The oracle.sql.STRUCT class is Oracle’s implementation of java.sql.Struct and extends the oracle.sql.Datum. It wraps the “raw bytes” of an instance of an Oracle Object type and contains its SQL type name and an array of oracle.sql.Datum objects that hold the value of each attribute in SQL format. This is the default mapping of Oracle objects when a custom mapping through SQLData or ORAData interfaces is not provided.

In addition to the java.sql.Struct methods discussed previously, it furnishes the following methods:

  • getOracleAttributes: Materializes the attributes of a STRUCT as an array of oracle.sql.Datum objects (i.e., oracle.sql.Datum[])

  • getDescriptor: Retrieves the StructDescriptor. Oracle Objects are structured data; in order for a data stream containing those objects to be parsed and interpreted, a descriptor of the structure (one descriptor per SQL type) must be present within the driver.

    • The oracle.sql.StructDescriptor class and its methods help describe, create, and convert objects to and from STRUCT.

    STRUCT str1 = new STRUCT(<connection>,
    <structDescriptor>, <attributes>);
    // Example
    StructDescriptor strdesc =
          StructDescriptor.createDescriptor(sql_type,
    strdescc);
        Object [] attrib = { new(ssn), name, address };
        return new STRUCT(strdesc, conn, attrib);
         

Note

When retrieving the Oracle object as STRUCT, the driver constructs the STRUCT descriptor; the application must furnish it when creating values for use with setObject().

  • toJdbc() and toJdbc(map): Retrieves the target class in the default map or in the specified type-map.

    To access a STRUCT or a Struct object, the JDBC application invokes either the standard getObject() method or the Oracle proprietary getSTRUCT () methods.

// retrieving the object as a java.sql.Stuct
ResultSet rset = stmt.executeQuery("SELECT * from
AddressTab");
Struct struct = (Struct)rset.getObject(1);

// retrieving the object as an oracle.sql.STRUCT
STRUCT struct =(oracle.sql.STRUCT) rset.getObject(1);
or
STRUCT struct = rset.getSTRUCT(1);

To retrieve the attributes, the JDBC application invokes either getAttributes() or getOracleAttributes().

// using getAttributes()
Struct struct = (Struct)rset.getObject(1);
Object[] attrarr = struct.getAttributes();

// using getOracleAttriutes()
oracle.sql.STRUCT strct =(oracle.sql.STRUCT)
rset.getObject(1);
System.out.println("SQL Object type: " +
strct.getSQLTypeName());
Object[] attrarr = strct.getOracleAttributes();

To bind the oracle.sql.STRUCT or the java.sql.Struct instance to an IN parameter, use the setObject (), setSTRUCT(), setSTRUCTAtName(), setObject(), setOBjectAtName(), setOracleObject(), andsetOracleObjectATName() methods of OraclePreparedStatement, OracleCalableStatement, andOracleResultSet.

STRUCT strct new STRUCT (...);
pstmt.setObject(<index>, strct, Types.STRUCT);

or:

((OralePreparedStatement)pstmt).setOracleObject(<index>,
strct);

The updateSTRUCT(), updateOracleObject() methods of OracleResultSet can be used to update a STRUCT object.

Code Snippet

Assume a user-defined object type, ADTTYP2, and then a table, ADTTAB2 of ADT TYP2 objects:

create type ADTTYP2 as object (n1 number, n2 varchar2(30), n3
date)
/
create table ADTTAB2 (id number, adtcol ADTTYP2)
/
insert into ADTTAB2 values (1, ADTTYP2(101, 'Row One', '01-
JAN-2001'));
commit;

 // inserts an oracle.sql.STRUCT object into an Object Type
table
 //
     OraclePreparedStatement ps =
        (OraclePreparedStatement) conn.prepareStatement
             ("INSERT INTO ADTTAB2 (ID, ADTCOL) VALUES(?, ?)");

     ps.setNUMBER (1, id);
     ps.setSTRUCT (2, adt);
     ps.execute ();
     ps.close();


//retrieves a user-defined type as oracle.sql.STRUCT
//
    OraclePreparedStatement ps =
       (OraclePreparedStatement) conn.prepareStatement
            ("SELECT ADTCOL FROM ADTTAB2 WHERE ID = ?");

     ps.setNUMBER (1, id);
     OracleResultSet rs = (OracleResultSet) ps.executeQuery();

     STRUCT st = null;
     while (rs.next())
     {
      st = (STRUCT) rs.getObject(1);
     }
     ps.close();
     return st;

See a complete code sample in Chapter 3.

Strong Type Mapping: SQLData and ORAData

SQLData

The java.sql.SQLData interface allows custom mapping of a user-defined SQL type to a Java class that implements this interface. The Java class must define the mapping in a type-map; it must also define a field for each attribute of the structured SQL type, and finally it must contain the following methods:

  • getSQLTypeName(): Returns the fully qualified name of the user-defined SQL type that this object represents.

  • readSQL(): Retrieves the state of the Java object with data read from the database, using a SQLInput stream. It calls the appropriate readXXX() methods (i.e., readInt, readLong, readFloat, readString, readBlob, readObject, etc.) of the SQLInput object.

    public void readSQL(SQLInput stream, String
    sql_type_name) throws SQLException
  • writeSQL(): Stores the state of the Java object back to the database using a SQLOutput stream. It calls the appropriate writeXXX() methods (i.e., writeInt, writeLong, writeString, writeObject) of the SQLOutput object.

public void writeSQL(SQLOutput stream) throws
SQLException

To read data from an Oracle object, the driver determines the Java class to use for converting the data from SQL format to Java attributes by looking up the type-map.

ResultSet rset =
         stmt.executeQuery ("select value(t) from
ClientTab t");
    while (rset.next ())
    {
      System.out.println(rset.getObject(1));
    }

The ResultSet.getObject() method maps the user-defined type to an instance of the implementation class. If the type-map is not specified, the Oracle object is materialized as an oracle.sql.STRUCT.

Conversely, for storing the Java class attributes into an Oracle object, the driver gets the SQL type name from the Java class by calling the getSQLTypeName() method of the SQLData interface.The SQLData object must be assigned the OracleTypes.STRUCT typecode.

pstmt.setObject(<index>, <SQLData object>,
OracleTypes.STRUCT);

The driver furnishes the SQLInput implementation class, which is an input stream passed toreadSQL(), and the SQLOutput implementation class, which is an output stream passed to writeSQL(). These classes are used by the SQLData methods, not by the application code.

Assume the following object type:

CREATE or REPLACE TYPE Client_t AS OBJECT
 (
  ssn NUMBER(10),
  name  VARCHAR2(30),
  address    VARCHAR2(100)
);
/

The following Java class manually implements SQLData for mapping the SQL CLIENT_T Object type; the JPublisher utility covered later generates this for you.

import java.sql.*;

public class jClient_t implements SQLData
{
  private String sql_type;
  public int ssn;
  public String name;
  public String address;

  public jClient_t () {}

  public jClient_t (String sql_type, int ssn, String name,
String address)
  {
    this.sql_type = sql_type;
    this.ssn = ssn;
    this.name = name;
    this.address = address;
  }
  public String getSQLTypeName() throws SQLException
  {
    return sql_type;
  }

  public void readSQL(SQLInput stream, String typeName)
throws SQLException {
    sql_type = typeName;
    ssn = stream.readInt();
    name = stream.readString();
    address = stream.readString();
  }
  public void writeSQL(SQLOutput stream) throws SQLException
{
    stream.writeInt(ssn);
    stream.writeString(name);
    stream.writeString(address);
  }

  public String toString ()
  {
   return sql_type + " = " + ssn + ", " + name + ", " +
address;
  }
}

A JDBC application would use this as follows:

  1. Create the Client_t type and the corresponding Object table either directly through SQL or programmatically, as follows:

     // Create the SQL object type "Client_t".
        stmt.execute ("CREATE TYPE Client_t AS OBJECT      "+
                      "(  ssn NUMBER,                      "+
                      "   name VARCHAR2(30),               "+
                      "   address VARCHAR2(100))           ");
    
         stmt.execute ("CREATE TABLE ClientTab of Client_t");
  2. Specify the mapping and insert instances of Client_t:

    // Map jClient_t class to Oracle object Client_t type
        map.put ("SCOTT.Client_t", Class.forName
        ("jClient_t"));
    
    PreparedStatement pstmt =
          conn.prepareStatement ("insert into ClentTab
    values (?)");
    
    
       // Insert a Client_t object into the database
          Object client = new jClient_ ("SCOTT.Client_t",
    981, "Adam", " 203 Chapms Elysses - Paris France");
          pstmt.setObject (1, client, OracleTypes.STRUCT);
          pstmt.executeUpdate ();

    The setObject() method invokes writeSQL(), which in turn invokes the writeXXX() per the SQLData.

  3. Retrieve instances of Client_t:

        Statement stmt = conn.createStatement ();
        ResultSet rset =
             stmt.executeQuery ("select value(o) from
    ClientTab o");
        while (rset.next ())
        {
          System.out.println(rset.getObject(1));
    
        }

The getObject() method invokes readSQL(), which in turn invokes the readXXX() per the SQLData.

ORAData and ORADataFactory

The oracle.sql.ORAData interface, which is a replacement for the oracle.sql.CustomDatum interface, is a more flexible, optimized, but proprietary alternative to SQLData. It allows custom mapping and processing of Oracle Objects as well as any other Oracle SQL type (i.e., any data type available in the Oracle database).

For example, beyond custom type-mapping, you can use ORAData to implement custom processing such as data encryption, decryption, validation, logging, Java object serialization and deserialization, character conversion and formatting, and so on. It does not require a type-map because it uses oracle.sql.Datum directly. The Java class, which implements the oracle.jdbc.ORAData interface, contains the following static fields and methods:

public static final String _SQL_NAME = "<schema>.<Object type>";
public static final int _SQL_TYPECODE = OracleTypes.STRUCT;
public static ORADataFactory getFactory() { ... }

Note

The public static final field _SQL_TYPECODE is an alternative

  way of specifying the type map.

  public Datum toDatum(java.sql.Connection c)
              throws java.sql.SQLException

The oracle.sql.Datum type is the internal format used by the driver to hold Oracle objects (it was introduced at the beginning of this long section). The toDatum() method retrieves the corresponding Datum object from the ORAData object.

The ORADataFactory interface acts as a constructor of ORAData instances. It provides the following methods for creating customized ORAData from a Datum:

public ORAData create(Datum d,
                      int sqlType)
               throws java.sql.SQLException

The following Java class manually implements ORAData and ORADataFactory for mapping the SQL CLIENT_T Object type; the JPublisher utility covered later generates this for you.

import java.sql.*;
import oracle.sql.*;

public class Client_t implements ORAData, ORADataFactory
{
  static final Client_t factory = new Client_t ();
  String sql_type = "SCOTT.CLIENT_T";
  public int ssn;
  public String name;
  public String address;

  public static ORADataFactory getFactory()
  {
    return _factory;
  }

  public Client_t () {}

  public Client_t (int ssn, String name, String address)
  {
    this.ssn = ssn;
    this.name = name;
    this.address = address;
  }

  public Datum toDatum(Connection conn)
    throws SQLException
  {
    StructDescriptor strdesc =
     StructDescriptor.createDescriptor(sql_type, conn);
    Object [] attribs = {  new Integer(ssn), name, address };
    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 Address (attribs[0].intValue (),
                        attribs[1].stringValue (),
                        attribs[2].stringValue ());
  }

  public String toString ()
  {
    return sql_type + " = " + ssn + ", " + name + ", " + address;
  }
}

To read data from an Oracle object, the JDBC application may use the getORAData() or getObject() methods; however, getObject() requires a type-map, similar to the SQLData approach.

// Using getORAData()
OraleResultSet  rset = (OracleResultSet)stmt.executeQuery
                         ("SELECT value(o) from ClientTab o");
Client_t client =
       (Client_t)rset.getORAData(1, Client_t.getFactory());

To write data into an Oracle object, the JDBC application may use the setORAData() or setObject() methods.

  PreparedStatement pstmt =
      conn.prepareStatement ("insert into ClentTab values (?)");

  Object client = new jClient_ ("SCOTT.Client_t", 1343, "Julie",
             " 203 Fillmore St – 92 xxx San Francisco USA");
  pstmt.setORAData(1, client);
  pstmt.executeUpdate ();

The setObject() method in ORAData works similar to the SQLDAta counterpart.

SQLJ Object Types

The SQLJ Object type is a special Object type specified by ANSI SQLJ Part II. It allows constructing user-defined SQL types, using Java classes that implement the SQLData or ORAData interfaces.

CREATE TYPE <SQLJ object>  AS OBJECT
 [ EXTERNAL NAME '<Class name>'  LANGUAGE JAVA ]
  USING SQLData|ORAdata (...);

The attributes, the setters, and the getters of the Java class can be mapped to SQL through the EXTERNAL NAME clause; more specifically, public class methods (i.e., public static ) map to STATIC FUNCTION or STATIC PROCEDURE, and public instance methods (i.e., public ) map to MEMBER FUNCTION orMEMBER PROCEDURE.

The SQLJ object type may also be created as a usual SQL object type (i.e., without a base Java class), but the “externalized” methods must be implemented by Java methods.

The oracle.sql.JAVA_STRUCT class, which extends the oracle.sql.STRUCT, and the OracleTypes.JAVA_STRUCT typecode represent such object types.

Here are the required steps for creating a SQLJ Obect type, as illustrated by Listing 8.3:

  1. Create a Java class implementing the ORAData and ORADataFactory interfaces.

  2. Load the Java class into the database.

  3. Issue the SQL CREATE TYPE command.

Example 8.3. Paymaster.java

import java.sql.*;
import java.io.*;
import oracle.sql.*;
import oracle.jdbc.*;
import java.math.*;
public class Paymaster implements SQLData {
  // Implement the attributes and operations for this type.
  private BigDecimal empno;
  private String ename;
  private String job;
  private BigDecimal mgr;
  private Date hiredate;
  private BigDecimal sal;
  private BigDecimal comm;
  private Ref dept;

  public BigDecimal wages() {
    BigDecimal pay = sal;
    if (comm != null) pay = pay.add(comm);
    return pay;
  }
  public void raiseSal(BigDecimal amount) {
    //
    sal = sal.add(amount);
    System.out.println(sal);
  }


  // Implement SQLData interface.
  String sql_type;

  public String getSQLTypeName() throws SQLException {
    return sql_type;
  }

  public void readSQL(SQLInput stream, String typeName)
    throws SQLException {
    sql_type = typeName;
    empno = stream.readBigDecimal();
    ename = stream.readString();
    job = stream.readString();
    mgr = stream.readBigDecimal();
    hiredate = stream.readDate();
    sal = stream.readBigDecimal();
    comm = stream.readBigDecimal();
    dept = stream.readRef();
  }

  public void writeSQL(SQLOutput stream) throws SQLException
{
    stream.writeBigDecimal(empno);
    stream.writeString(ename);
    stream.writeString(job);
    stream.writeBigDecimal(mgr);
    stream.writeDate(hiredate);
    stream.writeBigDecimal(sal);
    stream.writeBigDecimal(comm);
    stream.writeRef(dept);
  }
}

$ javac Paymaster.java
$ loadjava -u user/pass Paymaster.class

Paymaster.sql

----------------
drop table employee_tab;
drop table dept_tab;
drop type employee;
drop type department;

CREATE TYPE Department AS OBJECT (
  deptno NUMBER(2),
  dname  VARCHAR2(14),
  loc    VARCHAR2(13)
);
/
CREATE or replace TYPE Employee AS OBJECT (
  empno    NUMBER(4),
  ename    VARCHAR2(10),
  job       VARCHAR2(9),
  mgr      NUMBER(4),
  hiredate DATE,
  sal       NUMBER(7,2),
  comm      NUMBER(7,2),
  deptno   REF Department,
  MEMBER FUNCTION wages RETURN NUMBER
    AS LANGUAGE JAVA
    NAME 'Paymaster.wages() return java.math.BigDecimal',

 MEMBER PROCEDURE raise_sal (r NUMBER)
    AS LANGUAGE JAVA
    NAME 'Paymaster.raiseSal(java.math.BigDecimal)'

);
/
create table employee_tab of employee;
create table dept_tab of department;
insert into dept_tab values (department(1, 'physics',
'sample'));
insert into employee_tab values (employee(1, 'kuassi', 'sw',
10, '13-dec-2005', 1000, 1000, (select ref(d) from dept_tab
d)));

select value(a).wages() from employee_tab a;
$ sqlplus user/pass @Paymaster

REF

The SQL REF type represents references (pointer) to objects of a specified type such as REF of ADT types, REF of X. In SQL and PL/SQL, REF of X can be created in SQL by querying the X object and applying the REF operator. Similarly, to access the object referenced by a REF, you dereference the REF, using the Oracle-supplied DEREF operator.

The Oracle JDBC oracle.sql.REF class wraps the SQL REF data type and implements the java.sql.Ref interface. The java.sql.Ref interface furnishes the following methods:

  • getBaseTypeName(): Retrieves the fully qualified SQL name of the SQL structured type that this Ref object references

  • getObject(): Retrieves the SQL structured type instance referenced by this Ref object

  • getObject(Map): Retrieves the referenced object and maps it to a Java type using the given type-map

  • setObject(Object): Sets the structured type value that this Ref object references to the given instance of Object

The OraclePreparedStatement, OracleCallableStatement, and OracleResultSet furnish the getREF, getRef, setREF, setRef, setREFAtName, setRefType, setRefTypeAtName, updateREF, getObject, andsetObject methods. Unlike SQL, in JDBC, application codes cannot create an instance of oracle.sql.REF, only the driver may.

Manipulating REF of ADT Table Types

Assume a user-defined object type ADTtyp, a table ADTtab of ADTtyp objects, and a table of REF of ADTtyp:

create type ADTtyp as OBJECT (a1 number, a2 varchar2(20), a3
date)
/
create table ADTtab of ADTtyp
/
create table REFtab (id number, refcol REF ADTtyp)
/

Prepopulated as follows:

insert into ADTtab values (ADTtyp(1, 'One', '01-JAN-2001'));
insert into REFtab select 1, REF(R2) from ADTtab R2 where
R2.a1 = 1;
commit;

The following code snippet inserts anoracle.sql.REF into the REFtab table:

OraclePreparedStatement ps = (OraclePreparedStatement)
    conn.prepareStatement("INSERT INTO REFTAB (ID, REFCOL)
VALUES(?, ?)");

    ps.setNUMBER (1, id);
    ps.setREF (2, rf);
    ps.execute ();    

The following code snippet retrieves and returns an instance ofREF ADTTyp as oracle.sql.REF:

   OraclePreparedStatement ps = (OraclePreparedStatement)
      conn.prepareStatement ("SELECT REFCOL FROM REFTAB WHERE
ID = ?");

    ps.setNUMBER (1, id);
    OracleResultSet rs = (OracleResultSet) ps.executeQuery();

    REF r = null;
    while (rs.next())
    {
      r = (REF) rs.getObject(1);
    }
    ps.close();
    return r;

REF Cursors

Cursors contain query results and metadata. A REF Cursor (or cursor variable) data type contains a reference to a cursor. It can be passed between the RDBMS and the client, or between PL/SQL and Java in the database; it can also be returned from a query or a stored procedure. The Oracle JDBC furnishes the getCursor and setCursorAtName methods for manipulating Ref Cursor as ResultSet and OracleResultSet. Setting setCreateStatementAsRefCursor() to true turns any statements created from this connection into a REF CURSOR.

Returning a REF Cursor as a java.sql.ResultSet:

...

 ((OracleConnection)conn).setCreateStatementAsRefCursor(true);
     Statement stmt = conn.createStatement();
     ((OracleStatement)stmt).setRowPrefetch(1);

     ResultSet rset =
        stmt.executeQuery("select * from EMP order by empno");
        rset = ((OracleResultSet)rset).getCursor(i);
     return rset;

For CallableStatement, the RefCursor must be declared as an OUT parameter with the CURSOR typecode:

cstmt.registerOutParameter(1,OracleTypes.CURSOR);
...

See a complete example in Chapter 3.

JDBC Support for User-Defined Collections

User-defined collections include VARRAYs, Nested Tables, andPL/SQL Associative Arrays.

ARRAY, VARRAYs, and ArrayDescriptor

The Oracle database furnishes only variable-length arrays (i.e., VARRAYs) as part of the user-defined SQL collection types. They represent an ordered set of elements of the same type (scalar or complex types) and are used to map array data types in other languages. VARRAY data are stored in line in table columns; however, when their size is larger than 4 K, or when a storage clause is specified, these are stored outline, as BLOB data. The maximum number of elements in a VARRAY must be specified at creation time.

The Oracle JDBC drivers support the SQL VARRAY through the oracle.sql.ARRAY wrapperclass (which implements the standard java.sql.Array interface) and the oracle.sql.ArrayDescriptor class.

The oracle.sql.ARRAY furnishes the following methods: getArray() with various signatures (see the Oracle JDBC javadoc), getBaseType(), getBaseTypeName(), getDescriptor(), getDoubleArray(), getFloatArray(), getIntArray(), getLongArray(), getResultSet(), getJavaSQLConnection(), toJdbc(), and so on

The oracle.sql.ArrayDescriptor furnishes the following methods: createDescriptor(), dscType(), getArrayType(), getBaseName(), getBaseType(), getTypeCode(), getMaxLength(), getArrayType() (i.e., TYPE_VARRAY and TYPE_NESTED_TABLE), toResultSet(), and so on.

The OraclePreparedStatement, OracleCallableStatement, and OracleResultSet furnishthe setArray, setArrayAtName, setARRAY, setARRAYAtName, getArray, getARRAY, updateArray, and updateARRAY methods.These methods can be used directly to manipulate arrays of scalar (built-in) SQL types. However,if the elements of the SQL Array are user-defined types, the java.sql.SQLData or oracle.sql.ORAData classes and a type-map must be used to manipulate the elements of the Java array, similar to SQL Object types, covered earlier.

The following snippets excerpted from Chapter 3 illustrate the manipulation of VARRAY of scalar types, including VARRAY of NUMBER, VARRAY of VARCHAR2, and VARRAY of DATE.

VARRAY of NUMBER

Assume the following NVARRAY type and instance:

create or replace type NVARRAY as VARRAY(10) of number;
 NVARRAY(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// The following code snippet inserts a java.sql.Array into
// VarrayTab table as NVARRAY

    Statement stmt = conn.createStatement();

    OraclePreparedStatement ps = (OraclePreparedStatement)
    conn.prepareStatement("INSERT INTO VarrayTab (ID, NVA)
VALUES(?, ?)");
    ps.setNUMBER (1, id[0]);
    ps.setARRAY (2, (ARRAY)va[0]);
    ps.execute ();

// The following code snippet retrieves and returns an NVARRAY
// as a java.sql.Array

    OraclePreparedStatement ps = (OraclePreparedStatement)
    conn.prepareStatement ("SELECT NVA FROM VarrayTab WHERE ID
= ? ");
     ps.setNUMBER (1, id[0]);
     OracleResultSet rs = (OracleResultSet) ps.executeQuery();
     Array a = null;
     while (rs.next())
     {
       a = (Array) rs.getObject(1);
     }
     ps.close();
     return a;

See the complete code sample in Chapter 3.

VARRAY of VARCHAR2

Assume the following VC2VARRAY type and instance:

create or replace type VC2VARRAY as VARRAY(10) of
varchar2(30);
VC2VARRAY('Thirty One', 'Thirty Two', 'Thirty Three',
              'Thirty Four', 'Thirty Five', 'Thirty Six',
              'Thirty Seven', 'Thirty Eight')

// The following code snippet inserts a java.sql.Array row
// into VC2VARRAY as a java.sql.Array

    Statement stmt = conn.createStatement();

    OraclePreparedStatement ps = (OraclePreparedStatement)
    conn.prepareStatement("INSERT INTO VarrayTab(ID, VC2VA)
VALUES(?, ?)");

    ps.setNUMBER (1, id[0]);
    ps.setARRAY (2, (ARRAY)va[0]);
    ps.execute ();
    ps.close();


// The following code snippet retrieves and returns a
// VC2VARRAY as a java.sql.Array

    OraclePreparedStatement ps = (OraclePreparedStatement)
    conn.prepareStatement ("SELECT VC2VA FROM VarrayTab WHERE
ID = ? ");
    ps.setNUMBER (1, id[0]);
    OracleResultSet rs = (OracleResultSet) ps.executeQuery();
    Array a = null;
    while (rs.next())
    {
      a = (Array) rs.getObject(1);
    }
    ps.close();
    return a;

See the complete code sample in Chapter 3.

VARRAY of DATE

Assume the following DATVARRAY and instance:

create or replace type DATVARRAY as VARRAY(10) of date;
DATVARRAY('01-JAN-2005', '02-JAN-2005', '03-JAN-2005', '04-
JAN-2005',
              '05-JAN-2005', '06-JAN-2005', '07-JAN-2005',
              '08-JAN-2005','09-JAN-2005', '10-JAN-2005')

// The following code snippet inserts a java.sql.Array
// row into VarrayTab table as DATVARRAY

    Statement stmt = conn.createStatement();

    OraclePreparedStatement ps = (OraclePreparedStatement)
       conn.prepareStatement ("INSERT INTO VarrayTab (ID,
DATVA)
         VALUES(?, ?)");
    ps.setNUMBER (1, id[0]);
    ps.setARRAY (2, (ARRAY)va[0]);
    ps.execute ();
    ps.close();
    conn.commit();

// The following code snippet retrieves and returns
// a DATVARRAY as a java.sql.Array

    OraclePreparedStatement ps = (OraclePreparedStatement)
    conn.prepareStatement ("SELECT DATVA FROM VarrayTab WHERE
ID = ? ");

     ps.setNUMBER (1, id[0]);
     OracleResultSet rs = (OracleResultSet) ps.executeQuery();
     Array a = null;
     while (rs.next())
     {
       a = (Array) rs.getObject(1);
     }
     ps.close();
     return a;

See the complete code sample in Chapter 3.

Nested Tables

Nested Tables, or tables within a table, are part of the user-defined SQL collection types. They define a type, which represents an unordered set of elements of the same type and are used to map sets and bag data types in other languages. The nested table columns of a table are stored out of line from the rows of the parent table, using the “store as” clause (see the Oracle SQL Reference Guide). Unlike VARRAYs, which have fixed boundaries, Nested Tables are more flexible because the size can dynamically grow or shrink; however, there is no such thing as a free lunch, so the size change will be at the expense of more storage. If the elements of the Nested Tables are user-defined types, the java.sql.SQLData or oracle.sql.ORAData classes and a type-map must be used to manipulate the elements of the Java arrays, similar to SQL Object types, covered earlier.

The following snippets excerpted from Chapter 3 illustrate the manipulation of NESTED TABLE of scalar types, including NESTED TABLE of NUMBER, NESTED TABLE of VARCHAR2, andNESTED TABLE of DATE.

NESTED TABLE of NUMBER

Assume NTab_Num a nested table of type numnt store as NSTabNum and an instance declared as follows:

NTab_Num(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// The following code snippet inserts a java.sql.Array row
// into NSTableTab as NTab_Num

    Statement stmt = conn.createStatement();
    OraclePreparedStatement ps = (OraclePreparedStatement)
       conn.prepareStatement ("INSERT INTO NSTableTab (ID,
NUMNT)
         VALUES(?, ?)");

    ps.setNUMBER (1, id[0]);
    ps.setARRAY (2, (ARRAY) nt[0]);
    ps.execute ();
    ps.close();

// The following code snippet retrieves and returns a NTab_Num
// as a java.sql.Array

    OraclePreparedStatement ps = (OraclePreparedStatement)
       conn.prepareStatement ("SELECT NUMNT FROM NSTableTab
                 WHERE ID = ?");
    ps.setNUMBER (1, id[0]);
    OracleResultSet rs = (OracleResultSet) ps.executeQuery();
    Array a = null;
    while (rs.next())
    {
      a = (Array) rs.getObject(1);
    }
    ps.close();
    return a;

See the complete code sample in Chapter 3.

NESTED TABLE of VARCHAR2

Assume NTab_Vc2 a nested table of type vc2nt store as NSTabVc2 and an instance declared as follows:

NTab_Vc2('One', 'Two', 'Three', 'Four', 'Five', 'Six',
'Seven',
              'Eight', 'Nine', 'Ten')

// The following code snippet inserts a java.sql.Array row
// into NSTableTab as NTab_Vc2

Statement stmt = conn.createStatement();

 OraclePreparedStatement ps = (OraclePreparedStatement)
 conn.prepareStatement("INSERT INTO NSTableTab(ID,VC2NT)
VALUES(?, ?)");

    ps.setNUMBER (1, id[0]);
    ps.setARRAY (2, (ARRAY) nt[0]);
    ps.execute ();
    ps.close();

 // The following code snippet retrieves and returns a
 // NTab_Vc2 as a java.sql.Array

    OraclePreparedStatement ps = (OraclePreparedStatement)
       conn.prepareStatement ("SELECT VC2NT FROM NSTableTab
WHERE ID = ?");

     ps.setNUMBER (1, id[0]);
     OracleResultSet rs = (OracleResultSet) ps.executeQuery();
     Array a = null;
     while (rs.next())
     {
       a = (Array) rs.getObject(1);
     }
     ps.close();
     return a;

See the complete code sample in Chapter 3.

NESTED TABLE of DATE

Assume NTab_Dat a nested table of type datnt store as NSTabDat and an instance declared as follows:

NTab_Dat('01-JAN-2003', '02-JAN-2003', '03-JAN-2003', '04-
JAN-2003',
           '05-JAN-2003', '06-JAN-2003', '07-JAN-2003', '08-
JAN-2003',
           '09-JAN-2003', '10-JAN-2003')

// The following code snippet inserts a java.sql.Array row
// into NSTableTab as NTab_Dat

 Statement stmt = conn.createStatement();

 OraclePreparedStatement ps = (OraclePreparedStatement)
 conn.prepareStatement("INSERT INTO NSTableTab(ID,DATNT)
VALUES(?, ?)");
    ps.setNUMBER (1, id[0]);
    ps.setARRAY (2, (ARRAY) nt[0]);
    ps.execute ();
    ps.close();

    id[0] = new NUMBER(id[0].intValue() + 1000);
    nt[0] = nt2;
  }

// The following code snippet retrieves and returns a NTab_Dat
// as a java.sql.Array

  Statement stmt = conn.createStatement();

  OraclePreparedStatement ps = (OraclePreparedStatement)
    conn.prepareStatement ("SELECT DATNT FROM NSTableTab WHERE
ID = ?");

    ps.setNUMBER (1, id[0]);
    OracleResultSet rs = (OracleResultSet) ps.executeQuery();

    Array a = null;
    while (rs.next())
    {
      a = (Array) rs.getObject(1);
    }
    ps.close();
    id[0] = new NUMBER(id[0].intValue() + 1000);
    nt[0] = nt2;

    return a;

See the complete code sample in Chapter 3.

Alternatively to mapping NESTED TABLE to oracle.sql.VARRAY and java.sql.Array, you may perfom custom mapping of NESTED TABLEs, using classes that implement ORAData.

  • The custom collection must have 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";

PL/SQL Associative Array

The PL/SQL Associative Array type (previously PL/SQL Index-by Table) is a hash table of key/value pairs, which models unordered lists, trees, maps, dictionaries, and lookup tables found in other languages (e.g., a phone book of name/phone numbers, a dictionary of words, definition).

  • The keys have unique values that locate the element in the array; it can be of type number, string values, even user-defined subtypes, positive, negative, nonsequential, and expressed by the INDEX BY clause, including INDEX BY BINARY_INTEGER PLS_INTEGER, VARCHAR2(<size>), POSITIVE, NEGATIVE, NATURAL, and so on. However, the Oracle JDBC currently supports only number keys.

  • The values (elements of table) can be of all valid SQL or PL/SQL types. The Oracle JDBC currently supports only number and charac ter as value elements. RAW, DATE, CLOB, BLOB, TIMESTAMP[[L]TZ], INTEVALYM/INTERVADS, PL/SQL Records, and ADTs are currently not supported as element types.

PL/SQL Associative Arrays are similar to Nested Tables in terms of their structure (i.e., unbound, single dimension, all elements must be of the same type), but unlike nested tables, the former can only be used in PL/SQL, are sparse, have no storage clause, and are easily extensible. They are constructed in memory each time the corresponding package is initialized or at the invocation of corresponding procedure(s).

The following PL/SQL methods are available: COUNT, DELETE, EXISTS(n), FIRST, LAST, PRIOR(n), andNEXT(n). See the PL/SQL Users Guide for more details.

The Oracle JDBC allows applications to bind PL/SQL Associative Array types as IN, OUT, andIN/OUT parameters. Prior to Oracle JDBC 10g Release 2, PL/SQL Associate Arrays were supported by JDBC-OCI only, but with 10g Release 2, both driver types support these.

The Oracle JDBC supports PL/SQL Associative Array through the setPlsqlIndexTable(), registerIndexTableOutParameter(), getOraclePlsqlIndexTable(), and getPlsqlIndexTable() methods in OraclePreparedStatement and OraleCallableStatement.

  • setPlsqlIndexTable(): In OraclePreparedStatement and OracleCallableStatement for binding a PL/SQL associative array as IN parameter:

    setPlsqlIndexTable(int parameterIndex,
              java.lang.Object arrayData,
              int maxLen,
              int curLen,
              int elemSqlType,
              int elemMaxLen)
    
    // bind IN parameter
    proc.setPlsqlIndexTable (1, values,
                  maxLen, currentLen,
                  elemSqlType, elemMaxLen);
  • registerIndexTableOutParameter(): In OracleCallableStatement for registering the OUT parameter:

    registerIndexTableOutParameter(int paramIndex,
            int maxLen,
            int elemSqlType,
            int elemMaxLen)
    
    // register OUT parameter
        proc1.registerIndexTableOutParameter (2, maxLen,
                                                elemSqlType,
    elemMaxLen);

    The following methods return elements of associative arrays using either JDBC default mapping, Oracle mapping, or Java primitive type-mapping:

  • public java.lang.Object getPlsqlIndexTable(int paramIndex): Returns the elements of a PL/SQL index table as Java array using the JDBC default mappings.

    NUMERIC elements -> BigDecimal array (BigDecimal[] )
    VARCHAR elements   -> String array (String[])
    
    // retrieve the elements using JDBC default mapping
        BigDecimal[] elements =
          (BigDecimal[]) proc1.getPlsqlIndexTable (2);
  • public Datum[] getOraclePlsqlIndexTable(int paramIndex): JDBC-OCI driver specific; returns the elements of a PL/SQL index table as an oracle.sql.Datum array.

    NUMBER elements -> oracle.sql.Datum array (Datum[])
        VARCHAR elements -> oracle.sql.Datum array (Datum[])
    
    // retrieve the elements using Oracle JDBC mapping
    Datum[] elements = proc2.getOraclePlsqlIndexTable (2);
  • synchronized public Object getPlsqlIndexTable(int paramIndex, Class primitiveType): Returns the elements of index-by-table as array of Java primitive types. The possible primi-

    tiveType        values     are      java.lang.Integer.TYPE ,
    java.lang.Long.TYPE,                   java.lang.Float.TYPE,
    java.lang.Double.TYPE, and java.lang.Short.TYPE.
    
           java.lang.Integer.TYPE -> int array (int[])
           java.lang.Float.TYPE   -> double array (double[])
           java.lang.Short.TYPE   -> short array (short[])
    
              // retrieve the elements  as a Java primitive array.
              int[] element = (int[])
                 func1.getPlsqlIndexTable (2,
           java.lang.Integer.TYPE);

In order to use PL/SQL associative arrays, the following prerequisite steps must be performed either in a separate SQL session or directly within JDBC (see following example):

Step 1. Create a PL/SQL package and the PL/SQL associative array of the scalar type:

TYPE <index-by-table-type> IS TABLE OF <scalar>
                 INDEX BY BINARY_INTEGER

  CREATE OR REPLACE PACKAGE mypkg AS TYPE plsqldxtyp IS
        TABLE OF VARCHAR2(20) INDEX BY BINARY_INTEGER;
  END;

Step 2. Create PL/SQL procedures and/or functions, which take the new type as IN and/or OUT and/or IN/OUT parameter(s) and manipulate the associative array (e.g., update the elements):

CREATE TABLE plsqlidxtab (col VARCHAR2(30));

create or replace procedure proc1
(feedback OUT VARCHAR2(30),  p1 in mypkg.plsqlidxtyp) is
    begin
        for i in p1.FIRST..p1.LAST loop
        insert into plsqlidxtab (plsqlidxtyp(i));
        end loop;
       feedback := 'PLSQL ASSOC ARRAY ROCKS';
    end;          

Listing 8.4 illustrates PL/SQL Associative Array.

Example 8.4. myPLSQLIndexTab.java

import java.sql.*;
import java.util.*;
import java.io.*;
import oracle.jdbc.*;
import oracle.sql.*;
import oracle.jdbc.pool.*;
import oracle.jdbc.pool.OracleDataSource;

public class myPLSQLIndexTab
{
  public static void main(String[] args) throws SQLException
  {
   //Create an OracleDataSource
   OracleDataSource ods = new OracleDataSource();
   // Set the URL, using TNS Alias with JDBC-Thin
   String url = "jdbc:oracle:thin:scott/tiger@inst1";
   ods.setURL(url);
   // Retrieve a connection
   OracleConnection conn = (OracleConnection)ods.getConnection();
   Statement stmt = conn.createStatement ();
   try {

    // define PL/SQL Index by table
    stmt.execute ("create or replace package mypkg as type plsidxtyp "+
                  " is table of varchar2(20) index by binary_integer; "+
                  "end;");

    // create procedure p1
    stmt.execute ("create or replace procedure proc1 "+
              "(p1 in out mypkg.plsidxtyp) is " +
              "begin "+
              " for i in p1.FIRST..p1.LAST loop "+
              " p1(i) := p1(i)|| ' out'; "+
              " end loop; "+
              "end;");
   stmt.close();
    // set the table values
    String [] plsqlidxarr1 =
      { "element1","element2","element3", "element4" };

    // prepare the invocation of the stored procedue
    OracleCallableStatement cstmt = (OracleCallableStatement)
      conn.prepareCall ("begin proc1 (?); end;");

    // actual size of the index-by table bind value
    int currentLen = plsqlidxarr1.length;
    int maxLen = currentLen;

    // index-by table element type
    int elemSqlType =  OracleTypes.VARCHAR;

    // index-by table element length
    int elemMaxLen = 20;

     // Bind the IN parameter
     cstmt.setPlsqlIndexTable (1, plsqlidxarr1,
                         maxLen, currentLen,
                         elemSqlType, elemMaxLen);
     // Register OUT parameter
     // cstmt.registerOutParameter (1, Types.CHAR);
    // register the OUT parameter
    cstmt.registerIndexTableOutParameter (1, maxLen,
                      elemSqlType, elemMaxLen);
    // execute the call
    cstmt.execute ();

     // retrieve the elements using JDBC default mapping
     String[] elements =
       (String[]) cstmt.getPlsqlIndexTable (1);

    // print the elements
    for (int i=0; i<elements.length; i++)
     System.out.println(" " + elements[i]);

     cstmt.close();
    } catch (SQLException ea) {
      ea.printStackTrace();
    }
  }
}
$ javac myPLSQLIndexTab.java
$ java -Doracle.net.tns_admin=$TNS_ADMIN myPLSQLIndexTab
  element1 out
  element2 out
  element3 out
  element4 out
$

PL/SQL Record and PL/SQL Boolean

These data types are not currently supported by Oracle JDBC; for example, you cannot call a PL/SQL procedure that has a Record parameter. However, using JPublisher (covered later), a Java wrapper is generated that calls the procedure in question.

JDBC Support for Spatial Types

The SDO_Geometry, SDO_Topo_Geometry, andSDO_GeoRaster data types are not currently supported natively by the Oracle JDBC drivers; however, the Oracle inter Media and JPublisher furnish ways to manipulate these types (see the interMedia Case Study in Part VI).

Unsupported Types

As of Oracle Database 10g Release 2, the Oracle JDBC does not currently expose the following Oracle database supplied types to end users:

  • Media types

  • Expression filter types

  • URIType

  • Any types: SYS.AnyData, SYS.AnyType, and SYS.AnyDataSet

Result Set Support in Oracle JDBC

With connections and statements, result sets are one of the key JDBC features. In the previous section, we covered (among other things) the set of getXXX() methods for retrieving column data types that made a result set row. This section focuses on the result set types, their implementation in Oracle JDBC drivers, and methods for retrieving data and navigating through rows.

The Result Set API in a Nutshell

Result sets are Java objects that contain the set of rows and columns that result from the execution of a SQL query. The getXXX() and updateXXX() methods allow the retrieval, updating, and inserting of rows from/in the result set. A cursor controls positioning and how the rows are being returned/consumed by the requester. First, let’s look at the chronological evolution of the Result Set API.

The Evolution of the Result Set API

Since its introduction in JDBC 1.22, result sets have evolved into various types with additional capabilities. As discussed in the overview of the JDBC specifications in Chapter 6:

  • JDBC 1.22 specifiesjava.sql.ResultSet forward-only and readonly modes for managing the result set from the execution of a given JDBC statement.

  • JDBC 2.0 (core API) specifies scrollable and updatable result set (supports insert, update, and delete), which implements SQL-92 scrollable cursors. The result set maintains a positional cursor, which points to the current row. The movements of the cursor are controlled by the following methods on the result set object: first(), last(), beforeFirst(), afterLast(), next(), previous(), relative(), absolute(), and Hints, which specify the direction in which result sets will be processed (FETCH_FORWARD, FETCH_REVERSE), and fetch size.

  • JDBC 3.0 specifies the getMoreResults() method for controlling how to handle a previously opened result set upon the invocation of getResultSet(); in addition, JDBC 3.0 also introducesResultSet Holdability as theability to specify whether cursors should be held open or closed at the end of a transaction using CLOSE_CURSORS_AT_COMMIT orHOLD_CURSORS_OVER_COMMIT.

Methods Specified by the java.sql.ResultSet Interface

Methods for navigation and row manipulation include: absolute, afterLast, beforeFirst, getFetchDirection, isAfterLast, isBeforeFirst, isFirst, isLast, last, moveToCurrentRow, moveToInsertRow, next, previous, refreshRow, relative, rowDeleted, rowInserted, rowUpdated, getRow, cancelRowUpdates, clearWarnings, close, deleteRow, findColumn, first, setFetchDirection, setFetchSize.

Methods for retrieving and updating Oracle database SQL types to and from Java types (i.e., java.sql.*) include: getArray, getAsciiStream, getBigDecimal, getBinaryStream, getBlob, getBoolean, getBytes, getCharacterStream, getClob, getConcurrency, getCursorName, getDate, getDouble, getFetchSize, getFloat, getFloat, getInt, getInt, getLong, getLong, getMetaData, getObject, getRef, getShort, getShort, getStatement, getString, getString, getTime, getTime, getTime, getTime, getTimestamp, getTimestamp, getTimestamp, getTimestamp, getType, getUnicodeStream, getUnicodeStream, getURL, updateArray, updateArray, updateAsciiStream, updateBig-Decimal, updateBinaryStream, updateBlob, updateBoolean, updateByte, updateBytes, updateCharacterStream, updateClob, updateDate, updateDouble, updateFloat, updateInt, updateLong, updateNull, updateObject, updateRef, updateRow, updateShort, updateShort, updateString, updateTime, updateTimestamp, wasNull.

Depending on its requirement, a JDBC application must specify the scrollability of the result set (i.e., forward-only vs. scrollable), the sensitivity of the result set to underlying data changes (i.e., scroll insensitive vs. scroll sensitive), and the updatability or concurrency mode (i.e., read-only vs. updatable). Table 8.5 summarizes the six result set types.

Table 8.5. Result Set Types and Modes

Result Set Type

Concurrency (Mode)

Forward Only

Read Only

Forward Only

Updatable

Scroll Insensitive

Read Only

Scroll Insensitive

Updatable

Scroll Sensitive

Read Only

Scroll Sensitive

Updatable

The result set type and concurrency mode are specified on statement objects during the invocation of CreateStatement(), preparedStatement(), andprepareCall() methods using the following ResultSet static constants:

  • Result Set Types: TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE, TYPE_SCROLL_SENSITIVE

  • Result Set Concurrency: CONCUR_READ_ONLY, CONCUR_UPDATABLE

Examples:

Statement stmt =
con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
     ResultSet.CONCUR_READ_ONLY);

PreparedStatement pstmt = conn.preparedStatement ("SELECT
ename, empno, sal, job FROM emp WHERE ename = ?",
ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
CallableStatement cs = con.prepareCall("{ ? = call
stproc.queyr1()}",
     ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);

The result set type and result set concurrency can be obtained by invoking the following methods:

  • On Statement, PreparedStatement, orCallableStatement objects: All result sets produced will inherit the statement property.

    getResultSetType(): Returns the result set type property of this statement object.

    getResultSetConcurrency(): Returns the result set concurrency property of this statement object.

  • On the result set object upon the execution of the query:

    ResultSet.getType: Returns the type of the result set object ResultSet.getConcurrency: Returns the concurrency mode of the result set object.

Fetch Direction

In the JDBC 2.0 specification, the result set API furnishes the following hints and methods for presetting the fetch direction for scrollable result sets:

  • Fetch Direction Hints (int): ResultSet.FETCH_FORWARD, ResultSet.FETCH_REVERSE, andResultSet.FETCH_UNKNOWN.

    Note

    The Oracle JDBC driver supports only FETCH_FORWARD (FETCH_REVERSE or FETCH_UNKNOWN are ignored, and a SQL Warning is issued).

  • The following methods, which can be invoked either on the statement object (including plain Statement, PreparedStatement, and CallableStatement) or on the result set object, allow presetting and retrieving the fetch directions:

void setFetchDirection (int) throws SQLException
int getFetchDirection()throws SQLException

Code snippet:

ResultSet rs = null;
Statement stmt =

con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
                ResultSet.CONCUR_READ_ONLY);
rs.setFetchDirection(ResultSet.FETCH_FORWARD);
rs = stmt.executeQuery("SELECT empn, sal, job FROM emp");

Fetch Size

In the JDBC 2.0 specification, the result set API furnishes the following methods, which can be invoked either on the statement object (i.e., Statement, PreparedStatement, andCallableStatement) or on the result set object, for setting (and retrieving) the fetch size; in other words, the number of rows to be fetched at each database roundtrip:

void setFetchSize (int) throws SQLException
int getFetchSize() throws SQLException

Code snippet:

ResultSet rs = null;
Statement stmt = con.createStatement();
// set fetch size on the Statemnt object
stmt.setFetchSize(15);
rs = stmt.executeQuery("SELECT empn, sal, job FROM emp");

The fetch size is set by default to 10 in the Oracle JDBC drivers.

The Oracle Result Set Interface

The oracle.jdbc.OracleResultSet interface extends the standard result set interface, mainly with methods for retrieving and mapping Oracle database SQL types to Oracle JDBC types (i.e., oracle.jdbc.*). These methods include getARRAY, getBfile, getBFILE, getBLOB, getCHAR, getCLOB, getOPAQUE, getINTERVALYM, getINTERVALDS, getTIMESTAMP, getTIMESTAMPTZ, getTIMESTAMPLTZ, getCursor, getCustomDatum, getORAData, getCustomDatum, getDATE, getNUMBER, getOracleObject, getRAW, getREF, getROWID, getSTRUCT, updateARRAY, updateBfile, updateBLOB, updateCHAR, updateCLOB, updateORAData, updateDATE, updateINTERVALYM, updateINTERVALDS, updateTIMESTAMP, updateTIMESTAMPTZ, updateTIMESTAMPLTZ, updateNUMBER, updateOracleObject, updateRAW, updateREF, updateROWID, andupdateSTRUCT.

Implementation History

In Chapter 6, the historical trail of JDBC specifications support by Oracle JDBC drivers was briefly outlined. Result set support started with forward-only/read-only result set support in Oracle 8i release 8.1.5, which implements JDBC 1.22 API. Then, in Oracle 8i Release 8.1.6, a “Beta-quality”[7]support was made available for scrollable cursors; the “production quality” support was delivered in Oracle 8i Release 8.1.7. Result set holdability was finally supported in Oracle database 10g Release 2 (i.e., ResultSet.HOLD_CURSORS_OVER_COMMIT is the default and only holdability is supported). As mentioned in Chapter 6, the Oracle database does not allow returning multiple result sets; therefore, the OracleDatabaseMetaData.supportsMultipleOpenResults will return false.

Forward-only result sets (i.e., Forward-Only/Read-Only and Forward-Only/Updatable) reside within the RDBMS, while the rows and columns that make up scrollable result sets are cached on the client side within the driver. The next section describes the implementation and navigation within scrollable result sets, updating the result sets, inserting in the result sets, sensitivity and insensitivity to changes, and refetching rows.

Oracle JDBC Support for Scrollable Result Sets

Scrollable result sets (scroll insensitive and scroll sensitive) are more complicated to manage than forward-only result sets, because the former allow scrolling (i.e., random navigation) within the result set. The Oracle JDBC driver currently implements and stores a scrollable result set within a cache in the JDBC driver. Cursor positioning and movement within scrollable result sets were illustrated in Listing 8.1. From a performance perspective, because the entire set of rows that made up the result set is cached on the client side (i.e., in the JDBC driver), scrolling through a large result set will incur significant performance overhead, while a small result set will benefit from the client-side caching. This section explains the scrollable result set functionality and by the same token highlights the specifics of Oracle’s implementation. Scroll-sensitive result sets will be specifically addressed later.

Scrollable Result Set Retrictions

The following restrictions apply to Oracle JDBC implementation of scrollable result sets:

  • SELECT * FROM ...” queries are not allowed; as workaround you can either use table aliases “SELECT alias.* from <your table> alias” or explicitly select all the columns of the table.

  • The query can only apply to a single table, no join.

  • Calling getSTRUCT() on an ADT in a scrollable result set may result in a NullPointerException.

  • setFetchDirection() does not do anything.

Scrolling the Result Sets

In order to navigate within the scrollable result set, applications must position the cursor by invoking its positioning methods: beforeFirst(), first(), next(), previous(), last(), afterLast(), absolute(int row), and relative(int row). At the creation of the result set object, the cursor is positioned beforeFirst, as illustrated by Figure 8.2, and then moves forward to the first row upon the invocation of the next() method. Conversely, when the last row has been retrieved, the cursor is positioned afterLast; it then may move backward using either previous() or relative() methods. Listing 8.5 illustrates this navigation.

Navigating the Result Set Rows

Figure 8.2. Navigating the Result Set Rows

Example 8.5. NavigResSet

import java.sql.*;
import oracle.jdbc.*;
import oracle.sql.*;
import oracle.jdbc.pool.OracleDataSource;
import java.io.*;
/*
 *Exercise the various Result Set Navigation Methods
 *
 */

 public class NavigResSet
 {
  public static void main (String args [])throws SQLException
  {
   // Create an OracleDataSource
   OracleDataSource ods = new OracleDataSource();

   // Set the URL, using TNS Alias with JDBC-Thin
   String url = "jdbc:oracle:thin:scott/tiger@inst1";
   ods.setURL(url);
   // Retrieve a connection
   Connection conn = ods.getConnection();
   // Create a Statement
   Statement stmt = conn.createStatement
          (ResultSet.TYPE_SCROLL_INSENSITIVE,
                   ResultSet.CONCUR_READ_ONLY);
   // Set the statement fetch size to 1
   stmt.setFetchSize (1);

   // Query EMP Table
    ResultSet rset =
       stmt.executeQuery ("select EMPNO, ENAME, SAL from EMP");

    /*
     *  rs.next()
     */
     RsNext(rset);

    /*
     *  rs.previous()
     */
     RsPrevious(rset);

    /*
     *  rs.absolute()
     */
     RsAbsolute(rset);

    /*
     *  rs.relative()
     */
     RsRelative(rset);

    // Close RseultSet, statement and Connection
     rset.close();
     stmt.close();
     conn.close();
  }


  private static void RsNext(ResultSet rset) throws SQLException
  {
     System.out.println
         ("**** Forward Navig using ResultSet.next():");

     // Ensure the cursor is before the first row
     if (!rset.isBeforeFirst()) rset.beforeFirst ();

    // Iterate through the rows using next()
    while (rset.next())
       System.out.println ("Employee Id: " + rset.getString (1));
    }

   private static void RsPrevious(ResultSet rset) throws SQLException
   {
     System.out.println
          ("**** Backward Navig using ResultSet.previous():");
     // Ensure the cursor is after the last row
     if (!rset.isAfterLast()) rset.afterLast();

     // Iterate through the rows using previous()
     while (rset.previous())
        System.out.println ("Employee Id: " + rset.getString (1));
     }

     private static void RsAbsolute(ResultSet rset) throws SQLException
     {
     System.out.println
           ("**** Forward Navig using  ResultSet.absolute():");

     // Place the cursor at the first row
     int pos = 1;
     // Loop through rows
     while (rset.absolute(pos))
     {
       System.out.println ("Employee Id: " + rset.getString (1));
       pos ++;
     }
    }
   private static void RsRelative(ResultSet rset) throws SQLException
   {
    System.out.println
           ("**** Backward Navig using ResultSet.relative():");

    // Make sure the cursor is on the last row
    if (rset.getRow () == 0 || !rset.isLast())
          rset.last ();

    // relative(-1) is similar to previous()
    do
    {
      System.out.println ("Employee Id: " + rset.getString (1));
     }
     while (rset.relative (-1));
    }
}
$ javac NavigResSet.java
$ java -Doracle.net.tns_admin=$TNS_ADMIN NavigResSet

The Oracle Result Set Cache Interface

For various reasons, you may want to bypass the default (i.e., Oracle-supplied) result set cache. The Oracle JDBC driver furnishes the OracleResultSetCache interface, which allows you to implement your own result set cache.

/*
 *  OracleResultSetCache interface.
 */
public interface OracleResultSetCache
{

  /*
   * Store the data in row r and column c
   */
  public void put(int r, int c, Object value) throws IOException;


  /*
   * Retrieve the data in row r and column c.
   */
  public Object get(int row, int column) throws IOException;

  /*
   * Remove row r.
   */
  public void remove(int row) throws IOException;

  /*
   * Remove the data in row r and column c.
   */
  public void remove(int row, int column) throws IOException;

  /*
   * Clear/Purge the cache (remove all cached data).
   */
  public void clear() throws IOException;

  /*
   * Close the cache.
   */
  public void close() throws IOException;
}


import oracle.jdbc.*;
...
public class MyOracleResultSetCache implements OracleResultSetCache
{
 // OracleResultSetCache methods implementation

}

Once you have implemented your own cache, you can use the setResultSetCache() method to instruct OracleStatement, OraclePreparedStatement, or OracleCallableStatement objects—before the execution of these statements—to use the specified instance of your cache object.

public void
setResultSetCache(oracle.jdbc.OracleResultSetCache cache)
    throws SQLException;

Code Snippet:

============

OracleStatement stmt = conn.createStatement
          (ResultSet.TYPE_SCROLL_INSENSITIVE,
                   ResultSet.CONCUR_READ_ONLY);

// Instantiate the cache and assign the instance
// to the statement
      MyOracleResultSetCacheImpl rscache = new
      MyOracleResultSetCache();
      stmt.setResultSetCache(rscache);

// execute the statement
OracleResultSet rs = stmt.executeQuery(" SELECT col1, col2
FROM table1)

At this stage, you can navigate the result set from within the user-supplied cache, exactly as you would do with the Oracle-supplied cache.

Oracle JDBC Support for Updatable Result Sets

Unlike read-only result sets, rows in updatable result sets (i.e., concurrency type equal ResultSet.CONCUR_UPDATABLE) may be updated, inserted, and deleted. However, unlike the regular SQL DML operations (i.e., update, insert, delete) that can be performed as single operations directly on the target table(s), updatable result sets require additional steps, precautions, and restrictions.

Updatable Result Set Restrictions

The following restrictions apply to Oracle JDBC implementation of updatable result sets:

  • SELECT * FROM ...” queries are not allowed; as a workaround you can either explicitly select all the columns of the table or table aliases, as follows:

    SELECT alias.* from <your table> alias

  • The query can only apply to a single table (no join) and must select all nonnullable columns for the result insert to work.

  • The query cannot contain/select aggregate or derived columns (i.e., SUM, MAX, set).

Updating Result Sets

Performing an UPDATE operation in a result set requires the following four steps:

  1. For Scrollable result sets, move the cursor to the row to be updated, using the navigation/positioning methods of scrollable cusrors described earlier in this section. The position must be valid, not located by beforeFirst() orafterLast(). For Forward-Only result sets, iterate over the next() method until you locate the row in question.

  2. Invoke the updateXXX() methods corresponding to the data types of the columns to be updated (see section 8.2) with the new values.

    Example:

    rs.updateString(1, "20" );
    rs.updateBoolean(5, false);
    rs.updateShort(6, (short)53);
    rs.updateInt(7, index);
    rs.updateLong(8, (long)893932);
    rs.updateDouble(9, (double)930.932);
    rs.updateDouble(10, (double)99302.3945);
    ...
    rs.updateDATE(11, date);

    At this stage, you have updated the columns of a specific row in the result set. If I may paraphrase Alfred Korzybski,[8] “the result set is not the table.” You need to propagate the changes to the table, which is what the next step does.

  3. Invoke the updateRow() method on the result set object to copy and save the changes to the table:

    rs.updateRow();

    If, instead of saving the changes, you want to revert to the original values (before the updateXXX()), you can do so by invoking cancelRowUpdate() instead:

    rs.cancelRowUpdate();

    Note

    If, between steps 2 and 3, you change the position of the cursor, then the changes are discarded, which is equivalent to a cancelRowUpdate() invocation.

  4. At this stage, assuming you have invoked updateRow(), then you can either COMMIT orROLLBACK the changes as part of the ongoing transaction.

Note

In Listing 8.6, I used SELECT ... FOR UPDATE because the JDBC driver does not handle potential update conflicts; also, auto-commit has been disabled.

Example 8.6. UpdateRSet.java

import java.sql.*;
import oracle.jdbc.*;
import oracle.sql.*;
import oracle.jdbc.pool.OracleDataSource;
import java.io.*;
/*
 * Result Set Update Test.
 */
public class UpdateRSet
{
  public static void main (String args [])throws SQLException
  {
   // Create an OracleDataSource
   OracleDataSource ods = new OracleDataSource();

   // Set the URL, using TNS Alias with JDBC-Thin
    String url = "jdbc:oracle:thin:scott/tiger@inst1";
    ods.setURL(url);
    Connection conn = ods.getConnection(); // Retrieve a connection
    conn.setAutoCommit(false); // Disable Auto Commit
    Statement stmt = null;
    try
    {
     stmt = conn.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE,
     ResultSet.CONCUR_UPDATABLE );

     String sql =
      "SELECT deptno, dname, loc FROM dept WHERE deptno = 10 FOR
            UPDATE";
     ResultSet rs = stmt.executeQuery(sql);
     while (rs.next())
         System.out.println("** Result Set " + rs.getString(1) + " " +
            rs.getString(2) + " " + rs.getString(3) );

     // The result set has only 1 row
     rs.first();
     System.out.println("*** updating LOC column in row #1 of result
           set ...");
     rs.updateString(3, "LAS VEGAS " ); // change this the next time
     System.out.println("*** saving changes to the table and
            commiting...");
     rs.updateRow();
     conn.commit();
     rs.close();
     System.out.println("Display the table  After Update");
     rs = stmt.executeQuery("select deptno, dname, loc from dept");
     while (rs.next ())
     System.out.println (rs.getString(1) + " "
                   + rs.getString(2) + " "+ rs.getString(3));
     } catch (SQLException e) {
        System.out.println( " Exception = " + e.getMessage() +
                       " SQLState = " + e.getSQLState() +
                        " ErrorCode = " + e.getErrorCode() );
      }
  }
 }
$ javac UpdateRSet.java
$ java -Doracle.net.tns_admin=$TNS_ADMIN UpdateRSet

** Result Set 10 ACCOUNTING LOS ANGELES
*** updating LOC column in row #1 of result set ...
*** saving changes to the table and commiting...
Display the table  After Update
10 ACCOUNTING LAS VEGAS
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
$

Deleting Result Sets

Performing a DELETE operation in a result set requires the following three steps:

  1. For Scrollable result sets, move the cursor to the row to be deleted, using the navigation/positioning methods of scrollable result sets described earlier. The position must be valid, not located by beforeFirst() orafterLast(). For Forward-Only result sets, iterate over the next() method until you locate the row in question.

  2. Invoke the deleteRow() method on the result set to remove the row from the table:

    rs.deleteRow();

  3. At this stage, you can either COMMIT orROLLBACK the deletion as part of the ongoing transaction. Note: The JDBC driver does not handle potential delete conflict.

    Code snippet:

    rs.absolute(12);
    rs.deleteRow();
    ...
    COMMIT

Inserting Result Sets

Performing an INSERT operation in a result set requires the following five steps:

  1. For insert operations, each result set provides a virtual row (or staging area) called insert-row, which has a specific position. The result set method moveToInsertRow() saves the current position of the cursor (for later restoraton) and then moves it to the insert-row.

    rs.moveToInsertRow();

  2. Populate (i.e., assign value to) all of the nonnullable columns of the insert-row using the appropriate updateXXX() methods (nullable columns will be set to NULL, if not populated).

    rs.updateStrig("loc" "San Francisco"); //using column name
    rs.updateFloat("sal", 999.2f); //using column name
    rs.updateBoolean(5, false);
    rs.updateShort(6, (short)53);
    rs.updateInt(7, index);
    ...
    
    rs.updateLong(8, (long)893932);
    rs.updateDouble(9, (double)930.932);
    rs.updateDouble(10, (double)99302.3945);
  3. Similar to UPDATE result set, you need to insert the new row (i.e., the insert-row) to the database table using the insertRow() method. Positioning to a different row before the invocation of insertRow() will cancel the insert operation.

    rs.insertRow();

  4. Restore the cursor position saved in step 1, using the moveToCurrentRow() method. rs.insertRow();

  5. COMMIT or ROLLBACK as part of your ongoing transaction.

Note

The current Oracle JDBC implementation of the insertRow() method does not insert the new row into the result set itself, but, as illustrated in Listing 8.7, it does insert the new row in the database.

Example 8.7. InsertRSet.java

import java.sql.*;
import oracle.jdbc.*;
import oracle.sql.*;
import oracle.jdbc.pool.OracleDataSource;
import java.io.*;
/*
 * Result Set Insert
 */
public class InsertRSet
{
  public static void main (String args [])throws SQLException
  {
  // Create an OracleDataSource
  OracleDataSource ods = new OracleDataSource();

  // Set the URL, using TNS Alias with JDBC-Thin
  String url = "jdbc:oracle:thin:scott/tiger@inst1";
  ods.setURL(url);
  Connection conn = ods.getConnection(); // Retrieve a connection
  conn.setAutoCommit(false); // Disable Auto Commit
  Statement stmt = null;
  try
  {
    stmt = conn.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE,
    ResultSet.CONCUR_UPDATABLE );
    ResultSet rs = stmt.executeQuery("SELECT deptno, dname, loc FROM
dept");

    // Print the result set before the insert
    System.out.println("*** Printing Result Set *** ");
     while (rs.next())
        System.out.println(rs.getString(1) + " " +
           rs.getString(2) + " " + rs.getString(3) );

     System.out.println("*** Insert new row");
     // Step#1 save cursor position and move to insert-row
     rs.moveToInsertRow();

     // Step#2 populate the columns
     rs.updateString(1, "50");
     rs.updateString("dname", "MARKETING");
     rs.updateString(3, "San Diego" );



     // Step#3 save changes to the table
     System.out.println("*** saving changes to the table and
              commiting...");
     rs.insertRow();

     // Step#4 restore cursor position
     rs.moveToCurrentRow();

     // Step#5 Commit
     conn.commit();
     rs.close();

     System.out.println("*** Printing the table  After Insert");
     rs = stmt.executeQuery("select deptno, dname, loc from dept");
     while (rs.next ())
     System.out.println (rs.getString(1) + " "
                     + rs.getString(2) + " "+ rs.getString(3));
     } catch (SQLException e) {
       System.out.println( " Exception = " + e.getMessage() +
                       " SQLState = " + e.getSQLState() +
                        " ErrorCode = " + e.getErrorCode() );
     }
 }
}

$ vi InsertRSet.java
$ javac InsertRSet.java
$ java -Doracle.net.tns_admin=$TNS_ADMIN InsertRSet
*** Printing Result Set ***
10 ACCOUNTING LAS VEGAS
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
*** Insert Result Set
*** saving changes to the table and commiting...
*** Printing the table  After Insert
10 ACCOUNTING LAS VEGAS
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
50 MARKETING San Diego
$ 

Prefetching and Auto Refresh

Row Prefetching

Row prefetching consists of anticipating forward fetching by fetching multiple rows at a time. This improves performance, because the application can consume the prefetched rows without a database roundtrip. This is obviously a common requirement, but there is no standard API yet. The Oracle JDBC furnishes the following proprietary API for retrieving a window of rows, starting with the current row. The window size or fetch size can be set at statement or connection levels.

  • OracleConnection.setDefaultRowPrefetch: Sets the value of row prefetch for all statements associated with this connection. The statements retrieve this setting at creation time; subsequent changes to the connection’s default row prefetch won’t alter their settings.

  • OracleConnection.getDefaultRowPrefetch(): Retrieves the value of row prefetch for this connection object.

  • OracleStatement.setRowPrefetch(): Sets the row prefetch value for this statement and therefore for all result sets created from it.

  • OracleStatement.getRowPrefetch(): Retrieves the value of row prefetch for this statement.

The optimal Row Prefetching value is 10. Starting with JDBC 10g Release 1, the driver allocates a fixed amount of memory to hold all column values of each row; therefore, setting Row Prefetching to a value larger than 50 will result in allocating a too large amount of memory for minimal to no additional peformance (compared with Row Prefetching value of 10). You need to trade/tune the number of columns versus the number of rows.

When retrieving data (typically LONG, LONG RAW, orLOBs data types or other data types redefined using defineColumnType(nn, Types.LONG-VARXXX)) via the LONG streaming approach (i.e.,the data is fetched into a staging buffer, from which getXXXStream returns an InputStream), the driver sets the window size to 1 (i.e., disable row prefetching).

Code Snippet
============

Connection conn = ds.getConnection();
//Connection level row-prefetch  -- default for all statements
((OracleConnection)conn).setDefaultRowPrefetch(10);

Statement stmt = conn.createStatement();

ResultSet rset = stmt.executeQuery("SELECT dname FROM dept");
System.out.println( rset.next () );
while( rset.next () )
System.out.println( rset.getString (1) );

//Statement level row-prefetch overrides the default value

( (OracleStatement)stmt ).setRowPrefetch (5);

ResultSet rset = stmt.executeQuery("SELECT ename FROM emp");
System.out.println( rset.next () );
while( rset.next() )
System.out.println( rset.getString (1) );
stmt.close();
...

Auto Refetch

During the processing of a result set, changes may have occurred to rows that are part of the result set in question. The values of columns in the ResultSet set by the updateXXX() calls may not be in sync with the state/ values of the columns in the database table (e.g., a database trigger may have modified the column values). Auto-Refresh consists of automatically refetching the column values during updateRow(). The JDBC API specified the refreshRow() method on result set objects for obtaining the latest copies of the result rows from the RDBMS tables.

The Oracle JDBC implementation of refreshRow() only supports result sets that are implemented with a ROWID pseudo column: namely, the following result set types: Scroll-Insensitive/Updatable, Scroll-Sensitive/Read-Only, andScroll-Sensitive/Updatable. However, auto-refetch incurs a database roundtrip during the updateRow(). Disabling auto-refresh avoids such a roundtrip, resulting in performance improvement.

  • OracleStatement.setAutoRefetch(boolean): Sets the default value of ResultSet’s auto-refetch mode for the statement.

  • OracleResultSet.setAutoRefetch(boolean): Sets the default value of ResultSet’s auto-refetch mode for the result set object.

  • OracleConnection.setAutoRefetch(boolean): Sets the default state of ResultSet’s auto-refetch mode for the connection object and corresponding statements. The default value is true.

Changes Detection and Visibility

Visibility of Internal and External Changes

Change visibility refers to the ability to see a new value of the same data when calling ResultSet.getXXX() following a (previous) call toResultSet.updateXXX(). Internal changes refer to changes made by the result set itself through DML (i.e.,INSERT, UPDATE, DELETE) and are summarized in Table 8.6.

Table 8.6. Result Set, Internal Changes

 

Internal UPDATE

Internal DELETE

Internal INSERT

Forward-Only

No

No

No

Scroll-Sensitive

Yes

Yes

No

Scroll-Insensitive

Yes

Yes

No

The DatabaseMetaData (andOracleDatabaseMetaData) API furnish the following methods for checking the response:

public boolean ownUpdatesAreVisible(int type)
                         throws java.sql.SQLException
public boolean ownDeletesAreVisible(int type)
                         throws java.sql.SQLException
public boolean ownInsertsAreVisible(int type)
                         throws java.sql.SQLException

Where type stands for the result set type (i.e., ResultSet.TYPE_XXX).

OracleDatabaseMetaData odmd;
...
if (odmd.ownUpdatesAreVisible(ResultSet.TYPE_SCROLL_INSENSITIVE))
{
// changes are visible
}

External changes refer to committed changes (the Oracle database does not allow seeing uncommitted changes, anyway) made by external sources in other database sessions, or changes incurred by a trigger as a result of internal change. There is a nuance between the visibility of external changes and the awareness that the changes happened (changes detection). The OracleDatabaseMetaData implements the deletesAreDetected(), updatesAreDetected(), andinsertsAreDetected(); however, these methods will always return false because the Oracle JDBC does not detect row change.

Table 8.7 summarizes external changes visibility:

Table 8.7. Result Set, External Visibility

 

External UPDATE

External DELETE

External INSERT

Forward-Only

No

No

No

Scroll-Sensitive

Yes

No

No

Scroll-Insensitive

No

No

No

The DatabaseMetaData (andOracleDatabaseMetaData) API furnish the following methods for checking the response:

public boolean othersUpdatesAreVisible(int type)
                              throws java.sql.SQLException
public boolean othersDeletesAreVisible(int type)
                              throws java.sql.SQLException
public boolean othersInsertsAreVisible(int type)
                              throws java.sql.SQLException

Where type stands for the result set type (i.e., ResultSet.TYPE_XXX).

OracleDatabaseMetaData odmd;
...
if (odmd.othersUpdatesAreVisible(ResultSet.TYPE_SCROLL_SENSITIVE))
{
// changes are visible
}

RowSet

In the previous section, we looked at result sets and their capabilities; however, there are Java SE, Java EE, and Web services requirements that are not addressed by the result set interface when interacting with relational databases and tabular data. This section explains the RowSet API and Oracle JDBC implementation of JDBC Rowset, CachedRowSet, WebRowSet, JoinRowSet, and FilteredRowSet.

Introducing the RowSet API

The goal of the RowSet API is to address the following shortcomings: (1) associating a set of properties and a listener to a rowset; (2) making a result set scrollable and updatable when the JDBC driver does not offer these capabilities; (3) allowing manipulating data and making changes while disconnected and synchronizing the changes back to the data source; (4) making a result set object serializable and movable across JVM or the Web; (5) shipping result set to thin clients, handheld devices, or PDAs, which are not always connected and do not have a full-fledged JDBC driver stack; (6) consuming rowset in chunks; (7) turning rowset into an XML document; (8) producing a rowset from an XML document; and (9) joining rowset objects (while disconnected from the data source).

So, what are RowSets? The javax.sql.Rowset interface extends the java.sql.ResultSet interface; similarly, the RowSetMetaData interface extends the java.sql.ResultSetMetaData interface. Simply put, RowSets encapsulate ResultSets (and other tabular data) through a set of properties, including datasource, datasourcename, url, username, password, typeMap, maxFiedSize, maxRows, queryTimeout, fetchsize, transactionisolation, escapeProcessing, command, concurrency, readOnly, fetchDirection, as well as events and an event listener mechanism.

A listener for a RowSet object is a component that implements the following methods from the RowSetListener interface:

  • cursorMoved: What the listener will do, if anything, when the cursor in the RowSet object moves

  • public void cursorMoved(javax.sql.RowSetEvent event)

  • rowChanged: What the listener will do, if anything, when one or more column values in a row have changed, a row has been inserted, or a row has been deleted (OracleRowSet maintains attributes to let users know if the row is changed, deleted, or inserted)

    public void rowChanged(javax.sql.RowSetEvent event)

  • rowSetChanged: What the listener will do, if anything, when the RowSet object has been populated with new data

    public void rowSetChanged(javax.sql.RowSetEvent event)

    The OracleRowSetListenerAdapter implements RowSetListener.

The Java Specification Request (JSR) 114[9] formalizes the RowSet API and defines five models, including JdbcRowSet, CachedRowSet, WebRowSet, JoinRowSet, and FilteredRowSet. The relationship between the various RowSet models and their key differentiators is depicted in Figure 8.3. With 10g Release 1, the Oracle JDBC furnished the RowSets implementation based on the public draft of the JSR-114 specification, which is what was available by the time the product development was done; in 10g Release 2, the implementation is based on the final specification. All Oracle rowset implementations are by default serializable, read-only (i.e., ResultSet.CONCUR_READ_ONLY), and scrollable (ResultSet.TYPE_SCROLL_SENSITIVE).

How the RowSet Interface Stacks Up

Figure 8.3. How the RowSet Interface Stacks Up

The JSR-114 furnishes a reference implementation of each rowset type, as part of Java SE 5 (i.e., JDK 5.0), or as stand-alone JARS (i.e., rowsetjsr114.jar)[10] that can be used on top of any standard JDBC driver. JDBC providers such as Oracle provide their own implementation as part of their JDBC drivers (i.e., ojdbc14.jar).

JDBCRowSet and OracleJDBCRowSet

A JDBCRowSet object can be viewed as a ResultSet disguised as a JavaBean. It maintains a connection to its datasource, has a set of properties, and a listener notification mechanism. Its contents are identical to those in a ResultSet object, and it inherits all of the row manipulation methods, the cursor movement methods, the data type manipulation methods (i.e., getXXX(), setXXX(), and updateXXX()) of a ResultSet; hence, as illustrated by the code sample in Listing 8.8, operating on the JDBCRowSet object is equivalent to operating on the ResultSet object.

Example 8.8. myJDBCRowSet.java

import java.sql.*;
import java.io.*;
import oracle.sql.*;
import oracle.jdbc.*;
import javax.sql.*;
import oracle.jdbc.rowset.*;

public class myJDBCRowSet
{
  public static void main (String[] args) throws SQLException
  {
    RowSet rowset = new OracleJDBCRowSet ();
    try
    {
      rowset.setUrl("jdbc:oracle:thin:scott/tiger@inst1");
      //rowset.setUrl("jdbc:oracle:thin:@inst1");
      rowset.setUsername("scott");
      rowset.setPassword("tiger");
      rowset.setCommand ("SELECT empno, ename, sal, hiredate FROM emp");
      rowset.execute();

      for (int i = 0; rowset.next() ; ++i)
         System.out.println ("*** RowSet(" +(i+1) +") "
          +rowset.getInt (1) +" : " + rowset.getString (2)
         +" : " + rowset.getFloat (3) +" : " +rowset.getDate(4));
      } catch (SQLException sqle) {
        System.out.println( " SQL Exception = " + sqle.getMessage() +
                       " SQLState = " + sqle.getSQLState() +
                       " ErrorCode = " + sqle.getErrorCode() );
     }
   }
}

$ javac myJDBCRowSet.java
$ java -Doracle.net.tns_admin=$TNS_ADMIN myJDBCRowSet

*** Row(1) 7369 : SMITH : 800.0 : 1980-12-17
*** Row(2) 7499 : ALLEN : 1600.0 : 1981-02-20
*** Row(3) 7521 : WARD : 1250.0 : 1981-02-22
*** Row(4) 7566 : JONES : 2975.0 : 1981-04-02
*** Row(5) 7654 : MARTIN : 1250.0 : 1981-09-28
*** Row(6) 7698 : BLAKE : 2850.0 : 1981-05-01
*** Row(7) 7782 : CLARK : 2450.0 : 1981-06-09
*** Row(8) 7788 : SCOTT : 3000.0 : 1987-04-19
*** Row(9) 7839 : KING : 5000.0 : 1981-11-17
*** Row(10) 7844 : TURNER : 1500.0 : 1981-09-08
*** Row(11) 7876 : ADAMS : 1100.0 : 1987-05-23
*** Row(12) 7900 : JAMES : 950.0 : 1981-12-03
*** Row(13) 7902 : FORD : 3000.0 : 1981-12-03
*** Row(14) 7934 : MILLER : 1300.0 : 1982-01-23
$

According to the specification, the resulting objects are by default scrollable (i.e., ResultSet.TYPE_SCROLL_SENSITIVE) and read-only (i.e., ResultSet.CONCUR_READ_ONLY) but can be made updatable by calling setReadOnly (false). Changes made to the rowset using updateXXX() methods will be reflected in the corresponding database rows and columns. However, it is not clear how exactly a JDBCRowSet can be made updatable if the result set is not updatable.

The OracleJDBCRowSet class implements the javax.sql.rowset.JdbcRowSet interface. However, the OracleJDBCRowSet object is serializable, as are all Oracle’s implementations of the five rowset types.

Let’s look at the disconnected rowset models, which are more interesting.

CachedRowSet and OracleCachedRowSet

A CachedRowSet object is the most well known and probably the most used type of the RowSet family. It caches its rows in memory and allows operating on these without being connected to the datasource. TheOracleCachedRowSet object is Oracle’s implementation of the javax.sql.rowset.CachedRowSet interface. Unlike JDBCRowSets, discussed in the previous section, a CachedRowSet object turns any result set object (even the read-only ones) into scrollable and updatable rowsets. This capability is interesting if the JDBC driver does not support scrollable and updatable result sets. There are many other interesting features but, in my opinion, the most interesting and immediate benefit of CachedRowSet is the scalabilty and performance provided by the ability to be disconnected from the datasource; typically, fewer database connections will accommodate a larger number of clients.

Creating and Populating CachedRowSets

An instance of CachedRowSet object is created by invoking a constructor (i.e., new CachedRowSet() ornew OracleCachedRowSet()). It is then populated (i.e., gets its data) primarily from a result set, using either the populate() method, as illustrated by Listing 8.9, or the execute() method, discussed later.

Example 8.9. myCachedRowSet.java

import oracle.jdbc.rowset.*;
import javax.sql.*;
import java.sql.*;
import oracle.sql.*;
import java.io.*;
import oracle.jdbc.pool.OracleDataSource;
public class myCachedRowSet
{
 public static void main (String[] args) throws SQLException
 {
    OracleCachedRowSet crset = new OracleCachedRowSet ();
    try
    {
      // Get a connection from data source
      OracleDataSource ods = new OracleDataSource();
      String url = "jdbc:oracle:thin:scott/tiger@inst1";
      String sql = "SELECT empno, ename, sal, hiredate FROM emp";
      ods.setURL(url);
      Connection conn = ods.getConnection();
      Statement stmt = conn.createStatement ();

      // Execute the statement and produce a result set
      ResultSet rs = stmt.executeQuery ( sql);
      // set cached row set properties
      crset.setUrl(url);
      crset.setCommand (sql);
      crset.setUsername("scott");
      crset.setPassword("tiger");
      // populate the CachedRowSet with the data in the result set
      crset.populate (rs);

      // release the result set, the statement and the connection
      rs.close ();
      stmt.close ();
      conn.close ();
      System.out.println ("*** Connection  Closed *** ");

      // loop through the cached rowset and retrieve data
      for (int i = 0; crset.next () ; ++i)
        System.out.println ("*** Cached RowSet(" +(i+1) +") "
           + crset.getInt (1) +" : " + crset.getString (2)
            +" : " + crset.getFloat (3) +" : " + crset.getDate(4));
        crset.close ();

      } catch (SQLException sqle) {
        System.out.println( " SQL Exception = " + sqle.getMessage() +
                       " SQLState = " + sqle.getSQLState() +
                       " ErrorCode = " + sqle.getErrorCode() );
      }
    }
}

$ javac myCachedRowSet.java
$ java -Doracle.net.tns_admin=$TNS_ADMIN myCachedRowSet

*** Connection  Closed ***

*** Cached RowSet(1) 7369 : SMITH : 800.0 : 1980-12-17
*** Cached RowSet(2) 7499 : ALLEN : 1600.0 : 1981-02-20
*** Cached RowSet(3) 7521 : WARD : 1250.0 : 1981-02-22
*** Cached RowSet(4) 7566 : JONES : 2975.0 : 1981-04-02
*** Cached RowSet(5) 7654 : MARTIN : 1250.0 : 1981-09-28
*** Cached RowSet(6) 7698 : BLAKE : 2850.0 : 1981-05-01
*** Cached RowSet(7) 7782 : CLARK : 2450.0 : 1981-06-09
*** Cached RowSet(8) 7788 : SCOTT : 3000.0 : 1987-04-19
*** Cached RowSet(9) 7839 : KING : 5000.0 : 1981-11-17
*** Cached RowSet(10) 7844 : TURNER : 1500.0 : 1981-09-08
*** Cached RowSet(11) 7876 : ADAMS : 1100.0 : 1987-05-23
*** Cached RowSet(12) 7900 : JAMES : 950.0 : 1981-12-03
*** Cached RowSet(13) 7902 : FORD : 3000.0 : 1981-12-03
*** Cached RowSet(14) 7934 : MILLER : 1300.0 : 1982-01-23

In this example, the cached rowset is populated through the invocation of the populate() method, which takes a result set as parameter; this approach populates the cached rowset, starting with the first row of the result set. Another form of populate() takes an additional parameter representing the starting row within the result set from which to populate the cached rowset; this approach is used for chunking large result sets data to the cached rowset.

OracleCachedRowSet ocrs = new OracleCachedRowSet();
ocrs.setPageSize(8); // chunk size
ocrs.populate(rset, 2);  // populate cached row set, 8 rows at a time
                         // starting with row #2
// consume
ocrs.populate(rset, 10); // populate cached row set, 8 rows at a time
                         // starting with row #10 

The nextPage() method on the cached rowset, combined with setPageSize(), also allows you to populate and consume the result sets, iteratively, in chunks, as cached rowset.

ocrs.setPageSize(8);
while (ocrs.nextPage())
{
 //consume
}

A CachedRowset may also be populated using the following methods:

  • CachedRowSet.execute()

  • CachedRowSet.execute(conn)

The parameterless form uses the connection defined in the rowset properties, while the second form uses the connection object furnished in parameter and then closes it afterward.

A CachedRowSet object may also get its data from any tabular data-source such as flat files or spreadsheets. The reference implementation sup ports only getting data from a ResultSet object, and so does OracleCachedRowSet (the OracleCachedRowSetReader class simply implements javax.sql.RowSetReader), but Java developers can associate an alternative SyncProvider implementation, which can furnish access to other forms of tabular data sources:

  1. Either by invoking the setSyncProvider method on the CachedRowSet.

    crs.setSyncProvider("foo.bar.xxx.ExcelProvider");

  2. Or by invoking the cached rowset constructor with the Syncprovider implementation as parameter.

OracleCachedRowSetImpl ocrs =
      new
OracleCachedRowSetImpl("com.foo.xxx.ExcelProvider");

Manipulating and Making Changes to Disconnected Data

CachedRowsets make a result set object serializable and, therefore, movable across JVM, movable across the Web, and shipable to thin clients and handheld devices or PDAs, which are not always connected and do not have a full-fledged JDBC driver stack (e.g., Java Micro Edition stack). As a result, the cached rowset can be populated with chunks (pages) of data from the result set, updated or changed, and then synchronized back to the datasource. How does it work?

The SyncProvider furnishes a RowSetReader interface (implemented by the OracleCachedRowSetReader class) for reading from a datasource to populate the CachedRowSet, and a RowSetWriter interface (implemented by the OracleCachedRowSetWriter class) to synchronize the changes back to the datasource. During the propagation of changes, and depending on the implementation, the SyncProvider can detect conflicts between changes in the rowset and changes to the datasource. The CachedRowSet keeps track of the original state of the underlying datasource and the states in the rowset. In case of conflict, it invokes the restoreOriginal() method to resore the datasource to its state before the changes. Depending on the SyncProvider, the CacheRowSet performs an optimistic concurrency control (i.e., no lock used) or a pessimistic concurrency control (i.e., use locks). The Reference Implementation furnishes an optimistic concurrency control (RIOptimisticProvider), so does Oracle’s implementation.

// set cached row set properties
ocrs.setUrl(url);
ocrs.setCommand (sql);
ocrs.setUsername("scott");
ocrs.setPassword("tiger");

ocrs.beforeFirst();  // Navigate
ocrs..next();

pid = ocrs.getInt(1); // Get Value

// Make the rowset updatable
rowset.setReadOnly(false);
ocrs.updateInt(1,1721); // Update columns
ocrs.updateShort(2, 35);
ocrs.updateString(2, "Making Changes");

...
ocrs.updateRow();  // apply changes to the Cached Row

// Batch multiple rowset updates and
// optimize the invocation of accepChanges
...

// Assume another JDBC thread/client makes changes to the
// underlying rows at this stage    conflict
//

// Done with changes, connect to the data source ...
conn = ods.getConnection();

try
{
 // ... and propagate the changes to the data source
 ccrs.setTableName("<table>"); // the originating table
 ccrs.acceptChanges(conn);
} catch (SyncProviderException spe) {
  // Oops, conflict, restore the data source to its original state
  ccrs.restoreOriginal);
} 

Event Listeners

Similar to other rowsets, CachedRowSet objects may have consumers (i.e., displaying the data in the rowset) that want to be notified when a change happens, so as to refresh their own state (i.e., display). The interested parties must implement the javax.sql.RowSetListener interface and then register as a listener for the cached rowset in question, using the addRowSetListener() method.

ocrs.addRowSetListener(consumer1);
...
ocrs.addRowSetListener(consumern);

The interested parties will be notified when any CachedRowSet method moves the cursor (cursorMoved), changes rows(rowChanged), or when the entire rowset has changed (rowSetChanged).

Let’s now look at the other members of the RowSet family, which are more complex disconnected rowsets built on top of CachedRowSet and include: the WebRowSets, the JoinRowSets, and the FilteredRowSets.

WebRowSet and OracleWebRowSet

A WebRowSet object is a CachedRowSet object, augmented with a default WebRowSetXmlReader, which reads an XML document into the rowset, and a default WebRowSetXmlWriter, which writes the rowset as an XML document. The XML format of the WebRowSet makes it suitable for shipment across the Web (hence the name). As a result, Web services or Java components can receive data from a database, through Web protocols.

The oracle.jdbc.rowset.OracleWebRowSet class is an Oracle JDBC implementation of WebRowSet and furnishes the following features:

  • Supports JAXP 1.2–compliant XML parsers; SAX 2.0 and DOM parsers are supported by default.

  • Supports getXXX, setXXX, andupdateXXX methods for JDBC 3.0 standard complex types for disconnected RowSets.

  • Supports Blob/Clob data types.

  • OracleWebRowSetXmlReader.

  • OracleWebRowSetXmlWriter.

Understanding the WebRowSet XML Schema

The XML Schema Definition (XSD) is a W3C recommendation[11] for describing “the structure, content, and semantics of XML documents”; it allows all parties involved to have a common and exact understanding of the document based on the schema in question. The W3C XML schema for WebRowSet defined by Sun, and the corresponding schema definition, is available at the following URL: http://java.sun.com/xml/ns/jdbc/webrowset.xsd.

The WebRowSet schema also uses SQL/XML schema annotations, which are available at the following URL: http://standards.iso.org/iso/9075/2002/12/sqlxml.xsd.

The WebRowSet XSD describes the structure of a WebRowSet in the following areas: properties, metadata, and data.

<xs:element name="webRowSet">
<xs:complexType>
<xs:sequence>
  <xs:element ref="wrs:properties" />
  <xs:element ref="wrs:metadata" />
  <xs:element ref="wrs:data" />
  </xs:sequence>
  </xs:complexType>
  </xs:element>

Let’s look at the structure of each area of the WebRowSet.XSD and illustrate with the XML document deptWRS.xml corresponding to the output of the WebRowSet created from the dept table.

$ sqlplus scott/tiger

SQL*Plus: Release 10.2.0.1.0 - Production on Fri Nov 11 21:53:06 2005

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

SQL> desc dept
 Name                                      Null?    Type
 ----------------------------------------- -------- --------------------
 DEPTNO                                    NOT NULL NUMBER(2)
 DNAME                                               VARCHAR2(14)
 LOC                                                 VARCHAR2(13)

SQL> select * from dept
  2  /

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING      LAS VEGAS
        20 RESEARCH        DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS      BOSTON
        50 MARKETING       San Diego

SQL>

The Properties section: Describe the RowSet properties and the synchronization provider properties. Here is the corresponding section in the WebRowSet.XSD:

<xs:element name="properties">
<xs:complexType>
<xs:sequence>
  <xs:element name="command" type="xs:string" />
  <xs:element name="concurrency" type="xs:string" />
  <xs:element name="datasource" type="xs:string" />
  <xs:element name="escape-processing" type="xs:string" />
  <xs:element name="fetch-direction" type="xs:string" />
  <xs:element name="fetch-size" type="xs:string" />
  <xs:element name="isolation-level" type="xs:string" />
<xs:element name="key-columns">
<xs:complexType>
 <xs:sequence minOccurs="0" maxOccurs="unbounded">
  <xs:element name="column" type="xs:string" />
  </xs:sequence>
  </xs:complexType>
</xs:element>
<xs:element name="map">
<xs:complexType>
<xs:sequence minOccurs="0" maxOccurs="unbounded">
  <xs:element name="type" type="xs:string" />
  <xs:element name="class" type="xs:string" />
  </xs:sequence>
  </xs:complexType>
</xs:element>
  <xs:element name="max-field-size" type="xs:string" />
  <xs:element name="max-rows" type="xs:string" />
  <xs:element name="query-timeout" type="xs:string" />
  <xs:element name="read-only" type="xs:string" />
  <xs:element name="rowset-type" type="xs:string" />
  <xs:element name="show-deleted" type="xs:string" />
  <xs:element name="table-name" type="xs:string" />
  <xs:element name="url" type="xs:string" />
 <xs:element name="sync-provider">
 <xs:complexType>
<xs:sequence>
  <xs:element name="sync-provider-name" type="xs:string" />
  <xs:element name="sync-provider-vendor" type="xs:string" />
  <xs:element name="sync-provider-version" type="xs:string" />
  <xs:element name="sync-provider-grade" type="xs:string" />
  <xs:element name="data-source-lock" type="xs:string" />
</xs:sequence>
  </xs:complexType>
  </xs:element>
  </xs:sequence>
  </xs:complexType>
  </xs:element>

Here is an example based on Web RowSet XSD created for the DEPT table:

<properties>
    <command></command>
    <concurrency>1007</concurrency>
    <datasource></datasource>
    <escape-processing>true</escape-processing>
    <fetch-direction>1002</fetch-direction>
    <fetch-size>10</fetch-size>
    <isolation-level>2</isolation-level>
    <key-columns>
    </key-columns>
    <map>
       <type>SYS.XMLTYPE</type>
       <class>oracle.xdb.XMLTypeFactory</class>
     </map>
     <max-field-size>0</max-field-size>
     <max-rows>0</max-rows>
     <query-timeout>0</query-timeout>
     <read-only>false</read-only>
     <rowset-type>1005</rowset-type>
     <show-deleted>false</show-deleted>
     <table-name></table-name>
     <url>jdbc:oracle:thin:scott/tiger@inst1</url>
     <sync-provider>
       <sync-provider-name>com.sun.rowset.providers.RIOptimisticProvider</
sync-provider-name>
    <sync-provider-vendor>Sun Microsystems Inc.</sync-provider-vendor>
    <sync-provider-version>1.0 </sync-provider-version>
    <sync-provider-grade>2</sync-provider-grade>
    <data-source-lock>1</data-source-lock>
  </sync-provider>
</properties>

The Metadata section: Describes the metadata of the WebRowSet and is aligned with the metadata of the underlying java.sql.ResultSet interface.

<xs:element name="metadata">
<xs:complexType>
<xs:sequence>
 <xs:element name="column-count" type="xs:string" />
<xs:choice>
<xs:element name="column-definition" minOccurs="0"
maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="column-index"   type="xs:string" />
 <xs:element name="auto-increment" type="xs:string" />
 <xs:element name="case-sensitive" type="xs:string" />
 <xs:element name="currency" type="xs:string" />
 <xs:element name="nullable" type="xs:string" />
 <xs:element name="signed" type="xs:string" />
 <xs:element name="searchable" type="xs:string" />
 <xs:element name="column-display-size" type="xs:string" />
 <xs:element name="column-label" type="xs:string" />
 <xs:element name="column-name" type="xs:string" />
 <xs:element name="schema-name" type="xs:string" />
 <xs:element name="column-precision" type="xs:string" />
 <xs:element name="column-scale" type="xs:string" />
 <xs:element name="table-name" type="xs:string" />
 <xs:element name="catalog-name" type="xs:string" />
 <xs:element name="column-type" type="xs:string" />
 <xs:element name="column-type-name" type="xs:string" />
 </xs:sequence>
 </xs:complexType>
 </xs:element>
 </xs:choice>
 </xs:sequence>
 </xs:complexType>
 </xs:element>

Here is an example based on Web RowSet XSD created for the DEPT table:

<metadata>
    <column-count>3</column-count>
    <column-definition>
      <column-index>1</column-index>
      <auto-increment>false</auto-increment>
      <case-sensitive>false</case-sensitive>
      <currency>true</currency>
      <nullable>0</nullable>
      <signed>true</signed>
      <searchable>true</searchable>
      <column-display-size>22</column-display-size>
      <column-label>DEPTNO</column-label>
      <column-name>DEPTNO</column-name>
      <schema-name></schema-name>
      <column-precision>2</column-precision>
      <column-scale>0</column-scale>
      <table-name></table-name>
      <catalog-name></catalog-name>
      <column-type>2</column-type>
      <column-type-name>NUMBER</column-type-name>
    </column-definition>
    <column-definition>
      <column-index>2</column-index>
      <auto-increment>false</auto-increment>
      <case-sensitive>true</case-sensitive>
      <currency>false</currency>
      <nullable>1</nullable>
      <signed>true</signed>
      <searchable>true</searchable>
      <column-display-size>14</column-display-size>
      <column-label>DNAME</column-label>
      <column-name>DNAME</column-name>
      <schema-name></schema-name>
      <column-precision>0</column-precision>
      <column-scale>0</column-scale>
      <table-name></table-name>
      <catalog-name></catalog-name>
      <column-type>12</column-type>
      <column-type-name>VARCHAR2</column-type-name>
    </column-definition>
    <column-definition>
      <column-index>3</column-index>
      <auto-increment>false</auto-increment>
      <case-sensitive>true</case-sensitive>
      <currency>false</currency>
      <nullable>1</nullable>
      <signed>true</signed>
      <searchable>true</searchable>
      <column-display-size>13</column-display-size>
      <column-label>LOC</column-label>
      <column-name>LOC</column-name>
      <schema-name></schema-name>
      <column-precision>0</column-precision>
      <column-scale>0</column-scale>
      <table-name></table-name>
      <catalog-name></catalog-name>
      <column-type>12</column-type>
      <column-type-name>VARCHAR2</column-type-name>
    </column-definition>
  </metadata>

The Data section: Describes the original data and the current data, so as to track the delta between these. Tracking the changes will enable the WebRowSet to detect and resolve concflicts when synchronizing changes back to the data source.

<xs:element name="data">
<xs:complexType>
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="currentRow" minOccurs="0"
maxOccurs="unbounded">
<xs:complexType>
<xs:sequence minOccurs="0" maxOccurs="unbounded">
 <xs:element ref="wrs:columnValue" />
 </xs:sequence>
 </xs:complexType>
 </xs:element>
<xs:element name="insertRow" minOccurs="0"
maxOccurs="unbounded">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
 <xs:element ref="wrs:columnValue" />
 <xs:element ref="wrs:updateValue" />
 </xs:choice>
 </xs:complexType>
 </xs:element>
<xs:element name="deleteRow" minOccurs="0"
maxOccurs="unbounded">
<xs:complexType>
<xs:sequence minOccurs="0" maxOccurs="unbounded">
 <xs:element ref="wrs:columnValue" />
 <xs:element ref="wrs:updateValue" />
 </xs:sequence>
  </xs:complexType>
  </xs:element>
<xs:element name="modifyRow" minOccurs="0"
maxOccurs="unbounded">
<xs:complexType>
<xs:sequence minOccurs="0" maxOccurs="unbounded">
 <xs:element ref="wrs:columnValue" />
 <xs:element ref="wrs:updateValue" />
 </xs:sequence>
 </xs:complexType>
 </xs:element>
 </xs:sequence>
 </xs:complexType>
 </xs:element>
 </xs:schema>

This is a Web RowSet XSD entry based on the DEPT table:

<data>
    <currentRow>
       <columnValue>10</columnValue>
       <columnValue>ACCOUNTING</columnValue>
       <columnValue>LAS VEGAS </columnValue>
     </currentRow>
     <currentRow>
       <columnValue>20</columnValue>
       <columnValue>RESEARCH</columnValue>
       <columnValue>DALLAS</columnValue>
     </currentRow>
     <currentRow>
      <columnValue>30</columnValue>
      <columnValue>SALES</columnValue>
      <columnValue>CHICAGO</columnValue>
     </currentRow>
     <currentRow>
       <columnValue>40</columnValue>
       <columnValue>OPERATIONS</columnValue>
       <columnValue>BOSTON</columnValue>
     </currentRow>
     <currentRow>
       <columnValue>50</columnValue>
       <columnValue>MARKETING</columnValue>
      <columnValue>San Diego</columnValue>
    </currentRow>
  </data>

Creating a WebRowSet Object and Dumping Its Contents to an XML Document

Theoracle.jdbc.rowset.OracleWebRowSetXmlWriter class extends the javax.sql.rowset.spi.XmlWriter. It furnishes the writeXml() method for populating the OracleWebRowSet with XML data from the given resultset, and also for writing the content of the rowset to the supplied java.io.Writer or java.io.OutputStream objects. The structure of the XML data must be compatible with the standard WebRowSet XML schema definition (i.e., <data>, <properties>, and <metadata>, described later).

Here are the various signatures of writeXml():

writeXml(java.io.OutputStream ostream)
writeXml(java.sql.ResultSet rset, java.io.OutputStream ostream)
writeXml(java.sql.ResultSet rset, java.io.Writer writer)
writeXml(java.io.Writer writer)

Listing 8.10 creates a WebRowSet object, populates it with the result set obtained from querying all rows of the DEPT table, and then outputs the WebRowSet into the deptWRS.xml file.

Example 8.10. deptWebRowSet.java

import java.sql.*;
import java.io.*;
import oracle.sql.*;
import oracle.jdbc.*;
import oracle.jdbc.pool.OracleDataSource;
import javax.sql.*;
import oracle.jdbc.rowset.*;
public class deptWebRowSet
{
  public static void main (String[] args) throws SQLException
  {
    // Create an OracleDataSource
    OracleDataSource ods = new OracleDataSource();

    // Set the URL, using TNS Alias with JDBC-Thin
    String url = "jdbc:oracle:thin:scott/tiger@inst1";
    ods.setURL(url);
    Connection conn = ods.getConnection(); // Retrieve a connection
    conn.setAutoCommit(false); // Disable Auto Commit

    Statement stmt = conn.createStatement();
    ResultSet rset = stmt.executeQuery ("select * from dept");

    // Create and populate an OracleWebRowSet with the ResultSet
    System.out.println ("*** Create and populate WebRowSet");
    OracleWebRowSet wrset = new OracleWebRowSet();
    wrset.populate(rset);

    // close the statement, connection and result set
    System.out.println("*** Close Connection and other resources");
    stmt.close();
    conn.close();
    rset.close();

    // RowSet still contains values...
    wrset.first();
    String  dname = wrset.getString(2);
    System.out.println("*** Retrieve Department Name from Web Row Set: " +
dname);

    // Create a FileWriter (i.e., a java.io.Writer)
    try {
       FileWriter printXml = new FileWriter("deptWRS.xml");
       // Write the WebRowSet as XML
       wrset.writeXml(printXml);
       System.out.println("*** Output Web RowSet as XML");
      } catch (IOException exc) {
        System.out.println("......Couldn't create a FileWriter");
      }
    System.out.println ("*** WebRowSet created and XML outputted.");

  }
}
$ javac deptWebRowSet.java
$ java -Doracle.net.tns_admin=$TNS_ADMIN deptWebRowSet
*** Create and populate WebRowSet
*** Close Connection and other resources
*** Retrieve Department Name from Web Row Set: ACCOUNTING
*** Output Web RowSet as XML
*** WebRowSet created and XML outputted.
$

The deptWRS.xml file is created in the current directory; notice that its structure maps the WebRowSet XML Schema Definition (XSD).

Reading an XML Document into a WebRowSet Object

The oracle.jdbc.rowset.OracleWebRowSetXmlReader class extends the javax.sql.rowset.spi.XmlReader. It furnishes the readXml() method for populating the OracleWebRowSet object either from an XML document file (i.e., java.io.Reader) or an XML stream (i.e., java.io.InputStream), as reflected in the following signatures:

readXml(java.io.Reader reader)
readXml(java.io.InputStream istream)

The readXml requires the JDBC application to specify the XML parser either programmatically or through a system property.

For the Oracle SAX parser:

java -Djavax.xml.parsers.SAXParserFactory=oracle.xml.jaxp.JXSAXParserFactory

System.setProperty("javax.xml.parsers.SAXParserFactory",
                   "oracle.xml.jaxp.JXSAXParserFactory");

For the Oracle DOM parser:

java -Djavax.xml.parsers.DocumentBuilderFactory =oracle.xml.jaxp.
DocumentBuilderFactory

System.setProperty("javax.xml.parsers.DocumentBuilderFactory ",
                   "oracle.xml.jaxp.DocumentBuilderFactory ");
    

Here is a code snippet for creating a new OracleWebRowSet reading an XML document:

OracleWebRowSet wrset = new OracleWebRowSet();
// Use Oracle JAXP SAX parser
System.setProperty("javax.xml.parsers.SAXParserFactory",
    "oracle.xml.jaxp.JXSAXParserFactory");
try
{
  FileReader freadr = new FileReader("inWRS.xml");
  // Now read XML stream from the FileReader
  wrset.readXml(freadr);
  close(freadr);
}

Listing 8.11 constructs a WebRowSet in memory without any database connection, from an XML document (empWRS.xml; for ease of demo purpose, the XML document was previously created from a WebRowSet constructed by querying the EMP table). The readXml method of the WebRowSet object populates the rowset with the content of the file given as input to the java.io.FileReader object. Notice that the parser validates the structure of the document against the WebRowSet XSD (comment out http.proxyHost and http.proxyPort settings if you are not running the example behind a firewall).

Example 8.11. empWebRowSet.java

import java.sql.*;
import java.io.*;
import oracle.sql.*;
import oracle.jdbc.*;
import javax.sql.*;
import oracle.jdbc.rowset.*;
public class inWebRowSet
{
 public static void main (String[] args) throws SQLException
 {
  // Create and populate an OracleWebRowSet from an XML document
      // Use Oracle JAXP SAX parser
      System.setProperty("javax.xml.parsers.SAXParserFactory",
             "oracle.xml.jaxp.JXSAXParserFactory");
      System.setProperty("http.proxyHost", "www-proxy.us.oracle.com");
      System.setProperty("http.proxyPort", "80");

      OracleWebRowSet wrset = new OracleWebRowSet();
      try
      {
       System.out.println ("*** Create and populate WebRowSet");
        FileReader fr = new FileReader("empWRS.xml");
        // populate the Web RowSet from the FileReader
        wrset.readXml(fr);
        fr.close();
        System.out.println("*** Print the Web RowSet constructed from XML file");

        // loop through the Web rowset and print
        wrset.beforeFirst();
        for (int i = 0; wrset.next () ; ++i)
        System.out.println ("*** EMPNO: " + wrset.getInt (1)
                + " ENAME: " + wrset.getString (2)
                + "JOB: " + wrset.getString(3)+ " MGR ID: " + wrset.getInt(4)
                + " HIREDATE: " + wrset.getDate(5)
                + " SAL: " + wrset.getFloat(6) + " COMM" + wrset.getFloat(7)
                + " DEPTNO: " +  wrset.getInt(8) );
        wrset.close ();
      } catch (IOException exc){
         System.out.println("Couldn't construct a FileReader becoz: "
           + exc.getMessage());
      }
     }
    }

    $ javac inWebRowSet.java
    $ java -Doracle.net.tns_admin=$TNS_ADMIN inWebRowSet
    *** Create and populate WebRowSet
    *** Print the Web RowSet constructed from XML file

*** EMPNO: 7369 ENAME: SMITHJOB: CLERK MGR ID: 7902 HIREDATE: 1980-12-17 SAL: 800.0
COMM0.0 DEPTNO: 20
*** EMPNO: 7499 ENAME: ALLENJOB: SALESMAN MGR ID: 7698 HIREDATE: 1981-02-20 SAL:
1600.0 COMM300.0 DEPTNO: 30
*** EMPNO: 7521 ENAME: WARDJOB: SALESMAN MGR ID: 7698 HIREDATE: 1981-02-22 SAL:
1250.0 COMM500.0 DEPTNO: 30
*** EMPNO: 7566 ENAME: JONESJOB: MANAGER MGR ID: 7839 HIREDATE: 1981-04-02 SAL:
2975.0 COMM0.0 DEPTNO: 20
*** EMPNO: 7654 ENAME: MARTINJOB: SALESMAN MGR ID: 7698 HIREDATE: 1981-09-28 SAL:
1250.0 COMM1400.0 DEPTNO: 30
*** EMPNO: 7698 ENAME: BLAKEJOB: MANAGER MGR ID: 7839 HIREDATE: 1981-05-01 SAL:
2850.0 COMM0.0 DEPTNO: 30
*** EMPNO: 7782 ENAME: CLARKJOB: MANAGER MGR ID: 7839 HIREDATE: 1981-06-09 SAL:
2450.0 COMM0.0 DEPTNO: 10
*** EMPNO: 7788 ENAME: SCOTTJOB: ANALYST MGR ID: 7566 HIREDATE: 1987-04-19 SAL:
3000.0 COMM0.0 DEPTNO: 20
*** EMPNO: 7839 ENAME: KINGJOB: PRESIDENT MGR ID: 0 HIREDATE: 1981-11-17 SAL: 5000.0
COMM0.0 DEPTNO: 10
*** EMPNO: 7844 ENAME: TURNERJOB: SALESMAN MGR ID: 7698 HIREDATE: 1981-09-08 SAL:
1500.0 COMM0.0 DEPTNO: 30
*** EMPNO: 7876 ENAME: ADAMSJOB: CLERK MGR ID: 7788 HIREDATE: 1987-05-23 SAL: 1100.0
COMM0.0 DEPTNO: 20
*** EMPNO: 7900 ENAME: JAMESJOB: CLERK MGR ID: 7698 HIREDATE: 1981-12-03 SAL: 950.0
COMM0.0 DEPTNO: 30
*** EMPNO: 7902 ENAME: FORDJOB: ANALYST MGR ID: 7566 HIREDATE: 1981-12-03 SAL: 3000.0
COMM0.0 DEPTNO: 20
*** EMPNO: 7934 ENAME: MILLERJOB: CLERK MGR ID: 7782 HIREDATE: 1982-01-23 SAL: 1300.0
COMM0.0 DEPTNO: 10

$  

Changing a WebRowSet Object and Synchronizing Back to the Datasource

Similar to CachedRowSet object, you can make changes locally to a WebRowSet object (i.e., delete/update existing rows or add/insert new rows) and then synchronize the changes back to the underlying datasource, and you have the same ability to detect and resolve potential conflicts with changes performed by other threads/clients. But unlike CachedRowSet, the XML document representing the WebRowSet captures the details of the changes. Let’s look in greater detail at each case (i.e., insert, update, delete) and their corresponding code snippet(s).

Inserting a New Row in a WebRowSet Object

Similar to inserting a row in a CachedRowSet, inserting a new row in a WebRowSet object consists of saving the current position of the cursor (for later restoration), setting the cursor to the pseudo insertRow position, setting a value for columns, inserting the new row in the rowset, restoring the cursor position (saved earlier), and finally synchronizing the changes back to the datasource. The following code snippet illustrates the main steps. See Listing 8.12 for a complete Java example of WebRowSet modification.

    // save current cursor position then move to insertRow
    wrset.moveToInsertRow();

    // set values to columns
    wrset.updateString(1, "DEPT1 ");
    wrset.updateString(2, "1234");

    // insert the new row in the rowset
    wrset.insertRow();

    // set the cursor to the saved position
    wrset.moveToCurrentRow();

    // later, synchronize the changes back to the data source
    wrset.acceptChanges();

Here is the insertRow tag entry of the standard webrowset.xsd:

<xs:element name="insertRow" minOccurs="0"
maxOccurs="unbounded">
 <xs:complexType>
  <xs:choice minOccurs="0" maxOccurs="unbounded">
      <xs:element ref="wrs:columnValue" />
      <xs:element ref="wrs:updateValue" />
  </xs:choice>
 </xs:complexType>
</xs:element>

And here is the insertRow entry when inserting a new row into the DEPT table–based WebRowSet:

   <insertRow>
     <columnValue>60</columnValue>
     <columnValue>DEPT 60</columnValue>
     <columnValue>CYRACUSE</columnValue>
   </insertRow>

See Listing 8.12 for a complete example of WebRowSet modification.

Deleting a Row in a WebRowSet Object

Similar to deleting a row in a CachedRowSet, deleting a row in a WebRowSet object consists of navigating to the row in question (i.e., position the cursor), marking it as “deleted” using deleteRow method, and then synchronizing the changes back to the datasource using the acceptChanges() method of the rowset object. The following code snippet deletes the fourth row in the object referenced by wrset. See Listing 8.12 for a complete Java example of WebRowSet modification.

// Move the cursor to the row to be deleted
wrest.absolute(4); // 4th row

// Mark Row as "deleted"
wrset.deleteRow();

// later, synchronize changes to the data source
wrest.acceptChanges();

Here is the deleteRow tag entry of the standard webrowset.xsd:

<xs:element name="deleteRow" minOccurs="0" maxOccurs="unbounded">
  <xs:complexType>
    <xs:sequence minOccurs="0" maxOccurs="unbounded">
       <xs:element ref="wrs:columnValue" />
       <xs:element ref="wrs:updateValue" />
    </xs:sequence>
  </xs:complexType>
</xs:element>

And here is the deleteRow entry when deleting a row in the DEPT table– based WebRowSet:

<deleteRow>
  <columnValue>50</columnValue>
  <columnValue>MARKETING</columnValue>
  <columnValue>San Diego</columnValue>
</deleteRow>

Updating a Row in a WebRowSet

Similar to updating a CachedRowSet, modifying a row in a WebRowSet consists of navigating to the row in question and making changes to columns using the appropriate updateXXX() method(s), then applying the changes to the row using the updateRow() method on the rowset, and finally synchronizing back the changes to the datasource by invoking the acceptChanges() method. However, the last step could be delayed and batched with other changes (batch updates). The following code snippet moves the cursor to a specific row, performs changes to columns, and applies the changes to the rowset. See the complete example in Listing 8.12.

// Navigate to the row to be modified (1st row in this case)
wrset.beforeFirst();
wrset.next();
id = wrset.getInt(1);    // Get Value
wrset.updateInt(1,4545); // Update Int column
wrest.updateDouble("comm.", 1.19d);
wrest.updateString(3, "blahblah");

// Confirm changes in the Web RowSet object first
wrset.updateRow();

// Get a connection and persist the changes to the data source
conn = ods.getConnection(); // Retrieve a connection
wrset.acceptChanges(conn); 

Here is the modifyRow tag entry of the standard webrowset.xsd.

<xs:element name="modifyRow" minOccurs="0"
maxOccurs="unbounded">
  <xs:complexType>
    <xs:sequence minOccurs="0" maxOccurs="unbounded">
      <xs:element ref="wrs:columnValue" />
      <xs:element ref="wrs:updateValue" />
    </xs:sequence>
  </xs:complexType>
</xs:element>

And here is the modifyRow entry when updating an existing row in the DEPT table–based WebRowSet:

    <currentRow>
      <columnValue>40</columnValue>
      <columnValue>OPERATIONS</columnValue>
      <updateValue>NEW OPERATION</updateValue>
      <columnValue>BOSTON</columnValue>
      <updateValue>BANGALORE</updateValue>
    </currentRow> 

Notice that columnValue and updateValue elements help the rowset keep track of the original values in the datasource when the rowset was created and the new values (i.e., changes applied to the rowset by this client). See the complete code sample in Listing 8.12.

Example 8.12. modWebRowSet.java

import java.sql.*;
import java.io.*;
import oracle.sql.*;
import oracle.jdbc.*;
import oracle.jdbc.pool.OracleDataSource;
import javax.sql.*;
import oracle.jdbc.rowset.*;
public class modWebRowSet
{
  public static void main (String[] args) throws SQLException
  {
    // Create an OracleDataSource
    OracleDataSource ods = new OracleDataSource();

    // Set the URL, using TNS Alias with JDBC-Thin
    String url = "jdbc:oracle:thin:scott/tiger@inst1";
    ods.setURL(url);
    Connection conn = ods.getConnection(); // Retrieve a connection
    conn.setAutoCommit(false); // Disable Auto Commit

    Statement stmt = conn.createStatement();
    ResultSet rset = stmt.executeQuery ("select * from dept");

    // Create and populate the OracleWebRowSet with the ResultSet
    System.out.println ("*** Create and populate WebRowSet");
    OracleWebRowSet wrset = new OracleWebRowSet();
     wrset.populate(rset);

    // close the statement, connection and result set
    System.out.println("*** Close Connection and other resources");
    stmt.close();
    conn.close();
    rset.close();

     /*
      * Delete a row in WebRowSet where Deptno=50
      */
       System.out.println("*** Delete Row #50 ");
       // Move the cursor to the row to be deleted
       wrset.absolute(5); // 5th row
      // Mark Row as "deleted"
       wrset.deleteRow();

     /*
      * Update a row in WebRowSet where Deptno=40
      */
       System.out.println("***  Update  Row #40 ");
      // Set the cursor to 4th row and update DNAME and LOCATIN
      wrset.absolute(4); // 4th row
      wrset.updateString(2, "NEW OPERATION");
      wrset.updateString(3, "BANGALORE");
      // Confirm changes in the Web RowSet object first
      wrset.updateRow();

     /*
      * Insert a new row in WebRowSet with Deptno=60
      */
     System.out.println("*** Insert New Row #60");
      // save current cursor position then move to insertRow
      wrset.moveToInsertRow();
      // set values to columns
      wrset.updateInt(1, 60);
      wrset.updateString(2, "DEPT 60");
      wrset.updateString(3, "CYRACUSE");
     // insert the new row in the rowset
     wrset.insertRow();
     // set the cursor to the saved position
     wrset.moveToCurrentRow();
       try {
        // Dump the WebRowSet before synchronizing with data source
        FileWriter printXml = new FileWriter("modWRS_before.xml");
        wrset.writeXml(printXml);
        } catch (IOException exc) {
          System.out.println("...... Couldn't create a FileWriter");
       }
     /*
      * Synchronize changes back to the data source.
      */
      System.out.println("***  Synchronize changes to DS");
      wrset.setUrl("jdbc:oracle:thin:scott/tiger@inst1");
      wrset.setUsername("scott"); // working around a bug
      wrset.setPassword("tiger");
      conn = ods.getConnection(); // Retrieve a connection
      wrset.setTableName("dept"); // Needed for accaptChanges()
      // Synchrronize changes back to the data source
      wrset.acceptChanges(conn);
      conn.close();
      try {
        // Dump the WebRowSet after synchronizing with data source
        FileWriter printXml = new FileWriter("modWRS_after.xml");
        wrset.writeXml(printXml);
        // Print the Web rowset
        System.out.println("*** Output Web RowSet as XML");
        wrset.beforeFirst();
        for (int i = 0; wrset.next () ; ++i)
        System.out.println ("*** DEPTPNO: " + wrset.getInt (1)
            + " DNAME: " + wrset.getString (2)
            + " LOCATION: " + wrset.getString(3));
        wrset.close ();
        } catch (IOException exc) {
         System.out.println("...... Couldn't create a FileWriter");
       }
  }
}

Notice the wrest.setTableName("dept"); this is required before invoking wrest.acceptChanges(...), which can’t update more than one table.

Let’s query the DEPT Table, before the execution of modWebRowSet.java:

SQL> select * from dept
  2  /

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING      LAS VEGAS
        20 RESEARCH        DALLAS
        30 SALES          CHICAGO
        40 OPERATIONS      BOSTON
        50 MARKETING       San Diego

SQL> exit
Disconnected from Oracle Database 10g Enterprise Edition Release
10.2.0.1.0 – Production With the Partitioning and Data Mining options

$ javac modWebRowSet.java
$ java -Doracle.net.tns_admin=$TNS_ADMIN modWebRowSet
*** Create and populate WebRowSet
*** Close Connection and other resources
*** Delete Row #50
***  Update  Row #40
*** Insert New Row #60
***  Synchronize changes to DS
*** Output Web RowSet as XML
*** DEPTPNO: 10 DNAME: ACCOUNTING LOCATION: LAS VEGAS
*** DEPTPNO: 20 DNAME: RESEARCH LOCATION: DALLAS
*** DEPTPNO: 30 DNAME: SALES LOCATION: CHICAGO
*** DEPTPNO: 40 DNAME: NEW OPERATION LOCATION: BANGALORE
*** DEPTPNO: 60 DNAME: DEPT 60 LOCATION: CYRACUSE

Let’s query the DEPT Table, after the execution of modWebRowSet.java:

$ sqlplus scott/tiger

SQL*Plus: Release 10.2.0.1.0 - Production on Wed Nov 16
14:34:47 2005
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

SQL> select * from dept;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 ACCOUNTING      LAS VEGAS
        20 RESEARCH       DALLAS
        30 SALES          CHICAGO
        40 NEW OPERATION  BANGALORE
        60 DEPT 60        CYRACUSE

SQL>exit

And here is the <data> section of modWRS_after.xml:

<data>
     <currentRow>
        <columnValue>10</columnValue>
        <columnValue>ACCOUNTING</columnValue>
        <columnValue>LAS VEGAS </columnValue>
      </currentRow>
      <currentRow>
       <columnValue>20</columnValue>
       <columnValue>RESEARCH</columnValue>
       <columnValue>DALLAS</columnValue>
     </currentRow>
     <currentRow>
       <columnValue>30</columnValue>
       <columnValue>SALES</columnValue>
       <columnValue>CHICAGO</columnValue>
     </currentRow>
     <currentRow>
       <columnValue>40</columnValue>
       <columnValue>NEW OPERATION</columnValue>
       <columnValue>BANGALORE</columnValue>
     </currentRow>
    <currentRow>
      <columnValue>60</columnValue>
      <columnValue>DEPT 60</columnValue>
      <columnValue>CYRACUSE</columnValue>
    </currentRow>
  </data>

FilteredRowSet and OracleFilteredRowSet

In the SQL world, a WHERE clause allows you to specify a filtering criterion, which, if true, returns only rows that meet the criterion in question. What if you need to filter disconnected rows of a CachedRowSet or WebRowSet object? A FilteredRowSet object simulates a SQL WHERE clause by allowing you to apply user-defined filtering criteria on rows in a disconnected rowset; in other words, it returns/retrieves only rows that meet the defined criteria, and inserts/updates only rows that meet the constraints defined in the filter(s). The trick is to define constraints within a predicate class, which implement the javax.sql.rowset.Predicate interface,and then assign this predicate to theFilteredRowSet object; as a result, the constraints defined in the predicate class will be evaluated against each row in the rowset object.

The Oracle JDBC implements the standard javax.sql.rowset.FilteredRowSet andjavax.sql.rowset.Predicate interfaces in ojdbc14.jar and furnishes the oracle.jdbc.rowset.OracleFileteredRowSet class and oracle.jdbc.rowset.OraclePredicate interface for classes12.jar.

In order to use filtered rowsets (i.e., FilteredRowSet orOracleFilteredRowSet), the JDBC applications must:

  1. Define and implement the Predicate interface.

  2. Create an instance of FilteredRowSet object and set properties.

  3. Populate the FilteredRowSet.

  4. Set/enable filters.

  5. Retrieve the filtered rows.

The following sections describe these steps individually, and at the end a complete example is given (Listings 8.13 and 8.14).

Define and Implement the Predicate Interface

The JSR-114 (RowSet) does not currently furnish standard filters; the JDBC application must implement the javax.sql.rowset.Predicate or oracle.jdbc.rowset.OraclePredicate interfaces.

The filtering criterion within a Predicate object is materialized by the implementations of the Boolean evaluate method, which has the following signatures:

evaluate(javax.sql.RowSet rowSet);
evaluate(java.lang.Object value, int columnIndex);
evaluate(java.lang.Object value, java.lang.String
columnName);

The evaluate method returns true orfalse, depending on whether the rows in the rowset meet the defined constraint. One commonly used type of constraint consists of defining a range of values that the filtering column must match:

low boundary <= columnName <= high boundary

or:

low boundary <= columnIndex <= high boundary

with:

  • The (inclusive) high boundary of the range of values that meet the criterion

  • The (inclusive) low boundary of the range of values that meet the criterion

  • The column index or column name of the set of values that must fall between these boundaries (e.g., salary, job)

Listing 8.13 is an example of a Predicate class implementation using the type of constraint described previously.

Example 8.13. Predicate1.java

import java.util.*;
import java.sql.*;
import javax.sql.*;
import javax.sql.rowset.Predicate;

public class Predicate1 implements Predicate {

     private int  low;
     private int high;
     private String columnName;
     private int columnIndex;

     public Predicate1 (int low, int high, int colIndex) {
        this.low = low;
        this.high= high;
        this.columnIndex = colIndex;
        columnName = new String("");
       }

      public Predicate1 (int low, int high, String colName) {
          this.low = low;
          this.high= high;
          this.columnName = colName;
          columnIndex = 0;
      }

     public boolean evaluate(RowSet rs){
        int colvalue = 0;
        try{
          colvalue = rs.getInt(columnIndex);
          if (colvalue >= low && colvalue <= high ){
              return true;
           } else {
             return false;
          }
        } catch(SQLException sqle){
            return false;
        }
       }

     public boolean evaluate(java.lang.Object obj, int colIndex)
     {
          // cast Object to Integer then get it's value
          int colvalue = ((Integer)obj).intValue();

           if (colvalue >= low && colvalue <= high ){
              return true;
            } else {
              return false;
           }
      }

     public boolean evaluate(java.lang.Object obj, String colName)
     {
          // cast Object to Integer then get it's value
           int colvalue = ( (Integer)obj).intValue();

           if (colvalue >= low && colvalue >= high) {
              return true;
           }  else {
              return false;
          }
      }

}

Creating a FilteredRowSet Object and Setting Properties

Now that we have defined the filering criteria, we need to create an empty filtered rowset object and set some properties, as shown in the following code snippet:

// Create an OacleFilteredRowSet
OracleFilteredRowSet ofrs = new OracleFilteredRowSet();

// set properties
ofrs.setComand(" SELECT * from emp");
ofrs.setUrl (<URL>); // could also be set on the data source object
ofrs.setUsername("SCOTT");
ofrs.setPassword("TIGER");

// set ResultSet type (optional)
// ofrs.setType(ResultSet.TYPE_FORWARD_ONLY);
// ofrs.setType(ResultSet.TYPE_SCROLL_INSENSITIVE);
// Set concurrenty control (optional)
// ofrs.setConcurrency(ResultSet.CONCUR_UPDATABLE);

Populating the FilteredRowSet

FilteredRowset objects are populated using either execute() orpopulate() methods, with the same semantics as CachedRowSet and WebRowSet objects.

ofrs.execute(conn); // alternatively ofrs.populate();

At this stage, the filtered rowset has been prepopulated from the data-source using a query command; we can disconnect from the datasource and enable the filter.

Enabling Filters

// This Filter retrieves rows with col1 in (50,100) and col2
in (100,200)
int low[] = {50, 100};
int high[] = {100, 200};
int indexes[] = {1, 2};
ofrs.setFilter(new Predicate1(low, high, indexes));

// Retrieve the active predicate
Predicate p = ofrs.getFilter();
System.out.println("The active predicate: " +
p.getClass().getName());

Retrieving Filtered Rows and Closing the FilteredRowSet Object

The usual rowset navigation applies here:

ofrs.beforeFirst();

for (int i = 0; ofrs.next () ; ++i) {...}

Good programming practice recommends to close/release objects when you no longer need them:

// clode the FilteredRowSet object
ofrs.close();

Rows that do not meet the filtering criteria cannot be modified; also, only “filtered” rows can be synchronized with the datasource. A new filter (with different criteria) may be applied to an existing FilteredRowSet object, which will result in retrieving a different set of rows.

Example 8.14. myFilteredRowSet.java

import java.sql.*;
import java.util.*;
import javax.sql.RowSet;
import javax.sql.rowset.Predicate;
import oracle.sql.*;
import oracle.jdbc.rowset.*;
import oracle.jdbc.pool.OracleDataSource;

public class myFilteredRowSet
{
  public static void main(String[] args) throws SQLException
  {
      // Create an OracleDataSource
      OracleDataSource ods = new OracleDataSource();

      // Set the URL, using TNS Alias with JDBC-Thin
      String url = "jdbc:oracle:thin:scott/tiger@inst1";
      ods.setURL(url);
     Connection conn = ods.getConnection(); // Retrieve a connection

      conn.setAutoCommit(false); // Disable Auto Commit
      System.out.println ("*** Create and populate FilteredRowSet");
      OracleFilteredRowSet ofrs = new OracleFilteredRowSet();
      ofrs.setUsername("SCOTT");
      ofrs.setPassword("TIGER");
      ofrs.setCommand("select * from emp");
      // populate the FilteredRowSet
      ofrs.execute(conn);

      // close the connection and result set
      System.out.println("*** Close Connection and other resources");
      conn.close();

      // Let employees with deptno in (10, 20) pass through
      Predicate1 predic1 = new Predicate1 ( 10, 20, 8);
      ofrs.setFilter(predic1);

      // Retrieve the active predicate
      Predicate p2 = ofrs.getFilter();
      System.out.println("*** The active predicate is: " +
                                    p2.getClass().getName());

      // loop through the FilteredRowset and print
      ofrs.beforeFirst();
      for (int i = 0; ofrs.next () ; ++i)
      System.out.println ("*** EMPNO: " + ofrs.getInt (1)
                + " ENAME: " + ofrs.getString (2)
                + " JOB: " + ofrs.getString(3)+ " MGR ID: " +
    ofrs.getInt(4)
                + " HIREDATE: " + ofrs.getDate(5)
               + " SAL: " + ofrs.getFloat(6) + " COMM" + ofrs.getFloat(7)
               + " DEPTNO: " +  ofrs.getInt(8) );
          ofrs.close ();
   }
}

$ javac myFilteredRowSet.java
$ java -Doracle.net.tns_admin=$TNS_ADMIN myFilteredRowSet

*** Create and populate FilteredRowSet
*** Close Connection and other resources
*** The active predicatei is: Predicate1
*** EMPNO: 7369 ENAME: SMITHJOB: CLERK MGR ID: 7902 HIREDATE: 1980-12-
17 SAL: 800.0 COMM0.0 DEPTNO: 20
*** EMPNO: 7566 ENAME: JONESJOB: MANAGER MGR ID: 7839 HIREDATE: 1981-
04-02 SAL: 2975.0 COMM0.0 DEPTNO: 20
*** EMPNO: 7782 ENAME: CLARKJOB: MANAGER MGR ID: 7839 HIREDATE: 1981-
06-09 SAL: 2450.0 COMM0.0 DEPTNO: 10
*** EMPNO: 7788 ENAME: SCOTTJOB: ANALYST MGR ID: 7566 HIREDATE: 1987-
04-19 SAL: 3000.0 COMM0.0 DEPTNO: 20
*** EMPNO: 7839 ENAME: KINGJOB: PRESIDENT MGR ID: 0 HIREDATE: 1981-11-
17 SAL: 5000.0 COMM0.0 DEPTNO: 10
*** EMPNO: 7876 ENAME: ADAMSJOB: CLERK MGR ID: 7788 HIREDATE: 1987-05-
23 SAL: 1100.0 COMM0.0 DEPTNO: 20
*** EMPNO: 7902 ENAME: FORDJOB: ANALYST MGR ID: 7566 HIREDATE: 1981-
12-03 SAL: 3000.0 COMM0.0 DEPTNO: 20
*** EMPNO: 7934 ENAME: MILLERJOB: CLERK MGR ID: 7782 HIREDATE: 1982-
01-23 SAL: 1300.0 COMM0.0 DEPTNO: 10
$

JoinRowSet and OracleJoinRowSet

In the SQL world, a JOIN operation is a query involving at least two tables in the FROM clause and one or multiple JOIN predicate(s) in the WHERE clause, as in the following join query:

SELECT * from EMP, DEPT WHERE EMP.DEPTNO = DDEPT.DEPTNO;

In order for the JOIN predicate to evaluate to true, values of columns in one table must match values of column(s) in the other table; these columns are called the matching columns (e.g., EMP.DEPTNO andDDEPT.DEPTNO).

Similarly, the javax.sql.rowset.JoinRowSet interface lets you simulate a SQL JOIN operation between disconnected RowSet objects. A JoinRowSet object combines data from multiple rowsets. The Oracle JDBC implements the standard javax.sql.rowset.JoinRowSet interface in ojdbc14.jar and furnishes the oracle.jdbc.rowset.OracleJoinRowSet class for classes12.jar. In addition, for all rowset types, ojdbc14,jar also implements the javax.sql.rowset.Joinable interface, while classes12.jar implements the oracle.jdbc.rowset.Joinable interface.

In order to use Join rowsets (i.e., JoinRowSet orOracleJoinRowSet), the JDBC applications must:

  • Create an empty instance of JoinRowSet object and set properties.

  • Create and add RowSets objects to the JoinRowSet.

  • Define the match column (similar to SQL matching columns).

  • Navigate and consume the JoinRowSet Object.

The following sections describe these steps individually, and at the end a complete example is given (Listing 8.15).

Creating an Empty Instance of JoinRowSet Object and Setting Properties

 // Set the URL, using TNS Alias with JDBC-Thin
 String url = "jdbc:oracle:thin:scott/tiger@inst1";
 ods.setURL(url);
 Connection conn = ods.getConnection(); // Retrieve a
connection
 conn.setAutoCommit(false); // Disable Auto Commit
  ...
 OracleJoinRowSet ojrs = new OracleJoinRowSet();

Creating and Adding RowSet Objects to the JoinRowSet

  • The addRowSet () method has the following signatures and allows you to add RowSet objects to the JoinRowSet or OracleJoinRowSet object:

    addRowSet(Joinable rowSet)
    addRowSet(javax.sql.RowSet rowSet, int matchColumnIndex)
    addRowSet(javax.sql.RowSet rowSet,
          java.lang.String matchColumnName)
    addRowSet(javax.sql.RowSet[] rowSets,
          int[] matchColumnIndexes)
    addRowSet(javax.sql.RowSet[] rowSets,
          java.lang.String[] matchColumnNames)
  • The getRowSets() method allows you to retrieve a java.util.Collection object containing all rowset objects that have been added to this JoinRowSet object.

  • The getRowSetNames() method allows you to retrieve a String array containing the names of all RowSet objects that have been added to this JoinRowSet object.

Defining the Match Column(s)

Many of the aforementioned addRowSet() methods have either a matchColumnIndex or a matchColumnName parameter, which allows you to specify the match column.

ojrs.addRowSet(emprs, "DEPTNO");
ojrs.addRowSet(deptrs, 1);

The first rowset added to the join rowset imposes the match column to other rowsets that will be added later.

The setMatchColumn() method, inherited from the Joinable interface, is an alternative approach for setting the match column on a rowset object before adding it to the JoinRowSet object.

ocrs.setMatchColumn(3);
...
ojrs.addRowSer(ocrs);

The Joinable interface furnishes, in addition, the getMatchColumn() and unsetMatchColumn() methods for retrieving and unsetting the match column(s).

Multiple match columns may be specified as an array of int:

int[] matchcolint = {2, 3, 4};
  ...
ocrs.setMatchColumns(matchcolint);
ojrs.addRowSet(ocrs);

or an array of String objects:

String [] matchcolstr = {"DEPTNO", "LOCATION");
  ...
ocrs.setMatchColumns(matchcolint);
ojrs.addRowSet(ocrs);

The JOIN types include INNER JOIN, CROSS JOIN, and OUTER JOINS (FULL JOIN, LEFT JOIN, RIGHT JOIN). The following methods are specific to each JOIN type and return a Boolean indicating whether the type in question is supported or not:

  • supportsInnerJoin(): Returns only rows that satisfy the join condition; this is the default and only JOIN type currently supported in OracleJoinRowSet.

  • supportsCrossJoin(): Returns the combination of each row from the first table with each row from the second table.

  • supportsLeftOuterJoin(): Returns inner join results plus rows from the left table in the from clause list, for which no row from the right table satisfies the join condition.

  • supportsRightOuterJoin(): Returns inner join results plus rows from the right table (in the from clause list), for which no row from the left table satisfies the join condition.

  • supportsFullJoin(): This is a two-sided outer join, because it returns the results of both left join and right join.

See the Oracle Database SQL reference doc for more details on JOIN types.

  • The setJoinType() method allows you to define the JOIN type to be assigned to the JoinRowSet object. INNER JOIN is currently the default and the only supported type; therefore, you can ignore this method for now.

  • The getJoinType() method returns the JOIN type of the JoinRowSet object; it can be ignored for now.

  • The getWhereClause() method returns a description of the pseudo-WHERE clause.

    ojrs.getWhereClause()

Navigating and Consuming the JoinRowSet Objects

The usual rowset navigation applies here:

ojrs.beforeFirst();
...
for (int i = 0; ojrs.next () ; ++i) {...}

Example 8.15. myoinRowSet.java

import java.sql.*;
import java.util.*;
import javax.sql.RowSet;
import oracle.sql.*;
import oracle.jdbc.rowset.*;
import oracle.jdbc.pool.OracleDataSource;
/*
 * Create an OracleJoinRowSet corresponding to
 * SELECT * FROM EMP, DEPT WHERE EMP.DEPTNO = DEPT.DEPTNO
 *
 */
public class myJoinRowSet
{
  public static void main(String[] args) throws SQLException
  {
      // Create an OracleDataSource
      OracleDataSource ods = new OracleDataSource();

      // Set the URL, using TNS Alias with JDBC-Thin
      String url = "jdbc:oracle:thin:scott/tiger@inst1";
      ods.setURL(url);
      Connection conn = ods.getConnection(); // Retrieve a connection
      conn.setAutoCommit(false); // Disable Auto Commit

      OracleCachedRowSet ocrs1 = new OracleCachedRowSet();
      ocrs1.setCommand("SELECT * FROM DEPT");
      ocrs1.setUrl(url);
      ocrs1.setUsername("scott");
      ocrs1.setPassword("tiger");
      System.out.println ("*** Populate CRowSet #1 with rows from DEPT
 table");
      ocrs1.execute();

      OracleCachedRowSet ocrs2 = new OracleCachedRowSet();
      ocrs2.setCommand("SELECT * FROM EMP");
      ocrs2.setUrl(url);
      ocrs2.setUsername("scott");
      ocrs2.setPassword("tiger");
      System.out.println ("*** Populate CRowSet #2 with rows from EMP table");
      ocrs2.execute();

      System.out.println("*** Close the Connection");
      conn.close();

      System.out.println ("*** Create an empty JoinRowSet");
      OracleJoinRowSet ojrs = new OracleJoinRowSet();
      System.out.println ("*** Adding CRowSet#1 with DEPTNO as Match Column");
      ojrs.addRowSet(ocrs1, "DEPTNO");
      System.out.println ("*** Adding CRowSet#2 with DEPTNO as Match Column");
      ojrs.addRowSet(ocrs2, "DEPTNO");

      System.out.println ("*** WHERE Clause: " + ojrs.getWhereClause());
      // loop through the JoinRowset and print
      ojrs.beforeFirst();
      System.out.println ("DEPTNO DNAME  LOCATION ENAME    JOB   EMPNO" +
                                                         "HIRED  MGR ");
      System.out.println ("****** ****** ******** ****  ******** ******* " +
                                                         " **** ******** ");
      for (int i = 0; ojrs.next () ; ++i)
     System.out.println (ojrs.getString(1) + " " +  ojrs.getString(2) + " " +

      ojrs.getString(3) + " " +  ojrs.getString(4) + " "  + ojrs.getString(5)
+
       " " + ojrs.getString(6) + " " + ojrs.getString(7) + " " +
       ojrs.getString(8));

       ojrs.close ();
   }
}

$ javac myJoinRowSet.java
$ java -Doracle.net.tns_admin=$TNS_ADMIN myJoinRowSet

*** Populate Cached RowSet #1 with rows from DEPT table
*** Populate Cached RowSet #2 with rows from EMP table
*** Close the Connection
*** Create an empty JoinRowSet
*** Adding CRowSet#1 with DEPTNO as Match Column
*** Adding CRowSet#2 with DEPTNO as Match Column
*** WHERE Clause: WHERE
(null.DEPTNO = null.DEPTNO);
DEPTNO DNAME  LOCATION ENAME  JOB      EMPNO    HIRED   MGR
****** ****** ******** ****  ******** *******i  **** ********
10 ACCOUNTING LAS VEGAS CLARK MANAGER 7839 1981-06-09 2450
10 ACCOUNTING LAS VEGAS KING PRESIDENT null 1981-11-17 5000
10 ACCOUNTING LAS VEGAS MILLER CLERK 7782 1982-01-23 1300
20 RESEARCH DALLAS SMITH CLERK 7902 1980-12-17 800
20 RESEARCH DALLAS JONES MANAGER 7839 1981-04-02 2975
20 RESEARCH DALLAS SCOTT ANALYST 7566 1987-04-19 3000
20 RESEARCH DALLAS ADAMS CLERK 7788 1987-05-23 1100
20 RESEARCH DALLAS FORD ANALYST 7566 1981-12-03 3000
30 SALES CHICAGO ALLEN SALESMAN 7698 1981-02-20 1600
30 SALES CHICAGO WARD SALESMAN 7698 1981-02-22 1250
30 SALES CHICAGO MARTIN SALESMAN 7698 1981-09-28 1250
30 SALES CHICAGO BLAKE MANAGER 7839 1981-05-01 2850
30 SALES CHICAGO TURNER SALESMAN 7698 1981-09-08 1500
30 SALES CHICAGO JAMES CLERK 7698 1981-12-03 950
$ 

Further Considerations

The toCachedRowSet() method allows dumping the data of a JoinRowSet, which has been applied to the acceptChanges method, into a CachedRowSet for presumably further synchronization back to the datasource.

Conclusion

Throughout this long chapter, you have learned (or refreshed your memory on) how to: (1) use key metadata in JDBC, (2) manipulate Oracle SQL data types in JDBC, and (3) use Result Sets and RowSet (JSR-114). The next chapter discusses some of the quality of services in JDBC, such as transaction management, security management, and some best practices.



[1] In fact, in 10g, the conversion happens in the server, so the binders only prepare all needed information.

[2] There is no corresponding class for LONG, LONGRAW, and REF CURSOR.

[3] For server-side JDBC, this limit is 4,000 bytes for SQL operations, as opposed to PL/SQL operations.

[5] A type descriptor of OPAQUE data type.

[7] In other words, “Not Production Quality”!

[8] Alfred Korzybski, “The map is not the territory”; the analogy is not accurate but I like the formula.

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

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