| Oracle8i JDBC Developer's Guide and Reference Release 2 (8.1.6) A81354-01 |
|
If you want to create custom object classes for your Oracle objects, then you must define entries in the type 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 provide get and set methods corresponding to the Oracle object's attributes, although this is not necessary. To create and populate the custom classes and provide these read/write capabilities, you can choose between these two interfaces:
SQLData interface
CustomDatum and CustomDatumFactory interfaces provided by Oracle
The custom object class you create must implement one of these interfaces. The CustomDatum interface can also be used to implement the custom reference class corresponding to the custom object class. If you are using the SQLData interface, however, you can only use weak reference types in Java (java.sql.Ref or oracle.sql.REF). The SQLData 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 type CHAR) and EmpNum (employee number, which is type NUMBER). You use the type map to specify that the EMPLOYEE object should map to a custom object class that you call JEmployee. You can implement either the SQLData or CustomDatum interface in the JEmployee class.
You can create custom object classes yourself, but the most convenient way to create them is to employ the Oracle JPublisher utility to create them for you. As of release 8.1.6, JPublisher supports the standard SQLData interface as well as the Oracle-specific CustomDatum interface, and is able to generate classes that implement either one. See "Using JPublisher to Create Custom Object Classes" for more information.
The following section compares CustomDatum and SQLData functionality.
In deciding which of these two interface implementations to use, consider the following:
Advantages of CustomDatum:
CustomDatum from an oracle.sql.STRUCT. This is more efficient because it avoids unnecessary conversions to native Java types.
Datum object (which is in oracle.sql format) from the CustomDatum object, using the toDatum() method.
CustomDatum works directly with Datum types, which is the internal format used by the driver to hold Oracle objects.
Advantages of SQLData:
The SQLData interface is for mapping SQL objects only. The CustomDatum 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 a CustomDatum object from any datatype found in an Oracle database. This could be useful, for example, for serializing RAW data in Java.
If you use the SQLData interface in a custom object class, then you must create type map entries that specify the custom object class to use in mapping the SQL 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. The ResultSet interface getObject() method has a signature that lets you specify a type map:
rs.getObject(int columnIndex);
or:
rs.getObject(int columnIndex, Map map);
For a description of how to create these custom object classes with SQLData, see "Creating and Using Custom Object Classes for Oracle Objects".
When using a SQLData implementation, if you do not include a type map entry, then the object will map to the oracle.sql.STRUCT class by default. (CustomDatum implementations, by contrast, have their own mapping functionality so that a type map entry is not required. When using a CustomDatum implementation, use the Oracle getCustomDatum() method instead of the standard getObject() 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 SQL object type. When you write data to an Oracle object, the JDBC driver gets the SQL type name from the Java class by calling the getSQLTypeName() method of the SQLData 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 (instances of the oracle.sql.* classes) to store attributes.
When using a SQLData implementation, the JDBC applications programmer is responsible for providing a type map, which must be an instance of a class as follows:
or:
java.util.Dictionary class (or an instance of the Dictionary class itself)
You have the option of creating your own class to accomplish this, but under either JDK 1.2.x or JDK 1.1.x, the standard class java.util.Hashtable meets the requirement.
Hashtable and other classes used for type maps implement a put() 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 standard java.sql.Connection interface and the Oracle-specific oracle.jdbc.driver.OracleConnection class include a getTypeMap() method. Under JDK 1.2.x, both return a Map object; under JDK 1.1.x, both return a Dictionary object.
The remainder of this section covers the following topics:
When a connection instance is first established, the default type map is empty. You must populate it to use any SQL-Java mapping functionality.
Follow these general steps to add entries to an existing type map.
getTypeMap() method of your OracleConnection object to return the connection's type map object. The getTypeMap() method returns a java.util.Map object (or java.util.Dictionary under JDK 1.1.x). For example, presuming an OracleConnection instance oraconn:
java.util.Map myMap = oraconn.getTypeMap();
put() method to add map entries. The put() 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);
The sqlTypeName is a string that represents the fully qualified name of the SQL type in the database. The classObject is the Java class object to which you want to map the SQL type. Get the class object with the Class.forName() method, as follows:
myMap.put(sqlTypeName, Class.forName(className));
For example, if you have a PERSON SQL datatype defined in the CORPORATE database schema, then map it to a Person Java class defined as Person with this statement:
myMap.put("CORPORATE.PERSON", Class.forName("Person"));
The map has an entry that maps the PERSON SQL datatype in the CORPORATE database to the Person Java class.
Follow these general steps to create a new type map. This example uses an instance of java.util.Hashtable, which extends java.util.Dictionary and, under JDK 1.2.x, also implements java.util.Map.
Hashtable newMap = new Hashtable();
put() method of the type map object to add entries to the map. For more information on the put() method, see Step 2 under "Adding Entries to an Existing Type Map". For example, if you have an EMPLOYEE SQL type defined in the CORPORATE database, then you can map it to an Employee class object defined by Employee.java, with this statement:
newMap.put("CORPORATE.EMPLOYEE", class.forName("Employee"));
OracleConnection object's setTypeMap() method to overwrite the connection's existing type map. For example:
oraconn.setTypeMap(newMap);
In this example, setTypeMap() overwrites the oraconn connection's original map with newMap.
If you do not provide a type map with an appropriate entry when using a getObject() call, then the JDBC driver will materialize an Oracle object as an instance of the oracle.sql.STRUCT class. If the Oracle object type contains embedded objects, and they are not present in the type map, the driver will materialize the embedded objects as instances of oracle.sql.STRUCT as well. If the embedded objects are present in the type map, a call to the getAttributes() method will return embedded objects as instances of the specified Java classes from the type map.
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 the SQLData interface. Note that if you use this interface, you must supply a type 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.
The SQLData interface defines methods that translate between SQL and Java for Oracle database objects. Standard JDBC provides a SQLData interface and companion SQLInput and SQLOutput interfaces in the java.sql package (oracle.jdbc2 package under JDK 1.1.x).
If you create a custom object class that implements SQLData, then you must provide a readSQL() method and a writeSQL() method, as specified by the SQLData interface.
The JDBC driver calls your readSQL() 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 an OracleResultSet object getObject() call.
Similarly, the JDBC driver calls your writeSQL() 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 an OraclePreparedStatement object setObject() call.
The JDBC driver includes classes that implement the SQLInput and SQLOutput interfaces. It is not necessary to implement the SQLOutput or SQLInput objects--the JDBC drivers will do this for you.
The SQLInput implementation is an input stream class, an instance of which must be passed in to the readSQL() method. SQLInput includes a readXXX() method for every possible Java type that attributes of an Oracle object might be converted to, such as readObject(), readInt(), readLong(), readFloat(), readBlob(), and so on. Each readXXX() method converts SQL data to Java data and returns it into an output parameter of the corresponding Java type. For example, readInt() returns an integer.
The SQLOutput implementation is an output stream class, an instance of which must be passed in to the writeSQL() method. SQLOutput includes a writeXXX() method for each of these Java types. Each writeXXX() method converts Java data to SQL data, taking as input a parameter of the relevant Java type. For example, writeString() would take as input a string attribute from your Java class.
When you create a custom object class that implements SQLData, you must implement the readSQL() and writeSQL() methods, as described here.
You must implement readSQL() as follows:
public void readSQL(SQLInputstream, Stringsql_type_name) throws SQLException
readSQL() method takes as input a SQLInput stream and a string that indicates the SQL type name of the data (in other words, the name of the Oracle object type, such as EMPLOYEE).
When your Java application calls getObject(), the JDBC driver creates a SQLInput 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 calls readSQL(), it passes in these parameters.
readSQL() must call the appropriate readXXX() method of the SQLInput stream that is passed in.
For example, if you are reading EMPLOYEE objects that have an employee name as a CHAR variable and an employee number as a NUMBER variable, you must have a readString() call and a readInt() call in your readSQL() method. JDBC calls these methods according to the order in which the attributes appear in the SQL definition of the Oracle object type.
readSQL() method takes the data that the readXXX() methods read and convert, and assigns them to the appropriate fields or elements of a custom object class instance.
You must implement writeSQL() as follows:
public void writeSQL(SQLOutput stream) throws SQLException
writeSQL() method takes as input a SQLOutput stream.
When your Java application calls setObject(), the JDBC driver creates a SQLOutput stream object and populates it with data from a custom object class instance. When the driver calls writeSQL(), it passes in this stream parameter.
writeSQL() must call the appropriate writeXXX() method of the SQLOutput stream that is passed in.
For example, if you are writing to EMPLOYEE objects that have an employee name as a CHAR variable and an employee number as a NUMBER variable, then you must have a writeString() call and a writeInt() call in your writeSQL() method. These methods must be called according to the order in which attributes appear in the SQL definition of the Oracle object type.
writeSQL() method then writes the data converted by the writeXXX() methods to the SQLOutput stream so that it can be written to the database once you execute the prepared statement.
"SQLData Implementation--SQLDataExample.java" contains a sample implementation of the SQLData interface for a given SQL object definition.
This section describes how to read data from an Oracle object or write data to an Oracle object if your corresponding Java class implements SQLData.
This section summarizes the steps to read data from an Oracle object into your Java application when you choose the SQLData 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 object stmt.
ResultSet rs = stmt.executeQuery("SELECT emp_col FROM personnel");
The PERSONNEL table contains one column, EMP_COL, of SQL type EMP_OBJECT. This SQL type is defined in the type map to map to the Java class Employee.
getObject() method of your result set to populate an instance of your custom object class with data from one row of the result set. The getObject() method returns the user-defined SQLData object because the type map contains an entry for Employee.
if (rs.next()) Employee emp = (Employee)rs.getObject(1);
Note that if the type map did not have an entry for the object, then getObject() would return an oracle.sql.STRUCT object. Cast the output to type STRUCT, because the getObject() method signature returns the generic java.lang.Object type.
if (rs.next()) STRUCT empstruct = (STRUCT)rs.getObject(1);
The getObject() call triggers readSQL() and readXXX() calls from the SQLData interface, as described above.
get methods in your custom object class, then use them to read data from your object attributes. For example, if EMPLOYEE has an EmpName (employee name) of type CHAR, and an EmpNum (employee number) of type NUMBER, then provide a getEmpName() method that returns a Java String and a getEmpNum() method that returns an integer (int). Then invoke them in your Java application, as follows:
String empname = emp.getEmpName(); int empnumber = emp.getEmpNum();
Suppose you have an OracleCallableStatement ocs that calls a PL/SQL function GETEMPLOYEE(). The program passes an employee number (empnumber) to the function; the function returns the corresponding Employee object.
OracleCallableStatement to call the GETEMPLOYEE() function.
OracleCallableStatement ocs = (OracleCallableStatement)conn.prepareCall("{ ? = call GETEMPLOYEE(?) }");
empnumber as the input parameter to GETEMPLOYEE(). Register the SQLData object as the OUT parameter, with typecode OracleTypes.STRUCT. Then, execute the statement.
ocs.setInt(2, empnumber); ocs.registerOutParameter(1, OracleTypes.STRUCT, "EMP_OBJECT"); ocs.execute();
getObject() method to retrieve the employee object. The following code assumes that there is a type map entry to map the Oracle object to Java type Employee:
Employee emp = (Employee)ocs.getObject(1);
If there is no type map entry, then getObject() would return an oracle.sql.STRUCT object. Cast the output to type STRUCT, because the getObject() method signature returns the generic java.lang.Object type:
STRUCT emp = (STRUCT)ocs.getObject(1);
Suppose you have a PL/SQL function addEmployee(?) that takes an Employee object as an IN parameter and adds it to the PERSONNEL table. In this example, emp is a valid Employee object.
OracleCallableStatement to call the addEmployee(?) function.
OracleCallableStatement ocs = (OracleCallableStatement) conn.prepareCall("{ call addEmployee(?) }");
setObject() to pass the emp object as an IN parameter to the callable statement. Then, execute the statement.
ocs.setObject(1, emp); ocs.execute();
This section describes the steps in writing data to an Oracle object from your Java application when you choose the SQLData 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.
set methods in your custom object class, then use them to write data from Java variables in your application to attributes of your Java datatype object.
emp.setEmpName(empname); emp.setEmpNum(empnumber);
This statement uses the emp object and the empname and empnumber variables assigned in "Reading SQLData Objects from a Result Set".
PreparedStatement pstmt = conn.prepareStatement ("INSERT INTO PERSONNEL VALUES (?)");
This assumes conn is your connection object.
setObject() method of the prepared statement to bind your Java datatype object to the prepared statement.
pstmt.setObject(1, emp);
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 the oracle.sql.CustomDatum and oracle.sql.CustomDatumFactory interfaces (or you can implement CustomDatumFactory in a separate class). The CustomDatum and CustomDatumFactory 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 the |
The CustomDatum interface has these advantages:
CustomDatum uses oracle.sql.Datum types directly.
CustomDatum works directly with Datum types, the internal format the driver uses to hold Oracle objects.
The CustomDatum and CustomDatumFactory interfaces do the following:
toDatum() method of the CustomDatum class transforms the data into an oracle.sql.* representation.
CustomDatumFactory specifies a create() method equivalent to a constructor for your custom object class. It creates and returns a CustomDatum instance. The JDBC driver uses the create() method to return an instance of the custom object class to your Java application or applet. It takes as input an oracle.sql.Datum object and an integer indicating the corresponding SQL typecode as specified in the OracleTypes class.
CustomDatum and CustomDatumFactory have the following definitions:
public interface CustomDatum { Datum toDatum (OracleConnectionconn) throws SQLException; } public interface CustomDatumFactory { CustomDatum create (Datumd, intsql_Type_Code) throws SQLException; }
Where conn represents the Connection object, d represents an object of type oracle.sql.Datum, and sql_Type_Code represents the SQL typecode (from the standard Types or OracleTypes class) of the Datum object.
The JDBC drivers provide the following methods to retrieve and insert object data as instances of CustomDatum.
To retrieve object data:
OracleResultSet class getCustomDatum() method (assume an OracleResultSet object ors):
ors.getCustomDatum (intcol_index, CustomDatumFactoryfactory);
This method takes as input the column index of the data in your result set, and a CustomDatumFactory instance. For example, you can implement a getFactory() method in your custom object class to produce the CustomDatumFactory instance to input to getCustomDatum(). The type map is not required when using Java classes that implement CustomDatum.
or:
getObject(index, map) method specified by the ResultSet interface to retrieve data as instances of CustomDatum. 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.
To insert object data:
OraclePreparedStatement class setCustomDatum() method (assume an OraclePreparedStatement object ops):
ops.setCustomDatum (intbind_index, CustomDatumcustom_obj);
This method takes as input the parameter index of the bind variable and the name of the object containing the variable.
or:
setObject() method specified by the PreparedStatement interface. You can also use this method, in its different forms, to insert CustomDatum instances without requiring a type map.
The following sections describe the getCustomDatum() and setCustomDatum() methods.
To continue the example of an Oracle object EMPLOYEE, you might have something like the following in your Java application:
CustomDatum datum = ors.getCustomDatum(1, Employee.getFactory());
In this example, ors is an Oracle result set, getCustomDatum() is a method in the OracleResultSet class used to retrieve a CustomDatum object, and the EMPLOYEE is in column 1 of the result set. The static Employee.getFactory() method will return a CustomDatumFactory to the JDBC driver. The JDBC driver will call create() from this object, returning to your Java application an instance of the Employee class populated with data from the result set.
"CustomDatum Implementation--CustomDatumExample.java" contains an example implementation of the CustomDatum interface for a given SQL object definition.
This section describes how to read data from an Oracle object or write data to an Oracle object if your corresponding Java class implements CustomDatum.
This section summarizes the steps in reading data from an Oracle object into your Java application. These steps apply whether you implement CustomDatum 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 object stmt.
OracleResultSet ors = (OracleResultSet)stmt.executeQuery ("SELECT Emp_col FROM PERSONNEL");
Where PERSONNEL is a one-column table. The column name is Emp_col of type Employee_object.
getCustomDatum() method of your Oracle result set to populate an instance of your custom object class with data from one row of the result set. The getCustomDatum() method returns an oracle.sql.CustomDatum object, which you can cast to your specific custom object class.
if (ors.next()) Employee emp = (Employee)ors.getCustomDatum(1, Employee.getFactory());
or:
if (ors.next()) CustomDatum datum = ors.getCustomDatum(1, Employee.getFactory());
This example assumes that Employee is the name of your custom object class and ors is the name of your OracleResultSet object.
In case you do not want to use getCustomDatum(), the JDBC drivers let you use the getObject() method of a standard JDBC ResultSet to retrieve CustomDatum 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 is EMPLOYEE, then the corresponding Java class is Employee, which will implement CustomDatum. The corresponding Factory class is EmployeeFactory, which will implement CustomDatumFactory.
Use this statement to declare the EmployeeFactory entry for your type map:
map.put ("EMPLOYEE", Class.forName ("EmployeeFactory"));
Then use the form of getObject() where you specify the map object:
Employee emp = (Employee) rs.getObject (1, map);
If the connection's default type map 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 of getObject():
Employee emp = (Employee) rs.getObject (1);
get methods in your custom object class, use them to read data from your object attributes into Java variables in your application. For example, if EMPLOYEE has EmpName of type CHAR and EmpNum (employee number) of type NUMBER, provide a getEmpName() method that returns a Java string and a getEmpNum() method that returns an integer. Then invoke them in your Java application as follows:
String empname = emp.getEmpName(); int empnumber = emp.getEmpNum();
This section summarizes the steps in writing data to an Oracle object from your Java application. These steps apply whether you implement CustomDatum manually or use JPublisher to produce your custom object classes.
These steps assume you have already defined the Oracle object type and created the corresponding custom object class (or had JPublisher create it for you).
set methods in your custom object class, then use them to write data from Java variables in your application to attributes of your Java datatype object.
emp.setEmpName(empname); emp.setEmpNum(empnumber);
This statement uses the emp object and the empname and empnumber variables defined in "Reading Data from an Oracle Object Using a CustomDatum Implementation".
OraclePreparedStatement opstmt = conn.prepareStatement ("UPDATE PERSONNEL SET Employee = ? WHERE Employee.EmpNum = 28959);
This assumes conn is your Connection object.
setCustomDatum() method of the Oracle prepared statement to bind your Java datatype object to the prepared statement.
opstmt.setCustomDatum(1, emp);
The setCustomDatum() method calls the toDatum() method of the custom object class instance to retrieve an oracle.sql.STRUCT object that can be written to the database.
In this step you could also use the setObject() method to bind the Java datatype. For example:
opstmt.setObject(1,emp);
The CustomDatum interface offers far more flexibility than the SQLData interface. The SQLData interface is designed to let you customize the mapping of only SQL object types (that is, Oracle8 object types) to Java types of your choice. Implementing the SQLData 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.
The CustomDatum interface goes beyond supporting the customization of SQL object types to Java types. It lets you provide a mapping between Java object types and any SQL type supported by the oracle.sql package.
It might be useful to provide custom Java classes to wrap oracle.sql.* types and perhaps implement customized conversions or functionality as well. The following are some possible scenarios:
DATE field to java.util.Date format)
RAW fields, for example
For example, use CustomDatum to store instances of Java objects that do not correspond to a particular SQL Oracle8 object type in the database in columns of SQL type RAW. The create() method in CustomDatumFactory would have to implement a conversion from an object of type oracle.sql.RAW to the desired Java object. The toDatum() method in CustomDatum would have to implement a conversion from the Java object to an oracle.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 an oracle.sql.RAW and calls the CustomDatumFactory's create() method to convert the oracle.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 type RAW to store it. The driver transparently calls the CustomDatum.toDatum() method to convert the Java object to an oracle.sql.RAW object. This object is then stored in a column of type RAW in the database.
Support for the CustomDatum interfaces is also highly efficient because the conversions are designed to work using oracle.sql.* formats, which happen to be the internal formats used by the JDBC drivers. Moreover, the type map, which is necessary for the SQLData interface, is not required when using Java classes that implement CustomDatum. For more information on why classes that implement CustomDatum do not need a type map, see "Understanding the CustomDatum Interface".