This chapter describes the Java Database Connectivity (JDBC) support for user-defined object types. It discusses functionality of the generic, weakly typedoracle.sql.STRUCT class, as well as how to map to custom Java classes that implement either the JDBC standardSQLData interface or the OracleORAData interface.
The following topics are covered:
Oracle object types provide support for composite data structures in the database. For example, you can define aPerson type that has the attributesname ofCHAR type,phoneNumber ofCHAR type, andemployeeNumber ofNUMBER type.
Oracle provides tight integration between itsOracle object features and its JDBC functionality. You can use a standard, generic JDBC type to map to Oracle objects, or you can customize the mapping by creating custom Java type definition classes.
Note:
In this book, Java classes that you create to map to Oracle objects will be referred to ascustom Java classes or, more specifically,custom object classes. This is as opposed tocustom references classes, which are Java classes that map to object references, andcustom collection classes, which are Java classes that map to Oracle collections.Custom object classes can implement either a standard JDBC interface or an Oracle extension interface to read and write data. JDBC materializes Oracle objects as instances of particular Java classes. Two main steps in using JDBC to access Oracle objects are:
Creating the Java classes for the Oracle objects
Populating these classes. You have the following options:
Let JDBC materialize the object as aSTRUCT object.
Explicitly specify the mappings between Oracle objects and Java classes.
This includes customizing your Java classes for object data. The driver then must be able to populate instances of the custom object classes that you specify. This imposes a set of constraints on the Java classes. To satisfy these constraints, you can define your classes to implement either the JDBC standardjava.sql.SQLData interface or the Oracle extensionoracle.sql.ORAData interface.
You can use the Oracle JPublisher utility to generate custom Java classes.
Note:
When you use theSQLData interface, you must use a Java type map to specify your SQL-Java mapping, unless weakly typedjava.sql.Struct objects will suffice.If you choose not to supply a custom Java class for your SQL-Java mapping for anOracle object, then Oracle JDBC will materialize the object as an instance of theoracle.sql.STRUCT class.
You would typically want to useSTRUCT objects, instead of custom Java objects, in situations where you do not know the actual SQL type. For example, your Java application might be a tool to manipulate arbitrary object data within the database, as opposed to being an end-user application. You can select data from the database intoSTRUCT objects and createSTRUCT objects for inserting data into the database.STRUCT objects completely preserve data, because they maintain the data in SQL format. UsingSTRUCT objects is more efficient and more precise in situations where you do not need the information in an application specific form.
This section covers the following topics:
This section discusses standard versus Oracle-specific features of theoracle.sql.STRUCT class, introducesSTRUCT descriptors, and lists methods of theSTRUCT class to give an overview of its functionality.
Standard java.sql.Struct Methods
If your code must comply with standard JDBC 2.0, then use ajava.sql.Struct instance and use the following standard methods:
getAttributes(map)
This method retrieves the values of the attributes, using entries in the specified type map to determine the Java classes to use in materializing any attribute that is a structured object type. The Java types for other attribute values would be the same as for agetObject call on data of the underlying SQL type.
getAttributes
This method is the same as the precedinggetAttributes(map) method, except it uses the default type map for the connection.
This method returns a JavaString that represents the fully qualified name of the Oracle object type that thisStruct represents.
Oracle oracle.sql.STRUCT Class Methods
If you want to take advantage of the extended functionality offered by Oracle-defined methods, then use anoracle.sql.STRUCT instance.
Theoracle.sql.STRUCT class implements thejava.sql.Struct interface and provides extended functionality beyond the JDBC 2.0 standard.
TheSTRUCT class includes the following methods in addition to standardStruct functionality:
Retrieves the values of the values array asoracle.sql.* objects
Returns theStructDescriptor object for the SQL type that corresponds to thisSTRUCT object
Returns the current connection instance
Consults the default type map of the connection to determine what class to map to and, then, usestoClass
Consults the specified type map to determine what class to map to, and then usestoClass
This section discusses how to retrieve and manipulate Oracle objects and their attributes, using either Oracle-specific features or JDBC 2.0 standard features.
Note:
The JDBC driver seamlessly handles embedded objects, that is,STRUCT objects that are attributes ofSTRUCT objects, in the same way that it typically handles objects. When the JDBC driver retrieves an attribute that is an object, it follows the same rules of conversion by using the type map, if it is available, or by using default mapping.Retrieving an Oracle Object as an oracle.sql.STRUCT Object
You can retrieve an Oracle object directly into anoracle.sql.STRUCT instance. In the following example,getObject is used to get atype_struct object from thecol1 column of the tablestruct_table. BecausegetObject returns anObject type, the return is cast tooracle.sql.STRUCT. This example assumes that theStatement objectstmt has already been created.
String cmd;cmd = "CREATE TYPE type_struct AS object (field1 NUMBER,field2 DATE)";stmt.execute(cmd);cmd = "CREATE TABLE struct_table (col1 type_struct)";stmt.execute(cmd);cmd = "INSERT INTO struct_table VALUES (type_struct(10,'01-apr-01'))";stmt.execute(cmd);cmd = "INSERT INTO struct_table VALUES (type_struct(20,'02-may-02'))";stmt.execute(cmd);ResultSet rs= stmt.executeQuery("SELECT * FROM struct_table");oracle.sql.STRUCT oracleSTRUCT=(oracle.sql.STRUCT)rs.getObject(1);Another way to return the object as aSTRUCT object is to cast the result set toOracleResultSet and use the Oracle extensiongetSTRUCT method:
oracle.sql.STRUCT oracleSTRUCT=((OracleResultSet)rs).getSTRUCT(1);
Retrieving an Oracle Object as a java.sql.Struct Object
Alternatively, in the preceding example, you can use standard JDBC functionality, such asgetObject, to retrieve an Oracle object from the database as an instance ofjava.sql.Struct. BecausegetObject returns ajava.lang.Object, you must cast the output of the method toStruct. For example:
ResultSet rs= stmt.executeQuery("SELECT * FROM struct_table");java.sql.Struct jdbcStruct = (java.sql.Struct)rs.getObject(1);Retrieving Attributes as oracle.sql Types
If you want to retrieve Oracle object attributes from aSTRUCT orStruct instance asoracle.sql types, then use thegetOracleAttributes method of theoracle.sql.STRUCT class, as follows:
oracle.sql.Datum[] attrs = oracleSTRUCT.getOracleAttributes();
or:
oracle.sql.Datum[] attrs = ((oracle.sql.STRUCT)jdbcStruct).getOracleAttributes();
Retrieving Attributes as Standard Java Types
If you want to retrieve Oracle object attributes as standard Java types from aSTRUCT orStruct instance, use the standardgetAttributes method:
Object[] attrs = jdbcStruct.getAttributes();
Note:
Oracle JDBC drivers cache array and structure descriptors. This provides enormous performance benefits. However, it means that if you change the underlying type definition of a structure type in the database, the cached descriptor for that structure type will become stale and your application will receive aSQLException exception.This section describes how to createSTRUCT objects.
Steps in Creating StructDescriptor and STRUCT Objects
To create aSTRUCT object, you must:
Create aStructDescriptor object for the given Oracle object type, if it does not already exist.
Use theStructDescriptor to construct theSTRUCT object.
AStructDescriptor is an instance of theoracle.sql.StructDescriptor class and describes a type of Oracle object. Only oneStructDescriptor is necessary for each Oracle object type. The driver cachesStructDescriptor objects to avoid re-creating them if the type has already been encountered.
Before you can construct aSTRUCT object, aStructDescriptor must first exist for the given Oracle object type. If aStructDescriptor object does not exist, then you can create one by calling the staticStructDescriptor.createDescriptor method. This method requires you to passin the SQL type name of the Oracle object type and a connection object, as follows:
StructDescriptor structdesc = StructDescriptor.createDescriptor (sql_type_name,connection);
Thesql_type_name parameter is a JavaString containing the name of the Oracle object type, such asEMPLOYEE, andconnection is the connection object.
Once you have yourStructDescriptor object for the Oracle object type, you can construct theSTRUCT object. To do this, provide theConnection object, theStructDescriptor object, and an array of Java objects containing the attributes you want theSTRUCT to contain.
The following constructors ofSTRUCT are available:
STRUCT(Connection conn, java.sql.StructDescriptor structDesc, Object[] attributes)STRUCT(Connection conn, java.sql.StructDescriptor structDesc, java.util.Map map)
ThestructDesc parameter is theStructDescriptor object created previously andconn is yourConnection object. The attributes can be passed as an array ofjava.lang.Object or as ajava.util.Map object.
The following code illustrates the use of the constructor that takes anObject array:
...Object[] attributes = {"attribute1", null};STRUCT struct = new STRUCT(connection, structDescriptor, attributes);...The following code illustrates the use of the constructor that takes aMap object:
...HashMap map = new HashMap(1);map.put("A1","attribute1");STRUCT struct = new STRUCT(connection, structDescriptor, map);...To bind anoracle.sql.STRUCT object to a prepared statement or callable statement, you can either use the standardsetObject method (specifying the type code), or cast the statement object to an Oracle statement type and use the Oracle extensionsetOracleObject method. For example:
PreparedStatement ps= conn.prepareStatement("text_of_prepared_statement");STRUCT mySTRUCT = new STRUCT (...);ps.setObject(1, mySTRUCT, Types.STRUCT);or:
PreparedStatement ps= conn.prepareStatement("text_of_prepared_statement");STRUCT mySTRUCT = new STRUCT (...);((OraclePreparedStatement)ps).setOracleObject(1, mySTRUCT);Oracle JDBC driver furnishes public methods to enable and disable buffering ofSTRUCT attributes.
See Also:
"ARRAY Automatic Element Buffering"The following methods are included with theoracle.sql.STRUCT class:
ThesetAutoBuffering(boolean) method enables or disables auto-buffering. ThegetAutoBuffering method returns the current auto-buffering mode. By default, auto-buffering is disabled.
It is advisable to enable auto-buffering in a JDBC application when theSTRUCT attributes will be accessed more than once by thegetAttributes andgetArray methods, presuming theARRAY data is able to fit into the Java Virtual Machine (JVM) memory without overflow.
Note:
Buffering the converted attributes may cause the JDBC application to consume a significant amount of memory.When you enable auto-buffering, theoracle.sql.STRUCT object keeps a local copy of all the converted attributes. This data is retained so that subsequent access of this information does not require going through the data format conversion process.
If you want to createcustom object classes for yourOracle objects, then you must define entries in thetype map that specify the custom object classes that the drivers will instantiate for the corresponding Oracle objects.
You must also provide a way to create and populate instances of the custom object class from the Oracle object and its attribute data. The driver must be able to read from a custom object class and write to it. In addition, the custom object class can providegetXXX andsetXXX methods corresponding to the attributes of the Oracle object, although this is not necessary. To create and populate the custom classes and provide these read/write capabilities, you can choose between the following interfaces:
The JDBC standardSQLData interface
TheORAData andORADataFactory interfaces provided by Oracle
The custom object class you create must implement one of these interfaces. TheORAData interface can also be used to implement the custom reference class corresponding to the custom object class. However, if you are using theSQLData interface, then you can use only weak reference types in Java, such asjava.sql.Ref ororacle.sql.REF. TheSQLData interface is for mapping SQL objects only.
As an example, assume you have an Oracle object type,EMPLOYEE, in the database that consists of two attributes:Name, which is of theCHAR type andEmpNum, which is of theNUMBER type. You use the type map to specify that theEMPLOYEE object should map to a custom object class that you callJEmployee. You can implement either theSQLData orORAData interface in theJEmployee class.
You can create custom object classes yourself, but the most convenient way to create them is to use the OracleJPublisher utility to create them for you. JPublisher supports the standardSQLData interface as well as the Oracle-specificORAData interface, and is able to generate classes that implement either one.
This section covers the following topics:
In deciding which of the two interface implementations to use, you need to consider the advantages ofORAData andSQLData.
TheSQLData interface is for mapping SQL objects only. TheORAData interface is more flexible, enabling you to map SQL objects as well as any other SQL type for which you want to customize processing. You can create aORAData object from any data type found in Oracle Database. This could be useful, for example, for serializingRAW data in Java.
The advantages ofORAData are:
It does not require an entry in the type map for the Oracle object.
It has awareness of Oracle extensions.
You can construct anORAData from anoracle.sql.STRUCT. This is more efficient because it avoids unnecessary conversions to native Java types.
You can obtain the correspondingDatum object from theORAData object, using thetoDatum method.
It provides better performance.ORAData works directly withDatum types, which is the internal format used by the driver to hold Oracle objects.
SQLData is a JDBC standard that makes your code portable.
If you use theSQLData interface in a custom object class, then you must create type map entries that specify the custom object class to use in mapping the Oracle object type to Java. You can either use the default type map of the connection object or a type map that you specify when you retrieve the data from the result set. ThegetObject method of theResultSet interface has a signature that lets you specify a type map. You can use either of the following:
rs.getObject(int columnIndex);rs.getObject(int columnIndex, Map map);
When using a SQLData implementation, if you do not include a type map entry, then the object will map to theoracle.sql.STRUCT class by default.ORAData implementations, by contrast, have their own mapping functionality so that a type map entry is not required. When using anORAData implementation, use the OraclegetORAData method instead of the standardgetObject method.
The type map relates a Java class to the SQL type name of an Oracle object. This one-to-one mapping is stored in a hash table as a keyword-value pair. When you read data from an Oracle object, the JDBC driver considers the type map to determine which Java class to use to materialize the data from the Oracle object type. When you write data to an Oracle object, the JDBC driver gets the SQL type name from the Java class by calling thegetSQLTypeName method of theSQLData interface. The actual conversion between SQL and Java is performed by the driver.
The attributes of the Java class that corresponds to an Oracle object can use either Java native types or Oracle native types to store attributes.
When using aSQLData implementation, the JDBC applications programmer is responsible for providing a type map, which must be an instance of a class that implements the standardjava.util.Map interface.
You have the option of creating your own class to accomplish this, but the standardjava.util.Hashtable class meets the requirement.
Hashtable and other classes used for type maps implement aput method that takes keyword-value pairs as input, where each key is a fully qualified SQL type name and the corresponding value is an instance of a specified Java class.
A type map is associated with a connection instance. The standardjava.sql.Connection interface and the Oracle-specificoracle.jdbc.OracleConnection interface include agetTypeMap method. Both return aMap object.
This section covers the following topics:
When a connection instance is first established, the default type map is empty. You must populate it.
Perform the followinggeneral steps to add entries to an existing type map:
Use thegetTypeMap method of yourOracleConnection object to return the type map object of the connection. ThegetTypeMap method returns ajava.util.Map object. For example, presuming anOracleConnection instanceoraconn:
java.util.Map myMap = oraconn.getTypeMap();
Note:
If the type map in theOracleConnection instance has not been initialized, then the first call togetTypeMap returns an empty map.Use theput method of the type map to add map entries. Theput method takes two arguments: a SQL type name string and an instance of a specified Java class that you want to map to.
myMap.put(sqlTypeName,classObject);
ThesqlTypeName is a string that represents the fully qualified name of the SQL type in the database. TheclassObject is the Java class object to which you want to map the SQL type. Get the class object with theClass.forName method, as follows:
myMap.put(sqlTypeName, Class.forName(className));
For example, if you have aPERSON SQL data type defined in theCORPORATE database schema, then map it to aPerson Java class defined asPerson with this statement:
myMap.put("CORPORATE.PERSON", Class.forName("Person"));The map has an entry that maps thePERSON SQL data type in theCORPORATE database to thePerson Java class.
Note:
SQL type names in the type map must be all uppercase, because that is how Oracle Database stores SQL names.Performthe following general steps to create a new type map. This example uses an instance ofjava.util.Hashtable, which extendsjava.util.Dictionary and implementsjava.util.Map.
Create a new type map object.
Hashtable newMap = new Hashtable();
Use theput method of the type map object to add entries to the map. For example, if you have anEMPLOYEE SQL type defined in theCORPORATE database, then you can map it to anEmployee class object defined byEmployee.java, as follows:
newMap.put("CORPORATE.EMPLOYEE", class.forName("Employee"));When you finish adding entries to the map, use thesetTypeMap method of theOracleConnection object to overwrite the existing type map of the connection. For example:
oraconn.setTypeMap(newMap);
In this example,setTypeMap overwrites the original map of theoraconn connection object withnewMap.
Note:
The default type map of a connection instance is used when mapping is required but no map name is specified, such as for a result setgetObject call that does not specify the map as input.If you do not provide atype map with an appropriate entry when using agetObject call, then the JDBC driver will materialize an Oracle object as an instance of theoracle.sql.STRUCT class. If the Oracle object type contains embedded objects and they are not present in the type map, then the driver will materialize the embedded objects as instances oforacle.sql.STRUCT as well. If the embedded objects are present in the type map, then a call to thegetAttributes method will return embedded objects as instances of the specified Java classes from the type map.
One of the choices in making anOracle object and its attribute data available to Java applications is to create a custom object class that implements theSQLData interface. Note that if you use this interface, you must supply atype map that specifies the Oracle object types in the database and the names of the corresponding custom object classes that you will create for them.
TheSQLData interface defines methods that translate between SQL and Java for Oracle database objects. Standard JDBC provides aSQLData interface and companionSQLInput andSQLOutput interfaces in thejava.sql package.
If you create a custom object class that implementsSQLData, then you must provide areadSQL method and awriteSQL method, as specified by theSQLData interface.
The JDBC driver calls yourreadSQL method to read a stream of data values from the database and populate an instance of your custom object class. Typically, the driver would use this method as part of anOracleResultSet objectgetObject call.
Similarly, the JDBC driver calls yourwriteSQL method to write a sequence of data values from an instance of your custom object class to a stream that can be written to the database. Typically, the driver would use this method as part of anOraclePreparedStatement objectsetObject call.
Understanding the SQLInput and SQLOutput Interfaces
The JDBC driver includes classes that implement theSQLInput andSQLOutput interfaces. It is not necessary to implement theSQLOutput orSQLInput objects. The JDBC drivers will do this for you.
TheSQLInput implementation is an input stream class, an instance of which is passed to thereadSQL method.SQLInput includes areadXXX method for every possible Java type that attributes of an Oracle object may be converted to, such asreadObject,readInt,readLong,readFloat,readBlob, and so on. EachreadXXX method converts SQL data to Java data and returns it as the result with the corresponding Java type. For example,readInt returns anint.
TheSQLOutput implementation is an output stream class, an instance of which is passed in to thewriteSQL method.SQLOutput includes awriteXXX method for each of these Java types. EachwriteXXX method converts Java data to SQL data, taking as input a parameter of the relevant Java type. For example,writeString would take as input aString attribute from your Java class.
Implementing readSQL and writeSQL Methods
When you create a custom object class that implementsSQLData, you must implement thereadSQL andwriteSQL methods, as described here.
You must implementreadSQL as follows:
public void readSQL(SQLInputstream, Stringsql_type_name) throws SQLException
ThereadSQL method takes as input aSQLInput stream and a string that indicates the SQL type name of the data, that is, the name of the Oracle object type, such asEMPLOYEE.
When your Java application callsgetObject, the JDBC driver creates aSQLInput stream object and populates it with data from the database. The driver can also determine the SQL type name of the data when it reads it from the database. When the driver callsreadSQL, it passes in these parameters.
For each Java data type that maps to an attribute of the Oracle object,readSQL must call the appropriatereadXXX method of theSQLInput stream that is passed in.
For example, if you are readingEMPLOYEE objects that have an employee name as aCHAR variable and an employee number as aNUMBER variable, then you must have areadString call and areadInt call in yourreadSQL method. JDBC calls these methods according to the order in which the attributes appear in the SQL definition of the Oracle object type.
ThereadSQL method takes the data that thereadXXX methods read and convert and assigns them to the appropriate fields or elements of a custom object class instance.
You must implementwriteSQL as follows:
public void writeSQL(SQLOutput stream) throws SQLException
ThewriteSQL method takes as input aSQLOutput stream.
When your Java application callssetObject, the JDBC driver creates aSQLOutput stream object. When the driver callswriteSQL, it passes in this stream parameter.
For each Java data type that maps to an attribute of the Oracle object,writeSQL must call the appropriatewriteXXX method of theSQLOutput stream that is passed in.
For example, if you are writing toEMPLOYEE objects that have an employee name as aCHAR variable and an employee number as aNUMBER variable, then you must have awriteString call and awriteInt call in yourwriteSQL method. These methods must be called according to the order in which attributes appear in the SQL definition of the Oracle object type.
ThewriteSQL method then writes the data to theSQLOutput stream by calling thewriteXXX methods so that it can be sent to the database once you execute the prepared statement.
This section describes how to read data from an Oracle object or write data to an Oracle object if your corresponding Java class implementsSQLData.
Reading SQLData Objects from a Result Set
The following textsummarizes the steps to read data from an Oracle object into your Java application when you choose theSQLData implementation for your custom object class.
These steps assume you have already defined the Oracle object type, created the corresponding custom object class, updated the type map to define the mapping between the Oracle object and the Java class, and defined a statement objectstmt.
Query the database to read the Oracle object into a JDBC result set.
ResultSet rs = stmt.executeQuery("SELECT emp_col FROM personnel");ThePERSONNEL table contains one column,EMP_COL, of SQL typeEMP_OBJECT. This SQL type is defined in the type map to map to the Java classEmployee.
Use thegetObject method of your result set to populate an instance of your custom object class with data from one row of the result set. ThegetObject method returns the user-definedSQLData object because the type map contains an entry forEmployee.
if (rs.next()) Employee emp = (Employee)rs.getObject(1);
Note that if the type map did not have an entry for the object, thengetObject would return anoracle.sql.STRUCT object. Cast the output to typeSTRUCT, because thegetObject method signature returns the genericjava.lang.Object type.
if (rs.next()) STRUCT empstruct = (STRUCT)rs.getObject(1);
ThegetObject method callsreadSQL, which, in turn, callsreadXXX from theSQLData interface.
Note:
If you want to avoid using the defined type map, then use thegetSTRUCT method. This method always returns aSTRUCT object, even if there is a mapping entry in the type map.If you haveget methods in your custom object class, then use them to read data from your object attributes. For example, ifEMPLOYEE has the attributesEmpName of typeCHAR andEmpNum of typeNUMBER, then provide agetEmpName method that returns a JavaString and agetEmpNum method that returns anint value. Then call them in your Java application, as follows:
String empname = emp.getEmpName();int empnumber = emp.getEmpNum();
Retrieving SQLData Objects from a Callable Statement OUT Parameter
Consider you have anOracleCallableStatement instance,ocs, that calls a PL/SQL functionGETEMPLOYEE. The program passes an employee number to the function. The function returns the correspondingEmployee object. To retrieve this object you do the following:
Prepare anOracleCallableStatement to call theGETEMPLOYEE function, as follows:
OracleCallableStatement ocs = (OracleCallableStatement)conn.prepareCall("{ ? = call GETEMPLOYEE(?) }");Declare theempnumber as the input parameter toGETEMPLOYEE. Register theSQLData object as theOUT parameter, with the type codeOracleTypes.STRUCT. Then, run the statement. This can be done as follows:
ocs.setInt(2, empnumber); ocs.registerOutParameter(1, OracleTypes.STRUCT, "EMP_OBJECT"); ocs.execute();
Use thegetObject method to retrieve the employee object. The following code assumes that there is a type map entry to map the Oracle object to the Java typeEmployee:
Employee emp = (Employee)ocs.getObject(1);
If there is no type map entry, thengetObject would return anoracle.sql.STRUCT object. Cast the output to theSTRUCT type, because thegetObject method returns an instance of the genericjava.lang.Object class. This is done as follows:
STRUCT emp = (STRUCT)ocs.getObject(1);
Passing SQLData Objects to a Callable Statement as an IN Parameter
Suppose you have a PL/SQL functionaddEmployee(?) that takes anEmployee object as anIN parameter and adds it to thePERSONNEL table. In this example,emp is a validEmployee object.
Prepare anOracleCallableStatement to call theaddEmployee(?) function.
OracleCallableStatement ocs = (OracleCallableStatement) conn.prepareCall("{ call addEmployee(?) }");UsesetObject to pass theemp object as anIN parameter to the callable statement. Then, call the statement.
ocs.setObject(1, emp); ocs.execute();
Writing Data to an Oracle Object Using a SQLData Implementation
Thefollowing text describes the steps in writing data to an Oracle object from your Java application when you choose theSQLData implementation for your custom object class.
This description assumes you have already defined the Oracle object type, created the corresponding Java class, and updated the type map to define the mapping between the Oracle object and the Java class.
If you haveset methods in your custom object class, then use them to write data from Java variables in your application to attributes of your Java data type object.
emp.setEmpName(empname);emp.setEmpNum(empnumber);
This statement uses theemp object and theempname andempnumber variables assigned in the preceding example.
Prepare a statement that updates an Oracle object in a row of a database table, as appropriate, using the data provided in your Java data type object.
PreparedStatement pstmt = conn.prepareStatement ("INSERT INTO PERSONNEL VALUES (?)");This assumesconn is your connection object.
Use thesetObject method of the prepared statement to bind your Java data type object to the prepared statement.
pstmt.setObject(1, emp);
Run the statement, which updates the database.
pstmt.executeUpdate();
One of the choices in making an Oracle object and its attribute data available to Java applications is to create a custom object class that implements theoracle.sql.ORAData andoracle.sql.ORADataFactory interfaces. TheORAData andORADataFactory interfaces are supplied by Oracle and are not a part of the JDBC standard.
Note:
The JPublisher utility supports the generation of classes that implement theORAData andORADataFactory interfaces.Understanding ORAData Features
TheORAData interface has the following advantages:
It recognizes Oracle extensions to the JDBC.ORAData usesoracle.sql.Datum types directly.
It does not require a type map to specify the names of the Java custom classes you want to create.
It provides better performance.ORAData works directly withDatum types, the internal format the driver uses to hold Oracle objects.
TheORAData andORADataFactory interfaces do the following:
ThetoDatum method of theORAData class transforms the data into anoracle.sql.* representation.
ORADataFactory specifies acreate method equivalent to a constructor for your custom object class. It creates and returns anORAData instance. The JDBC driver uses thecreate method to return an instance of the custom object class to your Java application or applet. It takes as input anoracle.sql.Datum object and an integer indicating the corresponding SQL type code as specified in theOracleTypes class.
ORAData andORADataFactory have the following definitions:
public interface ORAData { Datum toDatum (OracleConnectionconn) throws SQLException;} public interface ORADataFactory { ORAData create (Datumd, intsql_Type_Code) throws SQLException; }Whereconn represents the Connection object,d represents an object of typeoracle.sql.Datum andsql_Type_Code represents the SQL type code of theDatum object.
Retrieving and Inserting Object Data
The JDBC drivers provide the following methods to retrieve and insert object data as instances ofORAData.
You can retrieve the object data in one of the following ways:
Use the followinggetORAData method of the Oracle-specificOracleResultSet class:
ors.getORAData (intcol_index, ORADataFactoryfactory);
This method takes as input the column index of the data in your result set and aORADataFactory instance. For example, you can implement agetORAFactory method in your custom object class to produce theORADataFactory instance to input togetORAData. The type map is not required when using Java classes that implementORAData.
Use the standardgetObject(index,map) method specified by theResultSet interface to retrieve data as instances ofORAData. In this case, you must have an entry in the type map that identifies the factory class to be used for the given object type and its corresponding SQL type name.
You can insert object data in one of the following ways:
Use the followingsetORAData method of the Oracle-specificOraclePreparedStatement class:
ops.setORAData (intbind_index, ORADatacustom_obj);
This method takes as input the parameter index of the bind variable and the name of the object containing the variable.
Use the standardsetObject method specified by thePreparedStatement interface. You can also use this method, in its different forms, to insertORAData instances without requiring a type map.
The following sections describe thegetORAData andsetORAData methods.
To continue the example of an Oracle objectEMPLOYEE, you might have something like the following in your Java application:
ORAData datum = ors.getORAData(1, Employee.getORAFactory());
In this example,ors is an Oracle result set,getORAData is a method in theOracleResultSet class used to retrieve aORAData object, and theEMPLOYEE is in column 1 of the result set. ThestaticEmployee.getORAFactory method will return aORADataFactory to the JDBC driver. The JDBC driver will callcreate() from this object, returning to your Java application an instance of theEmployee class populated with data from the result set.
Note:
ORAData andORADataFactory are defined as separate interfaces so that different Java classes can implement them if you wish.
To use theORAData interface, your custom object classes must importoracle.sql.*.
This section describes how to read data from an Oracle object or write data to an Oracle object if your corresponding Java class implementsORAData.
Reading Data from an Oracle Object Using a ORAData Implementation
The following textsummarizes the steps in reading data from an Oracle object into your Java application. These steps apply whether you implementORAData manually or use JPublisher to produce your custom object classes.
These steps assume you have already defined the Oracle object type, created the corresponding custom object class or had JPublisher create it for you, and defined a statement objectstmt.
Query the database to read the Oracle object into a result set, casting it to an Oracle result set.
OracleResultSet ors = (OracleResultSet)stmt.executeQuery ("SELECT Emp_col FROM PERSONNEL");WherePERSONNEL is a one-column table. The column name isEmp_col of typeEmployee_object.
Use thegetORAData method of your Oracle result set to populate an instance of your custom object class with data from one row of the result set. ThegetORAData method returns anoracle.sql.ORAData object, which you can cast to your specific custom object class.
if (ors.next()) Employee emp = (Employee)ors.getORAData(1, Employee.getORAFactory());
or:
if (ors.next()) ORAData datum = ors.getORAData(1, Employee.getORAFactory());
This example assumes thatEmployee is the name of your custom object class andors is the name of yourOracleResultSet object.
In case you do not want to usegetORAData, the JDBC drivers let you use thegetObject method of a standard JDBCResultSet to retrieveORAData data. However, you must have an entry in the type map that identifies the factory class to be used for the given object type and its corresponding SQL type name.
For example, if the SQL type name for your object isEMPLOYEE, then the corresponding Java class isEmployee, which will implementORAData. The corresponding Factory class isEmployeeFactory, which will implementORADataFactory.
Use this statement to declare theEmployeeFactory entry for your type map:
map.put ("EMPLOYEE", Class.forName ("EmployeeFactory"));Then use the form ofgetObject where you specify the map object:
Employee emp = (Employee) rs.getObject (1, map);
If the default type map of the connection already has an entry that identifies the factory class to be used for the given object type and its corresponding SQL type name, then you can use this form ofgetObject:
Employee emp = (Employee) rs.getObject (1);
If you haveget methods in your custom object class, then use them to read data from your object attributes into Java variables in your application. For example, ifEMPLOYEE hasEmpName of typeCHAR andEmpNum of typeNUMBER, provide agetEmpName method that returns a JavaString and agetEmpNum method that returns an integer. Then call them in your Java application as follows:
String empname = emp.getEmpName();int empnumber = emp.getEmpNum();
Note:
Alternatively, you can fetch data using a callable statement object. TheOracleCallableStatement class also has agetORAData method.Writing Data to an Oracle Object Using a ORAData Implementation
The following textsummarizes the steps in writing data to an Oracle object from your Java application. These steps apply whether you implementORAData manually or useJPublisher to produce your custom object classes.
These steps assume you have already defined the Oracle object type and created the corresponding custom object class.
Note:
The type map is not used when you are performing databaseINSERT andUPDATE operations.If you haveset methods in your custom object class, then use them to write data from Java variables in your application to attributes of your Java data type object.
emp.setEmpName(empname);emp.setEmpNum(empnumber);
Write an Oracle prepared statement that updates an Oracle object in a row of a database table, as appropriate, using the data provided in your Java data type object.
OraclePreparedStatement opstmt = conn.prepareStatement ("UPDATE PERSONNEL SET Employee = ? WHERE Employee.EmpNum = 28959);This assumesconn is yourConnection object.
Use thesetORAData method of the Oracle prepared statement to bind your Java data type object to the prepared statement.
opstmt.setORAData(1, emp);
ThesetORAData method calls thetoDatum method of the custom object class instance to retrieve anoracle.sql.STRUCT object that can be written to the database.
In this step you could also use thesetObject method to bind the Java data type. For example:
opstmt.setObject(1,emp);
Note:
You can use your Java data type objects as eitherIN orOUT bind variables.TheORAData interface offers far more flexibility than theSQLData interface. TheSQLData interface is designed to let you customize the mapping of only Oracle object types to Java types of your choice. Implementing theSQLData interface lets the JDBC driver populate fields of a custom Java class instance from the original SQL object data, and the reverse, after performing the appropriate conversions between Java and SQL types.
TheORAData interface goes beyond supporting the customization of Oracle object types to Java types. It lets you provide a mapping between Java object types andany SQL type supported by theoracle.sql package.
It may be useful to provide custom Java classes to wraporacle.sql.* types and perhaps implement customized conversions or functionality as well. The following are some possible scenarios:
Performing encryption and decryption or validation of data
Performing logging of values that have been read or are being written
Parsing character columns, such as character fields containing URL information, into smaller components
Mapping character strings into numeric constants
Making data into more desirable Java formats, such as mapping aDATE field tojava.util.Date format
Customizing data representation, for example, data in a table column is in feet but you want it represented in meters after it is selected
Serializing and deserializing Java objects
For example, useORAData to store instances of Java objects that do not correspond to a particular SQL object type in the database in columns of SQL typeRAW. Thecreate method inORADataFactory would have to implement a conversion from an object of typeoracle.sql.RAW to the desired Java object. ThetoDatum method inORAData would have to implement a conversion from the Java object to anoracle.sql.RAW object. This can be done, for example, by using Java serialization.
Upon retrieval, the JDBC driver transparently retrieves the raw bytes of data in the form of anoracle.sql.RAW and calls thecreate method ofORADataFactory to convert theoracle.sql.RAW object to the desired Java class.
When you insert the Java object into the database, you can simply bind it to a column of typeRAW to store it. The driver transparently calls theORAData.toDatum method to convert the Java object to anoracle.sql.RAW object. This object is then stored in a column of typeRAW in the database.
Support for theORAData interfaces is also highly efficient because the conversions are designed to work usingoracle.sql.* formats, which happen to be the internal formats used by the JDBC drivers. Moreover, the type map, which is necessary for theSQLData interface, is not required when using Java classes that implementORAData.
After theoracle.jdbc interfaces were introduced in Oracle9i Database as an alternative to theoracle.jdbc.driver classes, theoracle.sql.CustomDatum andoracle.sql.CustomDatumFactory interfaces, formerly used to access customized objects, were deprecated. Oracle recommends you use the new interfaces,oracle.sql.ORAData andoracle.sql.ORADataFactory.
Object-type inheritance allows a new object type to be created by extending another object type. The new object type is then a subtype of the object type from which it extends. The subtype automatically inherits all the attributes and methods defined in the supertype. The subtype can add attributes and methods and overload or override methods inherited from the supertype.
Object-type inheritance introducessubstitutability. Substitutability is the ability of a slot declared to hold a value of typeT in addition to any subtype of typeT. Oracle JDBC drivers handle substitutability transparently.
A database object is returned with its most specific type without losing information. For example, if theSTUDENT_T object is stored in aPERSON_T slot, Oracle JDBC driver returns a Java object that represents theSTUDENT_T object.
This section covers the following topics:
Create custom object classes if you want to have Java classes that explicitly correspond to the Oracle object types. If you have a hierarchy of object types, you may want a corresponding hierarchy of Java classes.
The most common way to create a database subtype in JDBC is to run a SQLCREATE TYPE command using theexecute method of thejava.sql.Statement interface. For example, you want to create a type inheritance hierarchy for:
PERSON_T | STUDENT_T | PARTTIMESTUDENT_T
The JDBC code for this can be as follows:
Statement s = conn.createStatement();s.execute ("CREATE TYPE Person_T (SSN NUMBER, name VARCHAR2(30), address VARCHAR2(255))");s.execute ("CREATE TYPE Student_T UNDER Person_t (deptid NUMBER, major VARCHAR2(100))");s.execute ("CREATE TYPE PartTimeStudent_t UNDER Student_t (numHours NUMBER)");In the following code, thefoo member procedure in typeST is overloaded and the member procedureprint overwrites the copy it inherits from typeT.
CREATE TYPE T AS OBJECT (..., MEMBER PROCEDURE foo(x NUMBER), MEMBER PROCEDURE Print(), ... NOT FINAL; CREATE TYPE ST UNDER T (..., MEMBER PROCEDURE foo(x DATE), <-- overload "foo" OVERRIDING MEMBER PROCEDURE Print(), <-- override "print" STATIC FUNCTION bar(...) ... ... );
Once the subtypes have been created, they can be used as both columns of a base table as well as attributes of a object type.
In most cases, a customized Java class represents a database object type. When you create a customized Java class for a subtype, the Java class can either mirror the database object type hierarchy or not.
You can use either theORAData orSQLData solution in creating classes to map to the hierarchy of object types.
This section covers the following topics:
Customized mapping where Java classes implement theoracle.sql.ORAData interface is the recommended mapping.ORAData mapping requires the JDBC application to implement theORAData andORADataFactory interfaces. The class implementing theORADataFactory interface contains a factory method that produces objects. Each object represents a database object.
The hierarchy of the class implementing theORAData interface can mirror the database object type hierarchy. For example, the Java classes mapping toPERSON_T andSTUDENT_T are as follows:
Code for thePerson.java class which implements theORAData andORADataFactory interfaces:
class Person implements ORAData, ORADataFactory { static final Person _personFactory = new Person(); public NUMBER ssn; public CHAR name; public CHAR address; public static ORADataFactory getORADataFactory() { return _personFactory; } public Person () {} public Person(NUMBER ssn, CHAR name, CHAR address) { this.ssn = ssn; this.name = name; this.address = address; } public Datum toDatum(OracleConnection c) throws SQLException { StructDescriptor sd = StructDescriptor.createDescriptor("SCOTT.PERSON_T", c); Object [] attributes = { ssn, name, address }; return new STRUCT(sd, c, attributes); } public ORAData create(Datum d, int sqlType) throws SQLException { if (d == null) return null; Object [] attributes = ((STRUCT) d).getOracleAttributes(); return new Person((NUMBER) attributes[0], (CHAR) attributes[1], (CHAR) attributes[2]); } }Student.java extending Person.java
Code for theStudent.java class, which extends thePerson.java class:
class Student extends Person { static final Student _studentFactory = new Student (); public NUMBER deptid; public CHAR major; public static ORADataFactory getORADataFactory() { return _studentFactory; } public Student () {} public Student (NUMBER ssn, CHAR name, CHAR address, NUMBER deptid, CHAR major) { super (ssn, name, address); this.deptid = deptid; this.major = major; } public Datum toDatum(OracleConnection c) throws SQLException { StructDescriptor sd = StructDescriptor.createDescriptor("SCOTT.STUDENT_T", c); Object [] attributes = { ssn, name, address, deptid, major }; return new STRUCT(sd, c, attributes); } public CustomDatum create(Datum d, int sqlType) throws SQLException { if (d == null) return null; Object [] attributes = ((STRUCT) d).getOracleAttributes(); return new Student((NUMBER) attributes[0], (CHAR) attributes[1], (CHAR) attributes[2], (NUMBER) attributes[3], (CHAR) attributes[4]); } }Customized classes that implement theORAData interface do not have to mirror the database object type hierarchy. For example, you could have declared theStudent class without a superclass. In this case,Student would contain fields to hold the inherited attributes fromPERSON_T as well as the attributes declared bySTUDENT_T.
The JDBC application uses the factory class in querying the database to return instances ofPerson or its subclasses, as in the following example:
ResultSet rset = stmt.executeQuery ("select person from tab1"); while (rset.next()) { Object s = rset.getORAData (1, PersonFactory.getORADataFactory()); ... }A class implementing theORADataFactory interface should be able to produce instances of the associated custom object type, as well as instances of any subtype, or at least all the types you expect to support.
In the following example, thePersonFactory.getORADataFactory method returns a factory that can handlePERSON_T,STUDENT_T, andPARTTIMESTUDENT_T objects, by returningperson,student, orparttimestudent Java instances.
class PersonFactory implements ORADataFactory { static final PersonFactory _factory = new PersonFactory (); public static ORADataFactory getORADataFactory() { return _factory; } public ORAData create(Datum d, int sqlType) throws SQLException { STRUCT s = (STRUCT) d; if (s.getSQLTypeName ().equals ("SCOTT.PERSON_T")) return Person.getORADataFactory ().create (d, sqlType); else if (s.getSQLTypeName ().equals ("SCOTT.STUDENT_T")) return Student.getORADataFactory ().create(d, sqlType); else if (s.getSQLTypeName ().equals ("SCOTT.PARTTIMESTUDENT_T")) return ParttimeStudent.getORADataFactory ().create(d, sqlType); else return null; } }The following example assumes a tabletabl1, such as the following:
CREATE TABLE tabl1 (idx NUMBER, person PERSON_T); INSERT INTO tabl1 VALUES (1, PERSON_T (1000, 'Scott', '100 Oracle Parkway')); INSERT INTO tabl1 VALUES (2, STUDENT_T (1001, 'Peter', '200 Oracle Parkway', 101, 'CS')); INSERT INTO tabl1 VALUES (3, PARTTIMESTUDENT_T (1002, 'David', '300 Oracle Parkway', 102, 'EE'));
The customized classes that implement thejava.sql.SQLData interface can mirror the database object type hierarchy. ThereadSQL andwriteSQL methods of a subclass typically call the corresponding superclass methods to read or write the superclass attributes before reading or writing the subclass attributes. For example, the Java classes mapping toPERSON_T andSTUDENT_T are as follows:
Code for thePerson.java class, which implements theSQLData interface:
import java.sql.*; public class Person implements SQLData { private String sql_type; public int ssn; public String name; public String address; public Person () {} 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); } }Student.java extending Student.java
Code for theStudent.java class, which extends thePerson.java class:
import java.sql.*; public class Student extends Person { private String sql_type; public int deptid; public String major; public Student () { super(); } public String getSQLTypeName() throws SQLException { return sql_type; } public void readSQL(SQLInput stream, String typeName) throws SQLException { super.readSQL (stream, typeName); // read supertype attributes sql_type = typeName; deptid = stream.readInt(); major = stream.readString(); } public void writeSQL(SQLOutput stream) throws SQLException { super.writeSQL (stream); // write supertype // attributes stream.writeInt (deptid); stream.writeString (major); } }Although not required, it is recommended that the customized classes, which implement theSQLData interface, mirror the database object type hierarchy. For example, you could have declared theStudent class without a superclass. In this case,Student would contain fields to hold the inherited attributes fromPERSON_T as well as the attributes declared bySTUDENT_T.
Code for theStudent.java class, which does not extend thePerson.java class, but implements the SQLData interface directly:
import java.sql.*; public class Student implements SQLData { private String sql_type; public int ssn; public String name; public String address; public int deptid; public String major; public Student () {} 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(); deptid = stream.readInt(); major = stream.readString(); } public void writeSQL(SQLOutput stream) throws SQLException { stream.writeInt (ssn); stream.writeString (name); stream.writeString (address); stream.writeInt (deptid); stream.writeString (major); } }Even though you can manually create customized classes that implement theSQLData,ORAData, andORADataFactory interfaces, it is recommended that you use Oracle JPublisher to automatically generate these classes. The customized classes generatedby Oracle JPublisher that implement theSQLData,ORAData, andORADataFactory interfaces, can mirror the inheritance hierarchy.
In a typical JDBC application, a subtype object is returned as one of the following:
A query result
A PL/SQLOUT parameter
A type attribute
You can use either the default mapping or theSQLData mapping or theORAData mapping to retrieve a subtype.
By default, a database object is returned as an instance of theoracle.sql.STRUCT class. This instance may represent an object of either the declared type or subtype of the declared type. If theSTRUCT class represents a subtype object in the database, then it contains the attributes of its supertype as well as those defined in the subtype.
Oracle JDBC driver returns database objects in their most specific type. The JDBC application can use thegetSQLTypeName method of theSTRUCT class to determine the SQL type of theSTRUCT object. The following code shows this:
// tab1.person column can store PERSON_T, STUDENT_T and PARTIMESTUDENT_T objects ResultSet rset = stmt.executeQuery ("select person from tab1"); while (rset.next()) { oracle.sql.STRUCT s = (oracle.sql.STRUCT) rset.getObject(1); if (s != null) System.out.println (s.getSQLTypeName()); // print out the type name which // may be SCOTT.PERSON_T, SCOTT.STUDENT_T or SCOTT.PARTTIMESTUDENT_T}WithSQLData mapping, the JDBC driver returns the database object as an instance of the class implementing theSQLData interface.
To useSQLData mapping in retrieving database objects, do the following:
Implement the container classes that implement theSQLData interface for the desired object types.
Populate the connection type map with entries that specify what custom Java type corresponds to each Oracle object type.
Use thegetObject method to access the SQL object values.
The JDBC driver checks the type map for an entry match. If one exists, then the driver returns the database object as an instance of the class implementing theSQLData interface.
The following code shows the whole SQLData customized mapping process:
// The JDBC application developer implements Person.java for PERSON_T, // Student.java for STUDENT_T // and ParttimeStudent.java for PARTTIMESTUDEN_T. Connection conn = ...; // make a JDBC connection // obtains the connection typemap java.util.Map map = conn.getTypeMap (); // populate the type map map.put ("SCOTT.PERSON_T", Class.forName ("Person")); map.put ("SCOTT.STUDENT_T", Class.forName ("Student")); map.put ("SCOTT.PARTTIMESTUDENT_T", Class.forName ("ParttimeStudent")); // tab1.person column can store PERSON_T, STUDENT_T and PARTTIMESTUDENT_T objects ResultSet rset = stmt.executeQuery ("select person from tab1"); while (rset.next()) { // "s" is instance of Person, Student or ParttimeStudent Object s = rset.getObject(1); if (s != null) { if (s instanceof Person) System.out.println ("This is a Person"); else if (s instanceof Student) System.out.println ("This is a Student"); else if (s instanceof ParttimeStudent) System.out.pritnln ("This is a PartimeStudent"); else System.out.println ("Unknown type"); } }The JDBC drivers check the connection type map for each call to the following:
getObject method of thejava.sql.ResultSet andjava.sql.CallableStatement interfaces
getAttribute method of thejava.sql.Struct interface
getArray method of thejava.sql.Array interface
getValue method of theoracle.sql.REF interface
WithORAData mapping, the JDBC driver returns the database object as an instance of the class implementing theORAData interface.
Oracle JDBC driver needs to be informed of what Java class is mapped to the Oracle object type. The following are the two ways to inform Oracle JDBC drivers:
The JDBC application uses thegetORAData(int idx, ORADataFactory f) method to access database objects. The second parameter of thegetORAData method specifies an instance of the factory class that produces the customized class. ThegetORAData method is available in theOracleResultSet andOracleCallableStatement classes.
The JDBC application populates the connection type map with entries that specify what custom Java type corresponds to each Oracle object type. ThegetObject method is used to access the Oracle object values.
The second approach involves the use of the standardgetObject method. The following code example demonstrates the first approach:
// tab1.person column can store both PERSON_T and STUDENT_T objects ResultSet rset = stmt.executeQuery ("select person from tab1"); while (rset.next()) { Object s = rset.getORAData (1, PersonFactory.getORADataFactory()); if (s != null) { if (s instanceof Person) System.out.println ("This is a Person"); else if (s instanceof Student) System.out.println ("This is a Student"); else if (s instanceof ParttimeStudent) System.out.pritnln ("This is a PartimeStudent"); else System.out.println ("Unknown type"); } }There are cases where JDBC applications create database subtype objects with JDBC drivers. These objects are sent either to the database as bind variables or are used to exchange information within the JDBC application.
With customized mapping, the JDBC application creates eitherSQLData- orORAData-based objects, depending on the approach you choose, to represent database subtype objects. With default mapping, the JDBC application createsSTRUCT objects to represent database subtype objects. All the data fields inherited from the supertype as well as all the fields defined in the subtype must have values. The following code demonstrates this:
Connection conn = ... // make a JDBC connection StructDescriptor desc = StructDescriptor.createDescriptor ("SCOTT.PARTTIMESTUDENT", conn); Object[] attrs = { new Integer(1234), "Scott", "500 Oracle Parkway", // data fields defined in // PERSON_T new Integer(102), "CS", // data fields defined in // STUDENT_T new Integer(4) // data fields defined in // PARTTIMESTUDENT_T }; STRUCT s = new STRUCT (desc, conn, attrs);s is initialized with data fields inherited fromPERSON_T andSTUDENT_T, and data fields defined inPARTTIMESTUDENT_T.
In a typical JDBC application, a Java object that represents a database object is sent to the databases as one of the following:
A data manipulation language (DML) bind variable
A PL/SQLIN parameter
An object type attribute value
The Java object can be an instance of theSTRUCT class or an instance of the class implementing either theSQLData orORAData interface. Oracle JDBC driver will convert the Java object into the linearized format acceptable to the database SQL engine. Binding a subtype object is the same as binding a standard object.
While the logic to access subtype data fields is part of the customized class, this logic for default mapping is defined in the JDBC application itself. The database objects are returned as instances of theoracle.sql.STRUCT class. The JDBC application needs to call one of the following access methods in theSTRUCT class to access the data fields:
Object[] getAttribute()
oracle.sql.Datum[] getOracleAttribute()
Subtype Data Fields from the getAttribute Method
ThegetAttribute method of thejava.sql.Struct interface is used in JDBC 2.0 to access object data fields. This method returns ajava.lang.Object array, where each array element represents an object attribute. You can determine the individual element type by referencing the corresponding attribute type in the JDBC conversion matrix, as listed inTable 4-1. For example, a SQLNUMBER attribute is converted to ajava.math.BigDecimal object. ThegetAttribute method returns all the data fields defined in the supertype of the object type as well as data fields defined in the subtype. The supertype data fields are listed first followed by the subtype data fields.
Subtype Data Fields from the getOracleAttribute Method
ThegetOracleAttribute method is an Oracle extension method and is more efficient than thegetAttribute method. ThegetOracleAttribute method returns anoracle.sql.Datum array to hold the data fields. Each element in theoracle.sql.Datum array represents an attribute. You can determine the individual element type by referencing the corresponding attribute type in the Oracle conversion matrix, as listed inTable 4-1. For example, a SQLNUMBER attribute is converted to anoracle.sql.NUMBER object. ThegetOracleAttribute method returns all the attributes defined in the supertype of the object type, as well as attributes defined in the subtype. The supertype data fields are listed first followed by the subtype data fields.
The following code shows the use of thegetAttribute method:
// tab1.person column can store PERSON_T, STUDENT_T and PARTIMESTUDENT_T objects ResultSet rset = stmt.executeQuery ("select person from tab1"); while (rset.next()) { oracle.sql.STRUCT s = (oracle.sql.STRUCT) rset.getObject(1); if (s != null) { String sqlname = s.getSQLTypeName(); Object[] attrs = s.getAttribute(); if (sqlname.equals ("SCOTT.PERSON") { System.out.println ("ssn="+((BigDecimal)attrs[0]).intValue()); System.out.println ("name="+((String)attrs[1])); System.out.println ("address="+((String)attrs[2])); } else if (sqlname.equals ("SCOTT.STUDENT")) { System.out.println ("ssn="+((BigDecimal)attrs[0]).intValue()); System.out.println ("name="+((String)attrs[1])); System.out.println ("address="+((String)attrs[2])); System.out.println ("deptid="+((BigDecimal)attrs[3]).intValue()); System.out.println ("major="+((String)attrs[4])); } else if (sqlname.equals ("SCOTT.PARTTIMESTUDENT")) { System.out.println ("ssn="+((BigDecimal)attrs[0]).intValue()); System.out.println ("name="+((String)attrs[1])); System.out.println ("address="+((String)attrs[2])); System.out.println ("deptid="+((BigDecimal)attrs[3]).intValue()); System.out.println ("major="+((String)attrs[4])); System.out.println ("numHours="+((BigDecimal)attrs[5]).intValue()); } else throw new Exception ("Invalid type name: "+sqlname); } } rset.close (); stmt.close (); conn.close ();Oracle JDBC drivers provide a set of metadata methods to access inheritance properties. The inheritance metadata methods are defined in theoracle.sql.StructDescriptor andoracle.jdbc.StructMetaData classes.
TheStructMetaData class provides inheritance metadata methods for subtype attributes. ThegetMetaData method of theStructDescriptor class returns an instance ofStructMetaData of the type. TheStructMetaData class contains the following inheritance metadata methods:
A convenient way to create custom object classes, as well as other kinds of custom Java classes, is to use the OracleJPublisher utility. It generates a full definition for a custom Java class, which you can instantiate to hold the data from an Oracle object. JPublisher-generated classes include methods to convert data from SQL to Java and from Java to SQL, as well as getter and setter methods for the object attributes.
This section covers the following topics:
See Also:
Oracle Database JPublisher User's Guide.You can direct JPublisher to create custom object classes that implement either theSQLData interface or theORAData interface, according to how you set the JPublisher type mappings.
If you use theORAData interface, thenJPublisher will also create a custom reference class to map to object references for the Oracle object type. If you use theSQLData interface, then JPublisher will not produce a custom reference class. You would use standardjava.sql.Ref instances instead.
If you want additional functionality, you can subclass the custom object class and add features as desired. When you run JPublisher, there is a command-line option for specifying both a generated class name and the name of the subclass you will implement. For the SQL-Java mapping to work properly, JPublisher must know the subclass name, which is incorporated into some of the functionality of the generated class.
Note:
Hand-editing the JPublisher-generated class, instead of subclassing it, is not recommended. If you hand-edit this class and later have to re-run JPublisher for some reason, you would have to re-implement your changes.JPublisher offers various choices for how to map user-defined types and their attribute types between SQL and Java. This section lists categories of SQL types and the mapping options available for each category.
JPublisher categorizes SQL types into the following groups, with corresponding JPublisher options as specifies:
User-defined types (UDT)
This includes Oracle objects, references, and collections. You use the JPublisher-usertypes option to specify the type-mapping implementation for UDTs, either a standardSQLData implementation or an Oracle-specificORAData implementation.
Numeric types
This includes anything stored in the database as theNUMBER SQL type. You use the JPublisher-numbertypes option to specify type-mapping for numeric types.
Large object (LOB) types
This includes the SQL types,BLOB andCLOB. You use the JPublisher-lobtypes option to specify type-mapping for LOB types.
Built-in types
This includes anything stored in the database as a SQL type not covered by the preceding categories. For example,CHAR,VARCHAR2,LONG, andRAW. You use the JPublisher-builtintypes option to specify type-mapping for built-in types.
JPublisher defines the following type-mapping modes, two of which apply to numeric types only:
Uses standard default mappings between SQL types and Java native types. For a custom object class, uses aSQLData implementation.
Oracle mapping (settingoracle)
Uses correspondingoracle.sql types to map to SQL types. For a custom object, reference, or collection class, uses aORAData implementation.
Object-JDBC mapping (settingobjectjdbc)
Is an extension of the JDBC mapping. Where relevant, object-JDBC mapping uses numeric object types from the standardjava.lang package, such asjava.lang.Integer,Float, andDouble, instead of primitive Java types, such asint,float, anddouble. Thejava.lang types are nullable, while the primitive types are not.
BigDecimal mapping (settingbigdecimal)
Usesjava.math.BigDecimal to map to all numeric attributes. This is appropriate if you are dealing with large numbers but do not want to map to theoracle.sql.NUMBER class.
Note:
UsingBigDecimal mapping can significantly degrade performance.Mapping the Oracle object type to Java
Use the JPublisher-usertypes option to determine how JPublisher will implement the custom Java class that corresponds to a Oracle object type:
A setting of-usertypes=oracle, which is the default setting, instructs JPublisher to create aORAData implementation for the custom object class. This will also result in JPublisher producing aORAData implementation for the corresponding custom reference class.
A setting of-usertypes=jdbc instructs JPublisher to create aSQLData implementation for the custom object class. No custom reference class can be created. You must usejava.sql.Ref ororacle.sql.REF for the reference type.
Note:
You can also use JPublisher with a-usertypes=oracle setting in creatingORAData implementations to map SQL collection types.The-usertypes=jdbc setting is not valid for mapping SQL collection types. TheSQLData interface is intended only for mapping Oracle object types.
Mapping Attribute Types to Java
If you do not specify mappings for the attribute types of the Oracle object type, then JPublisher uses the following defaults:
For numeric attribute types, the default mapping is object-JDBC.
For LOB attribute types, the default mapping is Oracle.
For built-in type attribute types, the default mapping is JDBC.
If you want alternate mappings, then use the-numbertypes,-lobtypes, and-builtintypes options, as necessary, depending on the attribute types you have and the mappings you desire.
If an attribute type is itself an Oracle object type, then it will be mapped according to the-usertypes setting.
Important:
Be aware that if you specify anSQLData implementation for the custom object class and want the code to be portable, then you must be sure to use portable mappings for the attribute types. The defaults for numeric types and built-in types are portable, but for LOB types you must specify-lobtypes=jdbc.Summary of SQL Type Categories and Mapping Settings
Table 13-1 summarizes JPublisher categories for SQL types, the mapping settings relevant for each category, and the default settings.
Table 13-1 JPublisher SQL Type Categories, Supported Settings, and Defaults
| SQL Type Category | JPublisher Mapping Option | Mapping Settings | Default |
|---|---|---|---|
UDT types | -usertypes | oracle, jdbc | oracle |
numeric types | -numbertypes | oracle, jdbc, objectjdbc, bigdecimal | objectjdbc |
LOB types | -lobtypes | oracle, jdbc | oracle |
built-in types | -builtintypes | oracle, jdbc | jdbc |
Oracle JDBC includes functionality to retrieve information about a structured object type regarding its attribute names and types. This is similar conceptually to retrieving information from a result set about its column names and types, and in fact uses an almost identical method.
This section covers the following topics:
Theoracle.sql.StructDescriptor class includes functionality to retrieve metadata about a structured object type. TheStructDescriptor class has agetMetaData method with the same functionality as the standardgetMetaData method available in result set objects. It returns a set of attribute information, such as attribute names and types. Call this method on aStructDescriptor object to get metadata about the Oracle object type that theStructDescriptor object describes.
The signature of theStructDescriptor classgetMetaData method is the same as the signature specified forgetMetaData in the standardResultSet interface. The signature is as follows:
ResultSetMetaData getMetaData() throws SQLException
However, this method actually returns an instance oforacle.jdbc.StructMetaData, a class that supports structured object metadata in the same way that the standardjava.sql.ResultSetMetaData interface specifies support for result set metadata.
The following method is also supported byStructMetaData:
String getOracleColumnClassName(int column) throws SQLException
This method returns the fully qualified name of theoracle.sql.Datum subclass whose instances are manufactured if theOracleResultSet classgetOracleObject method is called to retrieve the value of the specified attribute. For example,oracle.sql.NUMBER.
To use thegetOracleColumnClassName method, you must cast theResultSetMetaData object, which that was returned by thegetMetaData method, toStructMetaData.
Note:
In all the preceding method signatures,column is something of a misnomer. Where you specify a value of 4 forcolumn, you really refer to the fourth attribute of the object.Use the following steps to obtain metadata about a structured object type:
Create or acquire aStructDescriptor instance that describes the relevant structured object type.
Call thegetMetaData method on theStructDescriptor instance.
Call the metadata getter methods,getColumnName,getColumnType, andgetColumnTypeName, as desired.
The following method shows how to retrieve information about the attributes of a structured object type. This includes the initial step of creating aStructDescriptor instance.
// // Print out the ADT's attribute names and types // void getAttributeInfo (Connection conn, String type_name) throws SQLException { // get the type descriptor StructDescriptor desc = StructDescriptor.createDescriptor (type_name, conn); // get type metadata ResultSetMetaData md = desc.getMetaData (); // get # of attrs of this type int numAttrs = desc.length (); // temporary buffers String attr_name; int attr_type; String attr_typeName; System.out.println ("Attributes of "+type_name+" :"); for (int i=0; i<numAttrs; i++) { attr_name = md.getColumnName (i+1); attr_type = md.getColumnType (i+1); System.out.println (" index"+(i+1)+" name="+attr_name+" type="+attr_type); // drill down nested object if (attrType == OracleTypes.STRUCT) { attr_typeName = md.getColumnTypeName (i+1); // recursive calls to print out nested object metadata getAttributeInfo (conn, attr_typeName); } } }