PK
à=–Aoa«, mimetypeapplication/epub+zipPK à=–A iTunesMetadata.plistK´û
This chapter describes Oracle extensions to standard Java Database Connectivity (JDBC) that let you access and manipulate Oracle collections, which map to Java arrays, and their data. The following topics are discussed:
An Oracle collection, either a variable array (VARRAY) or a nested table in the database, maps to an array in Java. JDBC 2.0 arrays are used to materialize Oracle collections in Java. The terms collection and array are sometimes used interchangeably. However, collection is more appropriate on the database side and array is more appropriate on the JDBC application side.
Oracle supports only named collections, where you specify a SQL type name to describe a type of collection. JDBC enables you to use arrays as any of the following:
Columns in a SELECT
clause
IN
or OUT
bind variables
Attributes in an Oracle object
Elements of other arrays
This section covers the following topics:
In your application, you have the choice of materializing a collection as an instance of the oracle.sql.ARRAY
class, which is weakly typed, or materializing it as an instance of a custom Java class that you have created in advance, which is strongly typed. Custom Java classes used for collections are referred to as custom collection classes. A custom collection class must implement the Oracle oracle.sql.ORAData
interface. In addition, the custom class or a companion class must implement oracle.sql.ORADataFactory
. The standard java.sql.SQLData
interface is for mapping SQL object types only.
The oracle.sql.ARRAY
class implements the standard java.sql.Array
interface.
The ARRAY
class includes functionality to retrieve the array as a whole, retrieve a subset of the array elements, and retrieve the SQL base type name of the array elements. However, you cannot write to the array, because there are no setter methods.
Custom collection classes, as with the ARRAY
class, enable you to retrieve all or part of the array and get the SQL base type name. They also have the advantage of being strongly typed, which can help you find coding errors during compilation that might not otherwise be discovered until run time.
Furthermore, custom collection classes produced by JPublisher offer the feature of being writable, with individually accessible elements.
Note: There is no difference in the code between accessing VARRAYs and accessing nested tables.ARRAY class methods can determine if they are being applied to a VARRAY or nested table, and respond by taking the appropriate actions. |
See Also: For more information about custom collection classes, see "Custom Collection Classes with JPublisher". |
Because Oracle supports only named collections, you must declare a particular VARRAY
type name or nested table type name. VARRAY and nested table are not types themselves, but categories of types.
A SQL type name is assigned to a collection when you create it using the SQL CREATE TYPE
statement:
CREATE TYPE <sql_type_name> AS <datatype>;
A VARRAY is an array of varying size. It has an ordered set of data elements, and all the elements are of the same data type. Each element has an index, which is a number corresponding to the position of the element in the VARRAY. The number of elements in a VARRAY is the size of the VARRAY. You must specify a maximum size when you declare the VARRAY
type. For example:
CREATE TYPE myNumType AS VARRAY(10) OF NUMBER;
This statement defines myNumType
as a SQL type name that describes a VARRAY of NUMBER
values that can contain no more than 10 elements.
A nested table is an unordered set of data elements, all of the same data type. The database stores a nested table in a separate table which has a single column, and the type of that column is a built-in type or an object type. If the table is an object type, then it can also be viewed as a multi-column table, with a column for each attribute of the object type. You can create a nested table as follows:
CREATE TYPE myNumList AS TABLE OF integer;
This statement identifies myNumList
as a SQL type name that defines the table type used for the nested tables of the type INTEGER
.
The most common way to create a new multilevel collection type in JDBC is to pass the SQL CREATE TYPE
statement to the execute
method of the java.sql.Statement
class. The following code creates a one-level nested table, first_level
, and a two- levels nested table, second_level
:
Connection conn = .... // make a database // connection Statement stmt = conn.createStatement(); // open a database // cursor stmt.execute("CREATE TYPE first_level AS TABLE OF NUMBER"); // create a nested // table of number stmt.execute("CREATE TYPE second_level AS TABLE OF first_level"); // create a // two-levels nested table ... // other operations here stmt.close(); // release the // resource conn.close(); // close the // database connection
Once the multilevel collection types have been created, they can be used as both columns of a base table as well as attributes of a object type.
You can obtain collection data in an array instance through a result set or callable statement and pass it back as a bind variable in a prepared statement or callable statement.
The oracle.sql.ARRAY
class, which implements the standard java.sql.Array
interface, provides the necessary functionality to access and update the data of an Oracle collection.
This section covers Array Getter and Setter Methods. Use the following result set, callable statement, and prepared statement methods to retrieve and pass collections as Java arrays.
Result Set and Callable Statement Getter Methods
The OracleResultSet
and OracleCallableStatement
classes support getARRAY
and getArray
methods to retrieve ARRAY
objects as output parameters, either as oracle.sql.ARRAY
instances or java.sql.Array
instances. You can also use the getObject
method. These methods take as input a String
column name or int
column index.
Prepared and Callable Statement Setter Methods
The OraclePreparedStatement
and OracleCallableStatement
classes support setARRAY
and setArray
methods to take updated ARRAY
objects as bind variables and pass them to the database. You can also use the setObject
method. These methods take as input a String
parameter name or int
parameter index as well as an oracle.sql.ARRAY
instance or a java.sql.Array
instance.
This section discusses the following topics:
The oracle.sql.ARRAY
class contains methods that return array elements as Java primitive types. These methods allow you to access collection elements more efficiently than accessing them as Datum
instances and then converting each Datum
instance to its Java primitive value.
Note: These specialized methods of theoracle.sql.ARRAY class are restricted to numeric collections. |
Each method using the first signature returns collection elements as an XXX
[]
, where XXX
is a Java primitive type. Each method using the second signature returns a slice of the collection containing the number of elements specified by count
, starting at the index
location.
Oracle JDBC driver provides public methods to enable and disable buffering of ARRAY
contents.
The following methods are included with the oracle.sql.ARRAY
class:
It is advisable to enable auto-buffering in a JDBC application when the ARRAY
elements will be accessed more than once by the getAttributes
and getArray
methods, presuming the ARRAY
data is able to fit into the Java Virtual Machine (JVM) memory without overflow.
Important: Buffering the converted elements may cause the JDBC application to consume a significant amount of memory. |
When you enable auto-buffering, the oracle.sql.ARRAY
object keeps a local copy of all the converted elements. This data is retained so that a second access of this information does not require going through the data format conversion process.
If an array is in auto-indexing mode, then the array object maintains an index table to hasten array element access.
The oracle.sql.ARRAY
class contains the following methods to support automatic array-indexing:
By default, auto-indexing is not enabled. For a JDBC application, enable auto-indexing for ARRAY
objects if random access of array elements may occur through the getArray
and getResultSet
methods.
This section discusses how to create array objects and how to retrieve and pass collections as array objects, including the following topics.
Note: Oracle JDBC does not support the JDBC 4.0 methodcreateArrayOf method of java.sql.Connection interface. This method only allows anonymous array types, while all Oracle array types are named. Use the Oracle specific method oracle.jdbc.OracleConnection.createARRAY instead. |
This section describes how to create ARRAY
objects. This section covers the following topics:
Steps in Creating ARRAY Objects
Starting from Oracle Database 11g Release 1 (11.1), you can use the createARRAY
factory method of oracle.jdbc.OracleConnection
interface to create an array object. The factory method for creating arrays has been defined as follows:
public ARRAY createARRAY(java.lang.String typeName,java.lang.Object elements)throws SQLException
where, typeName
is the name of the SQL type of the created object and elements
is the elements of the created object.
Perform the following to create an array:
Create a collection with the CREATE
TYPE
statement as follows:
CREATE TYPE elements AS varray(22) OF NUMBER(5,2);
The two possibilities for the contents of elements
are:
An array of Java primitives. For example, int[]
.
An array of Java objects, such as xxx
[]
, where xxx
is the name of a Java class. For example, Integer[]
.
Construct the ARRAY
object by passing the Java string specifying the user-defined SQL type name of the array and a Java object containing the individual elements you want the array to contain.
ARRAY array = oracle.jdbc.OracleConnection.createARRAY(sql_type_name, elements);
Creating Multilevel Collections
As with single-level collections, the JDBC application can create an oracle.sql.ARRAY
instance to represent a multilevel collection, and then send the instance to the database. The same createARRAY
factory method you use to create single-level collections, can be used to create multilevel collections as well. To create a single-level collection, the elements are a one dimensional Java array, while to create a multilevel collection, the elements can be either an array of oracle.sql.ARRAY[]
elements or a nested Java array or the combinations.
The following code shows how to create collection types with a nested Java array:
// prepare the multilevel collection elements as a nested Java array int[][][] elements = { {{1}, {1, 2}}, {{2}, {2, 3}}, {{3}, {3, 4}} }; // create the ARRAY using the factory method ARRAY array = oracle.jdbc.OracleConnection.createARRAY(sql_type_name, elements);
This section first discusses how to retrieve an ARRAY
instance as a whole from a result set, and then how to retrieve the elements from the ARRAY
instance. This section covers the following topics:
You can retrieve a SQL array from a result set by casting the result set to OracleResultSet
and using the getARRAY
method, which returns an oracle.sql.ARRAY
object. If you want to avoid casting the result set, then you can get the data with the standard getObject
method specified by the java.sql.ResultSet
interface and cast the output to oracle.sql.ARRAY
.
Once you have an ARRAY
object, you can retrieve the data using one of these three overloaded methods of the oracle.sql.ARRAY
class:
Oracle also provides methods that enable you to retrieve all the elements of an array, or a subset.
Note: In case you are working with an array of structured objects, Oracle provides versions of these three methods that enable you to specify a type map so that you can choose how to map the objects to Java. |
getOracleArray
The getOracleArray
method is an Oracle-specific extension that is not specified in the standard Array
interface. The getOracleArray
method retrieves the element values of the array into a Datum[]
array. The elements are of the oracle.sql.*
data type corresponding to the SQL type of the data in the original array.
For an array of structured objects, this method will use oracle.sql.STRUCT
instances for the elements.
Oracle also provides a getOracleArray(
index
,
count
)
method
to get a subset of the array elements.
getResultSet
The getResultSet
method returns a result set that contains elements of the array designated by the ARRAY
object. The result set contains one row for each array element, with two columns in each row. The first column stores the index into the array for that element, and the second column stores the element value. In the case of VARRAYs, the index represents the position of the element in the array. In the case of nested tables, which are by definition unordered, the index reflects only the return order of the elements in the particular query.
Oracle recommends using getResultSet
when getting data from nested tables. Nested tables can have an unlimited number of elements. The ResultSet
object returned by the method initially points at the first row of data. You get the contents of the nested table by using the next
method and the appropriate get
XXX
method. In contrast, getArray
returns the entire contents of the nested table at one time.
The getResultSet
method uses the default type map of the connection to determine the mapping between the SQL type of the Oracle object and its corresponding Java data type. If you do not want to use the default type map of the connection, another version of the method, getResultSet(
map
)
, enables you to specify an alternate type map.
Oracle also provides the getResultSet(
index
,
count
)
and getResultSet(
index
,
count
,
map
)
methods to retrieve a subset of the array elements.
getArray
The getArray
method is a standard JDBC method that returns the array elements as a java.lang.Object
, which you can cast as appropriate. The elements are converted to the Java types corresponding to the SQL type of the data in the original array.
Oracle also provides a getArray(
index
,
count
)
method to retrieve a subset of the array elements.
If you use getOracleArray
to return the array elements, then the use by that method of oracle.sql.Datum
instances avoids the expense of data conversion from SQL to Java. The non-character data inside the instance of a Datum
class or any of its subclass remains in raw SQL format.
If you use getResultSet
to return an array of primitive data types, then the JDBC driver returns a ResultSet
object that contains, for each element, the index into the array for the element and the element value. For example:
ResultSet rset = intArray.getResultSet();
In this case, the result set contains one row for each array element, with two columns in each row. The first column stores the index into the array and the second column stores the element value.
If the elements of an array are of a SQL type that maps to a Java type, then getArray
returns an array of elements of this Java type. The return type of the getArray
method is java.lang.Object
. Therefore, the result must be cast before it can be used.
BigDecimal[] values = (BigDecimal[]) intArray.getArray();
Here intArray
is an oracle.sql.ARRAY
, corresponding to a VARRAY of type NUMBER
. The values
array contains an array of elements of type java.math.BigDecimal
, because the SQL NUMBER
data type maps to Java BigDecimal
, by default, according to Oracle JDBC drivers.
Note: UsingBigDecimal is a resource-intensive operation in Java. Because Oracle JDBC maps numeric SQL data to BigDecimal by default, using getArray may impact performance, and is not recommended for numeric collections. |
By default, if you are working with an array whose elements are structured objects, and you use getArray
or getResultSet
, then the Oracle objects in the array will be mapped to their corresponding Java data types according to the default mapping. This is because these methods use the default type map of the connection to determine the mapping.
However, if you do not want default behavior, then you can use the getArray(
map
)
or getResultSet(
map
)
method to specify a type map that contains alternate mappings. If there are entries in the type map corresponding to the Oracle objects in the array, then each object in the array is mapped to the corresponding Java type specified in the type map. For example:
Object[] object = (Object[])objArray.getArray(map);
Where objArray
is an oracle.sql.ARRAY
object and map
is a java.util.Map
object.
If the type map does not contain an entry for a particular Oracle object, then the element is returned as an oracle.sql.STRUCT
object.
The getResultSet(
map
)
method behaves similarly to the getArray(
map
)
method.
If you do not want to retrieve the entire contents of an array, then you can use signatures of getArray
, getResultSet
, and getOracleArray
that let you retrieve a subset. To retrieve a subset of the array, pass in an index and a count to indicate where in the array you want to start and how many elements you want to retrieve. As previously described, you can specify a type map or use the default type map for your connection to convert to Java types. For example:
Object object = arr.getArray(index, count, map); Object object = arr.getArray(index, count);
Similar examples using getResultSet
are:
ResultSet rset = arr.getResultSet(index, count, map); ResultSet rset = arr.getResultSet(index, count);
A similar example using getOracleArray
is:
Datum[] arr = arr.getOracleArray(index, count);
Where arr
is an oracle.sql.ARRAY
object, index
is type long
, count
is type int
, and map
is a java.util.Map
object.
Note: There is no performance advantage Hê·in retrieving a subset of an array, as opposed to the entire array. |
Use getOracleArray
to return an oracle.sql.Datum[]
array. The elements of the returned array will be of the oracle.sql.*
type that correspond to the SQL data type of the elements of the original array. For example:
Datum arraydata[] = arr.getOracleArray();
arr
is an oracle.sql.ARRAY
object.
The following example assumes that a connection object conn
and a statement object stmt
have already been created. In the example, an array with the SQL type name NUM_ARRAY
is created to store a VARRAY of NUMBER
data. The NUM_ARRAY
is in turn stored in a table VARRAY_TABLE
.
A query selects the contents of the VARRAY_TABLE
. The result set is cast to OracleResultSet
; getARRAY
is applied to it to retrieve the array data into my_array
, which is an oracle.sql.ARRAY
object.
Because my_array
is of type oracle.sql.ARRAY
, you can apply the methods getSQLTypeName
and getBaseType
to it to return the name of the SQL type of each element in the array and its integer code.
The program then prints the contents of the array. Because the contents of NUM_ARRAY
are of the SQL data type NUMBER
, the elements of my_array
are of the type, BigDecimal
. Before you can use the elements, they must first be cast to BigDecimal
. In the for
loop, the individual values of the array are cast to BigDecimal
and printed to standard output.
stmt.execute ("CREATE TYPE num_varray AS VARRAY(10) OF NUMBER(12, 2)"); stmt.execute ("CREATE TABLE varray_table (col1 num_varray)"); stmt.execute ("INSERT INTO varray_table VALUES (num_varray(100, 200))"); ResultSet rs = stmt.executeQuery("SELECT * FROM varray_table"); ARRAY my_array = ((OracleResultSet)rs).getARRAY(1); // return the SQL type names, integer codes, // and lengths of the columns System.out.println ("Array is of type " + array.getSQLTypeName()); System.out.println ("Array element is of type code " + array.getBaseType()); System.out.println ("Array is of length " + array.length()); // get Array elements BigDecimal[] values = (BigDecimal[]) my_array.getArray(); for (int i=0; i<values.length; i++) { BigDecimal out_value = (BigDecimal) values[i]; System.out.println(">> index " + i + " = " + out_value.intValue()); }
Note that if you use getResultSet
to obtain the array, then you must would first get the result set object, and then use the next
method to iterate through it. Notice the use of the parameter indexes in the getInt
method to retrieve the element index and the element value.
ResultSet rset = my_array.getResultSet(); while (rset.next()) { // The first column contains the element index and the // second column contains the element value System.out.println(">> index " + rset.getInt(1)+" = " + rset.getInt(2)); }
The oracle.sql.ARRAY
class provides three methods, which are overloaded, to access collection elements. The JDBC drivers extend these methods to support multilevel collections. These methods are:
getArray
method
getOracleArray
method
getResultSet
method
The getArray
method returns a Java array that holds the collection elements. The array element type is determined by the collection element type and the JDBC default conversion matrix.
For example, the getArray
method returns a java.math.BigDecimal
array for collection of SQL NUMBER
. The getOracleArray
method returns a Datum
array that holds the collection elements in Datum
format. For multilevel collections, the getArray
and getOracleArray
methods both return a Java array of oracle.sql.ARRAY
elements.
The getResultSet
method returns a ResultSet
object that wraps the multilevel collection elements. For multilevel collections, the JDBC applications use the getObject
, getARRAY
, or getArray
method of the ResultSet
class to access the collection elements as instances of oracle.sql.ARRAY
.
The following code shows how to use the getOracleArray
, getArray
, and getResultSet
methods:
Connection conn = ...; // make a JDBC connection Statement stmt = conn.createStatement (); ResultSet rset = stmt.executeQuery ("select col2 from tab2 where idx=1"); while (rset.next()) { ARRAY varray3 = (ARRAY) rset.getObject (1); Object varrayElems = varray3.getArray (1); // access array elements of "varray3" Datum[] varray3Elems = (Datum[]) varrayElems; for (int i=0; i<varray3Elems.length; i++) { ARRAY varray2 = (ARRAY) varray3Elems[i]; Datum[] varray2Elems = varray2.getOracleArray(); // access array elements of "varray2" for (int j=0; j<varray2Elems.length; j++) { ARRAY varray1 = (ARRAY) varray2Elems[j]; ResultSet varray1Elems = varray1.getResultSet(); // access array elements of "varray1" while (varray1Elems.next()) System.out.println ("idx="+varray1Elems.getInt(1)+" value="+varray1Elems.getInt(2)); } } } rset.close (); stmt.close (); conn.close ();
This section discusses how to pass arrays to prepared statement objects or callable statement objects.
Passing an Array to a Prepared Statement
Pass an array to a prepared statement as follows.
Note: you can use arrays as eitherIN or OUT bind variables. |
Define the array that you want to pass to the prepared statement as an oracle.sql.ARRAY
object.
ARRAY array = oracle.jdbc.OracleConnection.createARRAY(sql_type_name, elements);
sql_type_name
is a Java string specifying the user-defined SQL type name of the array and elements
is a java.lang.Object
containing a Java array of the elements.
Create a java.sql.PreparedStatement
object containing the SQL statement to be run.
Cast your prepared statement to OraclePreparedStatement
, and use setARRAY
to pass the array to the prepared statement.
(OraclePreparedStatement)stmt.setARRAY(parameterIndex, array);
parameterIndex
is the parameter index and array
is the oracle.sql.ARRAY
object you constructed previously.
Run the prepared statement.
Passing an Array to a Callable Statement
To retrieve a collection as an OUT
parameter in PL/SQL blocks, perform the following to register the bind type for your OUT
parameter.
Cast your callable statement to OracleCallableStatement
, as follows:
OracleCallableStatement ocs = (OracleCallableStatement)conn.prepareCall("{? = call func()}");
Register the OUT
parameter with the following form of the registerOutParameter
method:
ocs.registerOutParameter (int param_index, int sql_type, string sql_type_name);
param_index
is the parameter index, sql_type
is the SQL type code, and sql_type_name
is the name of the array type. In this case, the sql_type
is OracleTypes.ARRAY
.
Run the call, as follows:
ocs.execute();
Get the value, as follows:
oracle.sql.ARRAY array = ocs.getARRAY(1);
If your array contains Oracle objects, then you can use a type map to associate the objects in the array with the corresponding Java class. If you do not specify a type map, or if the type map does not contain an entry for a particular Oracle object, then each element is returned as an oracle.sql.STRUCT
object.
If you want the type map to determine the mapping between the Oracle objects in the array and their associated Java classes, then you must add an appropriate entry to the map.
The following example illustrates how you can use a type map to map the elements of an array to a custom Java object class. In this case, the array is a nested table. The example begins by defining an EMPLOYEE
object that has a name attribute and employee number attribute. EMPLOYEE_LIST
is a nested table type of EMPLOYEE
objects. Then an EMPLOYEE_TABLE
is created to store the names of departments within a corporation and the employees associated with each department. In the EMPLOYEE_TABLE
, the employees are stored in the form of EMPLOYEE_LIST
tables.
stmt.execute("CREATE TYPE EMPLOYEE AS OBJECT (EmpName VARCHAR2(50),EmpNo INTEGER))"); stmt.execute("CREATE TYPE EMPLOYEE_LIST AS TABLE OF EMPLOYEE"); stmt.execute("CREATE TABLE EMPLOYEE_TABLE (DeptName VARCHAR2(20), Employees EMPLOYEE_LIST) NESTED TABLE Employees STORE AS ntable1"); stmt.execute("INSERT INTO EMPLOYEE_TABLE VALUES ("SALES", EMPLOYEE_LIST (EMPLOYEE('Susan Smith', 123), EMPLOYEE('Scott Tiger', 124)))");
If you want to retrieve all the employees belonging to the SALES
department into an array of instances of the custom object class EmployeeObj
, then you must add an entry to the type map to specify mapping between the EMPLOYEE
SQL type and the EmployeeObj
custom object class.
To do this, first create your statement and result set objects, then select the EMPLOYEE_LIST
associated with the SALES
department into the result set. Cast the result set to OracleResultSet
so you can use the getARRAY
method to retrieve the EMPLOYEE_LIST
into an ARRAY
object (employeeArray
in the following example).
The EmployeeObj
custom object class in this example implements the SQLData
interface.
Statement s = conn.createStatement(); OracleResultSet rs = (OracleResultSet)s.executeQuery ("SELECT Employees FROM employee_table WHERE DeptName = 'SALES'"); // get the array object ARRAY employeeArray = ((OracleResultSet)rs).getARRAY(1);
Now that you have the EMPLOYEE_LIST
object, get the existing type map and add an entry that maps the EMPLOYEE
SQL type to the EmployeeObj
Java type.
// add type map entry to map SQL type // "EMPLOYEE" to Java type "EmployeeObj" Map map = conn.getTypeMap(); map.put("EMPLOYEE", Class.forName("EmployeeObj"));
Next, retrieve the SQL EMPLOYEE
objects from the EMPLOYEE_LIST
. To do this, call the getArray
method of the employeeArray
array object. This method returns an array of objects. The getArray
method returns the EMPLOYEE
objects into the employees
object array.
// Retrieve array elements Object[] employees = (Object[]) employeeArray.getArray();
Finally, create a loop to assign each of the EMPLOYEE
SQL objects to the EmployeeObj
Java object emp
.
// Each array element is mapped to EmployeeObj object. for (int i=0; i<employees.length; i++) { EmployeeObj emp = (EmployeeObj) employees[i]; ... }
This chapter primarily describes the functionality of the oracle.sql.ARRAY
class, but it is also possible to access Oracle collections through custom Java classes or, more specifically, custom collection classes.
You can create custom collection classes yourself, but the most convenient way is to use the Oracle JPublisher utility. Custom collection classes generated by JPublisher offer all the functionality described earlier in this chapter, as well as the following advantages:
They are strongly typed. This can help you find coding errors during compilation that might not otherwise be discovered until run time.
They can be changeable, or mutable. Custom collection classes produced by JPublisher, unlike the ARRAY
class, allow you to get and set individual elements using the getElement
and setElement
methods.
A custom collection class must satisfy three requirements:
It must implement the oracle.sql.ORAData
interface. Note that the standard JDBC SQLData
interface, which is an alternative for custom object classes, is not intended for custom collection classes.
It, or a companion class, must implement the oracle.sql.ORADataFactory
interface, for creating instances of the custom collection class.
It must have a means of storing the collection data. Typically it will directly or indirectly include an oracle.sql.ARRAY
attribute for this purpose.
A JPublisher-generated custom collection class implements ORAData
and ORADataFactory
and indirectly includes an oracle.sql.ARRAY
attribute. The custom collection class will have an oracle.jpub.runtime.MutableArray
attribute. The MutableArray
class has an oracle.sql.ARRAY
attribute.
Note: When you use JPublisher to create a custom collection class, you must use theORAData implementation. This will be true if the JPublisher -usertypes mapping option is set to oracle , which is the default.
You cannot use a |
As an example of custom collection classes being strongly typed, if you define an Oracle collection MYVARRAY
, then JPublisher can generate a MyVarray
custom collection class. Using MyVarray
instances, instead of generic oracle.sql.ARRAY
instances, makes it easier to catch errors during compilation instead of at run time. For example, if you accidentally assign some other kind of array into a MyVarray
variable.
If you do not use custom collection classes, then you would use standard java.sql.Array
instances, or oracle.sql.ARRAY
instances, to map to your collections.
This chapter describes how the Oracle Java Database Connectivity (JDBC) drivers handle Java streams for several data types. Data streams enable you to read LONG
column data of up to 2 gigabytes (GB). Methods associated with streams let you read the data incrementally.
This chapter covers the following topics:
Oracle JDBC drivers support the manipulation of data streams in either direction between server and client. The drivers support all stream conversions: binary, ASCII, and Unicode. Following is a brief description of each type of stream:
Binary
Used for RAW
bytes of data, and corresponds to the getBinaryStream
method
ASCII
Used for ASCII bytes in ISO-Latin-1 encoding, and corresponds to the getAsciiStream
method
Unicode
Used for Unicode bytes with the UTF-16
encoding, and corresponds to the getUnicodeStream
method
The getBinaryStream
, getAsciiStream
, and getUnicodeStream
methods return the bytes of data in an InputStream
object.
When a query selects one or more LONG
or LONG
RAW
columns, the JDBC driver transfers these columns to the client in streaming mode. In streaming mode, the JDBC driver does not read the column data from the network for LONG
or LONG RAW
columns, until required. The column data remains in the network communications channel until your code calls a getXXX
method to read the column data. Even after the call, the column data is read only as needed to populate return value from the getXXX call. Because the column data remains in the communications channel, the streaming mode interferes with all other use of the connection. Any use of the connection, other than reading the column data, will discard the column data from the channel. While the streaming mode makes efficient use of memory and minimizes network round trips, it interferes with many other database operations.
Note: Oracle recommends avoidingLONG and LONG RAW columns. Use LOB instead. |
To access the data in a LONG
column, you can get the column as a Java InputStream
object and use the read
method of the InputStream
object. As an alternative, you can get the data as a String
or byte
array. In this case, the driver will do the streaming for you.
You can get LONG
and LONG
RAW
data with any of the three stream types. The driver performs conversions for you, depending on the character set of the database and the driver.
Note: Do not create tables withLONG columns. Use large object (LOB) columns, CLOB , NCLOB , and BLOB , instead. LONG columns are supported only for backward compatibility. Oracle recommends that you convert existing LONG columns to LOB columns. LOB columns are subject to far fewer restrictions than LONG columns. |
This section covers the following topics:
A call to getBinaryStream
returns RAW
data. A call to getAsciiStream
converts the RAW
data to hexadecimal and returns the ASCII representation. A call to getUnicodeStream
converts the RAW
data to hexadecimal and returns the Unicode characters.
When you get LONG
data with getAsciiStream
, the drivers assume that the underlying data in the database uses an US7ASCII
or WE8ISO8859P1
character set. If the assumption is true, then the drivers return bytes corresponding to ASCII characters. If the database is not using an US7ASCII
or WE8ISO8859P1
character set, a call to getAsciiStream
returns meaningless information.
When you get LONG
data with getUnicodeStream
, you get a stream of Unicode characters in the UTF-16
encoding. This applies to all underlying database character sets that Oracle supports.
When you get LONG
data with getBinaryStream
, there are two possible cases:
If the driver is JDBC OCI and the client character set is not US7ASCII
or WE8ISO8859P1
, then a call to getBinaryStream
returns UTF-8
. If the client character set is US7ASCII
or WE8ISO8859P1
, then the call returns a US7ASCII
stream of bytes.
If the driver is JDBC Thin and the database character set is not US7ASCII
or WE8ISO8859P1
, then a call to getBinaryStream
returns UTF-8
. If the server-side character set is US7ASCII
or WE8ISO8859P1
, then the call returns a US7ASCII
stream of bytes.
Note: ReceivingLONG or LONG RAW columns as a stream requires you to pay special attention to the order in which you retrieve columns from the database. |
Table 12-1 summarizes LONG
and LONG
RAW
data conversions for each stream type.
Table 12-1 LONG and LONG RAW Data Conversions
Data type | BinaryStream | AsciiStream | UnicodeStream |
---|---|---|---|
LONG |
Bytes representing characters in Unicode |
Bytes representing characters in ISO-Latin-1 ( |
Bytes representing characters in Unicode |
LONG RAW |
unchanged data |
ASCII representation of hexadecimal bytes |
Unicode representation of hexadecimal bytes |
One of the features of a get
XXX
Stream
method is that it enables you to fetch data incrementally. In contrast, getBytes
fetches all the data in one call. This section contains two examples of getting a stream of binary data. The first version uses the getBinaryStream
method to obtain LONG
RAW
data, and the second version uses the getBytes
method.
Getting a LONG RAW Data Column with getBinaryStream
This example writes the contents of a LONG
RAW
column to a file on the local file system. In this case, the driver fetches the data incrementally.
The following code creates the table that stores a column of LONG
RAW
data associated with the name LESLIE:
-- SQL code: create table streamexample (NAME varchar2 (256), GIFDATA long raw); insert into streamexample values ('LESLIE', '00010203040506070809');
The following Java code snippet writes the data from the LONG
RAW
column into a file called leslie.gif:
ResultSet rset = stmt.executeQuery ("select GIFDATA from streamexample where NAME='LESLIE'"); // get first row if (rset.next()) { // Get the GIF data as a stream from Oracle to the client InputStream gif_data = rset.getBinaryStream (1); try { FileOutputStream file = null; file = new FileOutputStream ("leslie.gif"); int chunk; while ((chunk = gif_data.read()) != -1) file.write(chunk); } catch (Exception e) { String err = e.toString(); System.out.println(err); } finally { if file != null() file.close(); } }
In this example, the InputStream
object returned by the call to getBinaryStream
reads the data directly from the database connection.
Getting a LONG RAW Data Column with getBytes
This example gets the content of the GIFDATA
column with getBytes
instead of getBinaryStream
. In this case, the driver fetches all the data in one call and stores it in a byte array. The code snippet is as follows:
ResultSet rset2 = stmt.executeQuery ("select GIFDATA from streamexample where NAME='LESLIE'"); // get first row if (rset2.next()) { // Get the GIF data as a stream from Oracle to the client byte[] bytes = rset2.getBytes(1); try { FileOutputStream file = null; file = new FileOutputStream ("leslie2.gif"); file.write(bytes); } catch (Exception e) { String err = e.toString(); System.out.println(err); } finally { if file != null() file.close(); } }
Because a LONG RAW
column can contain up to 2 gigabytes of data, the getBytes
example can use much more memory than the getBinaryStream
example. Use streams if you do not know the maximum size of the data in your LONG
or LONG RAW
columns.
The JDBC driver automatically streams any LONG
and LONG RAW
columns. However, there may be situations where you want to avoid data streaming. For example, if you have a very small LONG
column, then you may want to avoid returning the data incrementally and, instead, return the data in one call.
To avoid streaming, use the defineColumnType
method to redefine the type of the LONG
column. For example, if you redefine the LONG
or LONG RAW
column as VARCHAR
or VARBINARY
type, then the driver will not automatically stream the data.
If you redefine column types with defineColumnType
, then you must declare the types of the columns in the query. If you do not declare the types of the columns, then executeQuery
will fail. In addition, you must cast the Statement
object to oracle.jdbc.OracleStatement
.
As an added benefit, using defineColumnType
saves the OCI and KPRB drivers a database round-trip when running the query. Without defineColumnType
, these JDBC drivers must request the data types of the column types. The JDBC Thin driver derives no benefit from defineColumnType
, because it always uses the minimum number of round-trips.
Using the example from the previous section, the Statement
object stmt
is cast to OracleStatement
and the column containing LONG RAW
data is redefined to be of the type VARBINARAY
. The data is not streamed. Instead, it is returned in a byte array. The code snippet is as follows:
//cast the statement stmt to an OracleStatement oracle.jdbc.OracleStatement ostmt = (oracle.jdbc.OracleStatement)stmt; //redefine the LONG column at index position 1 to VARBINARY ostmt.defineColumnType(1, Types.VARBINARY); // Do a query to get the images named 'LESLIE' ResultSet rset = ostmt.executeQuery ("select GIFDATA from streamexample where NAME='LESLIE'"); // The data is not streamed here rset.next(); byte [] bytes = rset.getBytes(1);
If you use the defineColumnType
Oracle extension to redefine a CHAR
, VARCHAR
, or RAW
column as a LONGVARCHAR
or LONGVARBINARY
, then you can get the column as a stream. The program will behave as if the column were actually of type LONG
or LONG RAW
. Note that there is not much point to this, because these columns are usually short.
If you try to get a CHAR
, VARCHAR
, or RAW
column as a data stream without redefining the column type, then the JDBC driver will return a Java InputStream
, but no real streaming occurs. In the case of these data types, the JDBC driver fully fetches the data into an in-memory buffer during a call to the executeQuery
method or the next
method. The get
XXX
Stream
entry points return a stream that reads data from this buffer.
The term large object (LOB) refers to a data item that is too large to be stored directly in a database table. Instead, a locator is stored in the database table, which points to the location of the actual data. External files are managed similarly. The JDBC drivers can support the following types through the use of streams:
Binary large object (BLOB)
For unstructured binary data
Character large object (CLOB)
For character data
National Character large object (NCLOB)
For national character data
Binary file (BFILE)
For external files
LOBs and BFILEs behave differently from the other types of streaming data described in this chapter. Instead of storing the actual data in the table, a locator is stored. The actual data can be manipulated using this locator, including reading and writing the data as a stream. Even when streaming, only the necessary bits of data move across the network. By contrast, when streaming a LONG
or LONG RAW
, all the data always moves across the network.
Streaming BLOBs, CLOBs, and NCLOBs
When a query fetches one or more BLOB
, CLOB
, or NCLOB
columns, the JDBC driver transfers the data to the client. This data can be accessed as a stream. To manipulate BLOB
, CLOB
, or NCLOB
data from JDBC, use methods in the Oracle extension classes oracle.sql.BLOB
, oracle.sql.CLOB
and oracle.sql.NCLOB
. These classes provide specific functionality, such as reading from the BLOB
, CLOB
, or NCLOB
into an input stream, writing from an output stream into a BLOB
, CLOB
, or NCLOB
, determining the length of a BLOB
, CLOB
, or NCLOB
, and closing a BLOB
, CLOB
, or NCLOB
.
Streaming BFILEs
An external file, or BFILE, is used to store a locator to a file outside the database. The file can be stored somewhere on the file system of the data server. The locator points to the actual location of the file.
When a query fetches one or more BFILE
columns, the JDBC driver transfers the file to the client as required. The data can be accessed as a stream To manipulate BFILE data from JDBC, use methods in the Oracle extension class oracle.sql.BFILE
. This class provides specific functionality, such as reading from the BFILE into an input stream, writing from an output stream into a BFILE, determining the length of a BFILE, and closing a BFILE.
If a query fetches multiple columns and one of the columns contains a data stream, then the contents of the columns following the stream column are not available until the stream has been read, and the stream column is no longer available once any following column is read. Any attempt to read a column beyond a streaming column closes the streaming column.
Streaming Example with Multiple Columns
Consider the following code:
ResultSet rset = stmt.executeQuery ("select DATECOL, LONGCOL, NUMBERCOL from TABLE"); while rset.next() { //get the date data java.sql.Date date = rset.getDate(1); // get the streaming data InputStream is = rset.getAsciiStream(2); // Open a file to store the gif data FileOutputStream file = new FileOutputStream ("ascii.dat"); // Loop, reading from the ascii stream and // write to the file int chunk; while ((chunk = is.read ()) != -1) file.write(chunk); // Close the file file.close(); //get the number column data int n = rset.getInt(3); }
The incoming data for each row has the following shape:
<a date><the characters of the long column><a number>
As you process each row of the result set, you must complete any processing of the stream column before reading the number column.
Bypassing Streaming Data Columns
There may be situations where you want to avoid reading a column that contains streaming data. If you do not want to read such data, then call the close
method of the stream object. This method discards the stream data and enables the driver to continue reading data from all the columns that contain non-streaming data and follow the column containing streaming data. Even though you are intentionally discarding the stream, it is a good programming practice to retrieve the columns in the same order as in the SELECT
statement.
In the following example, the stream data in the LONG
column is discarded and the data from only the DATE
and NUMBER
column is recovered:
ResultSet rset = stmt.executeQuery ("select DATECOL, LONGCOL, NUMBERCOL from TABLE"); while rset.next() { //get the date java.sql.Date date = rset.getDate(1); // access the stream data and discard it with close() InputStream is = rset.getAsciiStream(2); is.close(); // get the number column data int n = rset.getInt(3); }
You can discard the data from a stream at any time by calling the close
method. It is a good programming practice to close the stream when you no longer need it.
Note: Closing a stream has little performance effect on a LONG or LONG RAW column. All of the data still move across the network and the driver must read the bits from the network. |
This section discusses several cautionary issues regarding the use of streams:
This section describes some of the precautions you must take to ensure that you do not accidentally discard or lose your stream data. The drivers automatically discard stream data if you perform any JDBC operation that communicates with the database, other than reading the current stream. Two common precautions are:
Use the stream data after you access it.
To recover the data from a column containing a data stream, it is not enough to fetch the column. You must immediately process the contents of the column. Otherwise, the contents will be discarded when you fetch the next column.
Call the stream column in the same order as in the SELECT
statement.
If your query fetches multiple columns, the database sends each row as a set of bytes representing the columns in the SELECT
order. If one of the columns contains stream data, then the database sends the entire data stream before proceeding to the next column.
If you do not use the order as in the SELECT
statement to access data, then you can lose the stream data. That is, if you bypass the stream data column and access data in a column that follows it, then the stream data will be lost. For example, if you try to access the data for the NUMBER
column before reading the data from the stream data column, then the JDBC driver first reads then discards the streaming data automatically. This can be very inefficient if the LONG
column contains a large amount of data.
If you try to access the LONG
column later in the program, then the data will not be available and the driver will return a "Stream Closed
" error.
The later point is illustrated in the following example:
ResultSet rset = stmt.executeQuery ("select DATECOL, LONGCOL, NUMBERCOL from TABLE"); while rset.next() { int n = rset.getInt(3); // This discards the streaming data InputStream is = rset.getAsciiStream(2); // Raises an error: stream closed. }
If you get the stream but do not use it before you get the NUMBER
column, then the stream still closes automatically:
ResultSet rset = stmt.executeQuery ("select DATECOL, LONGCOL, NUMBERCOL from TABLE"); while rset.next() { InputStream is = rset.getAsciiStream(2); // Get the stream int n = rset.getInt(3); // Discards streaming data and closes the stream } int c = is.read(); // c is -1: no more characters to read-stream closed
Starting from Oracle Database 10g, the size limit of the data that is used with the setBytes
and setString
methods, have been increased significantly. Any Java byte
array can be passed to setBytes
, and any Java String
can be passed to setString
. The JDBC driver automatically switches to using setBinaryStream
or setCharacterStream
or to using setBytesForBlob
or setStringForClob
, depending on the size of the data, whether the statement is SQL or PL/SQL, and the driver used.
There are some limitation with earlier versions of Oracle Database and in the server-side internal driver.
This appendix briefly discusses the general structure of Java Database Connectivity (JDBC) error messages, then lists general JDBC error messages and TTC error messages that Oracle JDBC drivers can return. The appendix is organized as follows:
Each of the message lists is first sorted by ORA
number, and then alphabetically.
The general JDBC error message structure allows run-time information to be appended to the end of a message, following a colon, as follows:
<error_message>:<extra_info>
For example, a "closed statement" error might be displayed as follows:
Closed Statement:next
This indicates that the exception was thrown during a call to the next
method (of a result set object).
In some cases, the user can find the same information in a stack trace.
This section lists general JDBC error messages, first sorted by the ORA
number, and then in alphabetic order in the following subsections:
Note: TheORA-17033 and ORA-17034 error messages use the term SQL92. The JDBC escape syntax was previously known as SQL92 Syntax or SQL92 escape syntax. |
The following table lists the JDBC error messages sorted by the ORA
number:
Table D-1 JDBC Messages Sorted by ORA Number
ORA Number | Message |
---|---|
ORA-17001 |
Internal Error |
ORA-17002 |
Io exception |
ORA-17003 |
Invalid column index |
ORA-17004 |
Invalid column type |
ORA-17005 |
Unsupported column type |
ORA-17006 |
Invalid column name |
ORA-17007 |
Invalid dynamic column |
ORA-17008 |
Closed Connection |
ORA-17009 |
Closed Statement |
ORA-17010 |
Closed Resultset |
ORA-17011 |
Exhausted Resultset |
ORA-17012 |
Parameter Type Conflict |
ORA-17014 |
ResultSet.next was not called |
ORA-17015 |
Statement was cancelled |
ORA-17016 |
Statement timed out |
ORA-17017 |
Cursor already initialized |
ORA-17018 |
Invalid cursor |
ORA-17019 |
Can only describe a query |
ORA-17020 |
Invalid row prefetch |
ORA-17021 |
Missing defines |
ORA-17022 |
Missing defines at index |
ORA-17023 |
Unsupported feature |
ORA-17024 |
No data read |
ORA-17025 |
Error in defines.isNull () |
ORA-17026 |
Numeric Overflow |
ORA-17027 |
Stream has already been closed |
ORA-17028 |
Can not do new defines until the current ResultSet is closed |
ORA-17029 |
setReadOnly: Read-only connections not supported |
ORA-17030 |
READ_COMMITTED and SERIALIZABLE are the only valid transaction levels |
ORA-17031 |
setAutoClose: Only support auto close mode on |
ORA-17032 |
cannot set row prefetch to zero |
ORA-17033 |
Malformed SQL92 string at position |
ORA-17034 |
Non supported SQL92 token at position |
ORA-17035 |
Character Set Not Supported !! |
ORA-17036 |
exception in OracleNumber |
ORA-17037 |
Fail to convert between UTF8 and UCS2 |
ORA-17038 |
Byte array not long enough |
ORA-17039 |
Char array not long enough |
ORA-17040 |
Sub Protocol must be specified in connection URL |
ORA-17041 |
Missing IN or OUT parameter at index: |
ORA-17042 |
Invalid Batch Value |
ORA-17043 |
Invalid stream maximum size |
ORA-17044 |
Internal error: Data array not allocated |
ORA-17045 |
Internal error: Attempt to access bind values beyond the batch value |
ORA-17046 |
Internal error: Invalid index for data access |
ORA-17047 |
Error in Type Descriptor parse |
ORA-17048 |
Undefined type |
ORA-17049 |
Inconsistent java and sql object types |
ORA-17050 |
no such element in vector |
ORA-17051 |
This API cannot be be used for non-UDT types |
ORA-17052 |
This ref is not valid |
ORA-17053 |
The size is not valid |
ORA-17054 |
The LOB locator is not valid |
ORA-17055 |
Invalid character encountered in |
ORA-17056 |
Non supported character set (add orai18n.jar in your classpath) |
ORA-17057 |
Closed LOB |
ORA-17058 |
Internal error: Invalid NLS Conversion ratio |
ORA-17059 |
Fail to convert to internal representation |
ORA-17060 |
Fail to construct descriptor |
ORA-17061 |
Missing descriptor |
ORA-17062 |
Ref cursor is invalid |
ORA-17063 |
Not in a transaction |
ORA-17064 |
Invalid Sytnax or Database name is null |
ORA-17065 |
Conversion class is null |
ORA-17066 |
Access layer specific implementation needed |
ORA-17067 |
Invalid Oracle URL specified |
ORA-17068 |
Invalid argument(s) in call |
ORA-17069 |
Use explicit XA call |
ORA-17070 |
Data size bigger than max size for this type |
ORA-17071 |
Exceeded maximum VARRAY limit |
ORA-17072 |
Inserted value too large for column |
ORA-17074 |
invalid name pattern |
ORA-17075 |
Invalid operation for forward only resultset |
ORA-17076 |
Invalid operation for read only resultset |
ORA-17077 |
Fail to set REF value |
ORA-17078 |
Cannot do the operation as connections are already opened |
ORA-17079 |
User credentials doesn't match the existing ones |
ORA-17080 |
invalid batch command |
ORA-17081 |
error occurred during batching |
ORA-17082 |
No current row |
ORA-17083 |
Not on the insert row |
ORA-17084 |
Called on the insert row |
ORA-17085 |
Value conflicts occurs |
ORA-17086 |
Undefined column value on the insert row |
ORA-17087 |
Ignored performance hint: setFetchDirection() |
ORA-17088 |
Unsupported syntax for requested resultset type and concurrency level |
ORA-17089 |
internal error |
ORA-17090 |
operation not allowed |
ORA-17091 |
Unable to create resultset at the requested type and/or concurrency level |
ORA-17092 |
JDBC statements cannot be created or executed at end of call processing |
ORA-17093 |
OCI operation returned OCI_SUCCESS_WITH_INFO |
ORA-17094 |
Object type version mismatched |
ORA-17095 |
Statement cache size has not been set |
ORA-17096 |
Statement Caching cannot be enabled for this logical connection. |
ORA-17097 |
Invalid PL/SQL Index Table element type |
ORA-17098 |
Invalid empty lob operation |
ORA-17099 |
Invalid PL/SQL Index Table array length |
ORA-17100 |
Invalid database Java Object |
ORA-17101 |
Invalid properties in OCI Connection Pool Object |
ORA-17102 |
Bfile is read only |
ORA-17103 |
invalid connection type to return via getConnection. Use getJavaSqlConnection instead |
ORA-17104 |
SQL statement to execute cannot be empty or null |
ORA-17105 |
connection session time zone was not set |
ORA-17106 |
invalid JDBC-OCI driver connection pool configuration specified |
ORA-17107 |
invalid proxy type specified |
ORA-17108 |
No max length specified in defineColumnType |
ORA-17109 |
standard Java character encoding not found |
ORA-17110 |
execution completed with warning |
ORA-17111 |
Invalid connection cache TTL timeout specified |
ORA-17112 |
Invalid thread interval specified |
ORA-17113 |
Thread interval value is more than the cache timeout value |
ORA-17114 |
could not use local transaction commit in a global transaction |
ORA-17115 |
could not use local transaction rollback in a global transaction |
ORA-17116 |