As a reminder, the following is the DDL for the PERSON and PERSON_IDENTIFIER tables introduced in Chapter 8: create table PERSON person_id number not null, last_name varchar230 not null
Trang 1The CallableStatement object used in this chapter's examples is an interface The full interface name is java.sql.CallableStatement This interface is implemented by
oracle.jdbc.driver.OracleCallableStatement, which extends
oracle.jdbc.driver.OraclePreparedStatement This means that all the proprietary methods that are available in OracleStatement and OraclePreparedStatement are also available in Oracle-CallableStatement The following is a list of the proprietary methods available in OracleCallableStatement, all of which can throw a SQLException:
clearParameters( )
ARRAY getARRAY(int parameterIndex)
InputStream getAsciiStream(int parameterIndex)
BFILE getBFILE(int parameterIndex)
InputStream getBinaryStream(int parameterIndex)
BLOB getBLOB(int parameterIndex)
CHAR getCHAR(int parameterIndex)
CLOB getCLOB(int parameterIndex)
ResultSet getCursor(int parameterIndex)
Object getCustomDatum(int parameterIndex, CustomDatumFactory factory) DATE getDATE(int parameterIndex)
NUMBER getNUMBER(int parameterIndex)
Datum getOracleObject(int parameterIndex)
RAW getRAW(int parameterIndex)
REF getREF(int parameterIndex)
ROWID getROWID(int parameterIndex)
STRUCT getSTRUCT(int parameterIndex)
InputStream getUnicodeStream(int parameterIndex)
registerOutParameter(
int paramIndex, int sqlType, int scale, int maxLeng th)
Part IV: Object-Relational SQL
We've now covered everything there is to know about using JDBC with relational
SQL Our second and third options for how to use the database involve
object-relational SQL Object-object-relational SQL is the application of SQL to Oracle
database objects and forms the basis of the object portion of Oracle's
object-relational features In Part IV we'll cover the use of the Statement, ResultSet, PreparedStatement, and CallableStatement Java objects with Oracle
database objects So let's start our journey into object-relational SQL with an
overview of Oracle's object-relational technology
Chapter 14 An Object-Relational SQL Example
Oracle documentation refers to Oracle8i's ability to store user-defined data types in the database
as object-relational SQL I think this is quite appropriate With Oracle you have the choice of creating three different kinds of tables:
• A traditional relational table using native SQL data types such as VARCHAR2, DATE, and NUMBER
• A relational table with object columns This type of table is a hybrid, using both native SQL data types and user-defined data types
• An object table, which is defined solely based on a user-defined data type
The best part of this architecture is that it's flexible enough to facilitate both relational SQL and object-oriented development tools by providing both a relational view and an object view of the
Trang 2same database You can create object views against relational tables to create an object face to
a relational database or create object tables and access the column attributes as though they are part of relational tables by using the TABLE operator You can have your cake and eat it too!
In this chapter, we'll discuss the use of JDBC with database objects We'll start by examining object analysis and design but not from the traditional point of view Instead, we'll look at how we can transform our relational model from that shown in Chapter 8 into an object model We'll then look at how we can transform our relational database into an object database by implementing object views on our relational data We'll finish up by creating object tables to replace our
relational tables, and we'll use those object tables for examples in the chapters that follow
14.1 From Relational Tables to Object Views
In Chapter 8, we discussed creating a demographic database for HR data By the end of the chapter we had gone through several evolutions with our analysis and had presented 11 entities
We then created the DDL for five of the entities:
PERSON
PERSON_IDENTIFIER
PERSON_IDENTIFIER_TYPE
PERSON_LOCATION
LOCATION
We can now take these five entities and create object views to present our relational database as
an object-relational database Seeing that we are now looking at the data from an object
perspective, we have two ways in which to implement an object solution We can create object views on top of our relational tables or transform our entities into object tables
14.1.1 Transforming Entities into Objects
Recalling all the entities we identified in Chapter 8, we can first create object tables for
EMPLOYMENT STATUS, LOCATION, ORGANIZATION, and POSITION Then we can create another object table for PERSON, folding into it the following intersection entities as nested tables
or varying arrays:
PERSON_EMPLOYMENT_STATUS
PERSON_LOCATION
PERSON_ORGANIZATION
PERSON_POSITION
PERSON_IDENTIFIER
Nested tables and varying arrays are both referred to as collections Adding the five collections
listed here to a person object table would create a rather large person object that would need
to retrieve all related person data at one time This may be desirable for you, the programmer, but
it will lead to poor application performance It is much more advisable to fold only the
PERSON_IDENTIFIER entity into the person entity as a collection, creating a new PERSON object table, because it is common to query for persons by their identifiers This leaves the intersection entities as separate object tables with the end result being that we are left with four objects: PERSON, PERSON_IDENTIFIER_TYPE, PERSON_LOCATION, and LOCATION Our next decision is whether to use references, or primary and foreign keys to enforce referential integrity Since there are some negative performance implications associated with using
references in object views, we'll use primary and foreign keys With these decisions behind us, let's move forward by creating object views for our four new objects
14.1.2 Creating Object Views
To create an object view, follow these steps:
Trang 31 Define an object type in which its attributes correspond to the column types of the
associated relational table
2 Identify a unique value from the underlying relational table to act as a reference value for the rows
3 Create an object view to extract the data
4 Create INSTEAD OF triggers to make the view updateable
We'll do this for the PERSON and PERSON_IDENTIFIER tables As a reminder, the following is the DDL for the PERSON and PERSON_IDENTIFIER tables introduced in Chapter 8:
create table PERSON (
person_id number not null,
last_name varchar2(30) not null,
first_name varchar2(30) not null,
middle_name varchar2(30),
birth_date date no t null,
mothers_maiden_name varchar2(30) not null )
create table PERSON_IDENTIFIER (
person_id number not null,
id varchar2(30) not null,
id_type varchar2(30) not null )
14.1.2.1 Creating user-defined data types
Step 1 is to create a user-defined data type to represent the data from these two tables as an object We'll start with the PERSON_IDENTIFIER table Here's the DDL to create a
corresponding user-defined data type:
create type PERSON_IDENTIFIER_typ as object (
id varchar2(30),
id_type varchar2(30) )
Notice that we don't have the person_id in the type definition That's because the person_id will be implicit, because the identifiers are stored in the form of a nested table within the enclosing person object Next, we need to create a nested table type definition to transform the
PERSON_IDENTIFIER table into a collection for the person object Here's the DDL to do that: create type PERSON_IDENTIFIER_tab as
table of PERSON_IDENTIFIER_typ
Now that we have a collection type for the PERSON_IDENTIFIER table, we can define the person type:
create type PERSON_typ as object (
person_id number,
last_name varchar2(30),
first_name varchar2(30),
middle_name varchar2(30),
birth_date date,
mothers_maiden_name varchar2(30),
identifiers person_identifier_tab,
map member function get_map return varchar2,
member function get_age return number,
member function get_age_on( aid_date in date ) return number,
static function get_id return number );
/
Trang 4create type body PERSON_typ as
map member function get_map return varchar2 is
begin
return rpad( last_name, 30 )||
rpad( first_name, 30 )||
rpad( middle_name, 30 )||
rpad( mothers_maiden_name,30 )||
to_char( birth_date, 'YYYYMMDDHH24MISS' );
end get_map;
member function get_age return number is
begin
return trunc( months_between( SYSDATE, birth_date ) / 12 );
end get_age;
member function get_age_on( aid_date in date ) return number is
begin
return trunc( months_between( aid_date, birth_date ) / 12 );
end get_age_on;
static function get_id return number is
n_person_id number := 0;
begin
select person_seq.nextval into n_person_id from dual;
return n_person_id;
end get_id;
end;
/
We've added one static and three member methods to person_typ I'll use these in the coming chapters to demonstrate how to call a database object's methods
14.1.2.2 Selecting a reference value
Now that we have defined types for the PERSON and PERSON_IDENTIFIER tables, we need to decide which value to use for an object reference An object reference acts as a unique identifier for an object, just as a primary key acts as a unique identifier for a row in a relational table Since we're creating object views, and the column person_id is common to both tables, we'll use it for the reference value
If we were creating an object table, as we will do later in this chapter, we could choose between
using a unique value in the attribute of a user-defined data type or a reference A reference is a
database-generated global unique identifier (GUID) My preference, even with object tables, is to use an attribute as a primary key instead of a GUID, because you can create foreign key
constraints between object tables with a primary key
14.1.2.3 Creating an object view
Now that we have all the necessary types defined and have selected a reference value, we can move on to step 3, which is to create a view to extract the data from our two relational tables and cast it to a person type object Here's the object view:
create or replace view person_ov of
person_typ with object identifier( person_id ) as
select person_id,
last_name,
first_name,
Trang 5middle_name,
birth_date,
mothers_maiden_name,
cast(
multiset (
select i.id,
i.id_type
from person_identifier i
where i.person_id = p.person_id ) as
person_identifier_tab ) as
identifiers
from person p
In this object view, we select data from the PERSON table and use the CAST and MULTISET keywords to transform the related values in the PERSON_IDENTIFER table into a
person_identifier_tab object The MULTISET keyword is used because the result of the subquery has multiple rows The CAST keyword takes the values and creates a
person_identifier_typ object for each row, which in turn becomes elements of the
person_identifier_tab object The result of a query against the person_ov object view is a person_typ object for each PERSON row in the database Each person_typ object includes any related person_identifiers
14.1.2.4 Creating INSTEAD OF triggers
At this point, we can retrieve data from the PERSON and PERSON_IDENTIFIER tables in the form of a table of person_ov objects However, if we need to insert, update, or delete objects,
we need to create INSTEAD OF triggers on person_ov INSTEAD OF triggers encapsulate insert, update, and delete logic for a view in the form of PL/SQL or Java code Example 14-1
shows the three INSTEAD OF triggers required for the PERSON table, and Example 14-2
shows the three required triggers for the nested PERSON_IDENTIFIER table All six of these INSTEAD OF triggers are required to make the person_ov object view updateable
The first three triggers, shown in Example 14-1, are PERSON_OV_IOI, PERSON_OV_IOU, and PERSON_OV_IOD These triggers intercept inserts, updates, and deletes against the person_ov object view and propagate them to the PERSON table instead
Example 14-1 person_ov INSTEAD OF triggers for the PERSON table
create or replace trigger person_ov_ioi
instead of insert on person_ov
for each row
declare
t_identifiers person_identifier_tab;
begin
insert into person (
PERSON_ID,
LAST_NAME,
FIRST_NAME,
MIDDLE_NAME,
BIRTH_DATE,
MOTHERS_MAIDEN_NAME ) values ( :new.PERSON_ID,
:new.LAST_NAME,
:new.FIRST_NAME,
:new.MIDDLE_NAME,
Trang 6:new.BIRTH_DATE,
:new.MOTHERS_MAIDEN_NAME ); if :new.identifiers is not null then t_identifiers := :new.identifiers; for i in t_identifiers.first t_identifiers.last loop insert into person_identifier ( PERSON_ID, ID, ID_TYPE ) values ( :new.PERSON_ID, t_identifiers( i ).ID, t_identifiers( i ).ID_TYPE ); end loop; end if; end; / create or replace trigger person_ov_i ou instead of update on person_ov for each row declare t_identifiers person_identifier_tab; begin update person set PERSON_ID = :new.PERSON_ID,
LAST_NAME = :new.LAST_NAME,
FIRST_NAME = :new.FIRST_NAME,
MIDDLE_NAME = :new.MIDDLE_NAME,
BIRTH_DATE = :new.BIRTH_DATE,
MOTHERS_MAIDEN_NAME = :new.MOTHERS_MAIDEN_NAME where PERSON_ID = :old.PE RSON_ID;
delete person_identifier
where PERSON_ID = :old.PERSON_ID;
if :new.identifiers is not null then
t_identifiers := :new.identifiers;
for i in t_identifiers.first t_identifiers.last loop insert into person_identifier (
PERSON_ID,
ID,
ID_TYPE )
values (
:new.PERSON_ID,
t_identifiers( i ).ID,
t_identifiers( i ).ID_TYPE );
end loop;
end if;
end;
/
Trang 7create or replace trigger person_ov_iod
instead of delete on person_ov
for each row
declare
begin
delete person_identifier
where PERSON_ID = :old.PERSON_ID;
delete person
where PERSON_ID = :old.PERSON_ID;
end;
/
The next three triggers, IDENTIFIERS_OF_PERSON_OV_IOI,
IDENTIFIERS_OF_PERSON_OV_IOU, and IDENTIFIERS_OF_PERSON_OV_IOD (shown in
Example 14-2) handle inserts, updates, and deletes against the PERSON_IDENTIFIER table, respectively, which is represented by the nested table identifiers in person_ov These are used only when the identifiers attribute of the person_typ is the only attribute modified by
a SQL statement
Example 14-2 person_ov INSTEAD OF triggers for the PERSON_IDENTIFIER table
create or replace trigger identifiers_of_person_ov_ioi
instead of insert on nested table identifiers of person_ov
for each row
declare
begin
insert into person_identifier (
PERSON_ID,
ID,
ID_TYPE )
values (
:parent.PERSON_ID,
:new.ID,
:new.ID_TYPE );
end;
/
create or replace trigger identifiers_of_person_ov_iou
instead of update on nested table identifiers of person_ov
for each row
declare
begin
update person_identifier
set ID = :new.ID,
ID_TYPE = :new.ID_TYPE
where PERSON_ID = :parent.PERSON_ID
and ID = :old.ID
and ID_TYPE = :old.ID_TYPE;
end;
/
Trang 8create or replace trigger identifiers_of_person_ov_iod
instead of delete on nested table identifiers of person_ov
for each row
declare
begin
delete person_identifier
where PERSON_ID = :parent.PERSON_ID
and ID = :old.ID
and ID_TYPE = :old.ID_TYPE;
end;
/
With our six INSTEAD OF triggers in place, we have a fully updateable object view, named person_ov, for the PERSON and PERSON_IDENTIFIERS table With the person_ov object view, we can update the underlying tables with relational SQL or treat them as an object Using object views, you can migrate a legacy relational database to a relational-object database While creating an object view does not take much effort, writing the INSTEAD OF triggers to make the view updateable takes a great deal of effort The best solution for a new application is to create object tables directly based on our user-defined data types Object tables can be modified using either relational SQL or object-relational SQL
14.2 Object Tables
Now that you've seen the object view solution, let's examine the use of object tables In this section, we'll take the four example entities: person, location, person location, and person identifier type, and create object tables to represent those entities as objects We'll create the user-defined database types, and the object tables to implement them, in order of the
dependence
To begin, we create an object table corresponding to the person identifier type entity First, we need to define a type:
create type PERSON_IDENTIFIER_TYPE_typ as object (
code varchar2(30),
description varchar2(80),
inactive_date date )
/
Now that we have the type definition, we create the person_identifier_type_ot object table using the following DDL:
create table PERSON_IDENTIFIER_TYPE_ot of
PERSON_IDENTIFIER_TYPE_typ
tablespace USERS pctfree 20
storage (initial 100 K next 100 K pctincrease 0)
/
alter table PERSON_IDENTIFIER_TYPE_ot add
constraint PERSON_IDENTIFIER_TYPE_ot_PK
primary key (
code )
using index
tablespace USERS pctfree 20
storage (initial 10 K next 10 K pctincreas e 0)
/
Notice that we also created a primary key constraint on the table using the code attribute
Trang 9Next, we define the location type:
create type LOCATION_typ as object (
location_id number,
parent_location_id number,
code varchar2(30),
name varchar2(80),
start_date date,
end_date date,
map member function get_map return varchar2,
static function get_id return number );
/
create type body LOCATION_typ as
map member function get_map return va rchar2 is
begin
return rpad( code, 30 )||
rpad( name, 80 )||
to_char( start_date, 'YYYYMMDDHH24MISS' );
end get_map;
static function get_id return number is
n_location_id number := 0;
begin
select location_seq.nextval into n_location_id from dual;
return n_location_id;
end get_id;
end;
/
And now that we have the location type, we create the location_ot object table using the following DDL:
create table LOCATION_ot of
LOCATION_typ
tablespace USERS pctfree 20
storage (initial 100 K next 100 K pctincrease 0)
/
alter table LOCATION_ot add
constraint LOCATION_ot_PK
primary key ( location_id )
using index
tablespace USERS pctfree 20
storage (initial 10 K next 10 K pctincrease 0)
/
create unique index LOCATION_ot_UK1
on LOCATION_ot (
code,
name,
start_date )
tablespace USERS pctfree 20
storage (initial 100 K next 100 K pctincrease 0)
/
drop sequence LOCATION_ID
/
create sequence LOCATION_ID
Trang 10start with 1
order
/
This time we created a sequence to provide unique values for the primary key attribute
location_id We also created a unique "external" key against the code, name, and
start_date attributes
Now we need to create the person_ot object table, but to do so, we first need to define not only the person type, but also the person_identifer type and the person_identifier
collection type We can reuse the three types we defined for the person_ov object view, so here's the DDL for the person_ot object table:
create table PERSON_ot of
PERSON_typ
nested table identifiers store as PERSON_IDENTIFIER_ot
tablespace USERS pctfree 20
storage (initial 100 K next 100 K pctincrease 0)
/
alter table PERSON_ot add
constraint PERSON_ot_PK
primary key ( person_id )
using index
tablespace USERS pctfree 20
storage (initial 10 K next 10 K pctincrease 0)
/
alter table PERSON_IDENTIFIER_ot add
constraint PERSON_IDENTIFIER_ot_PK
primary key (
id,
id_type )
using index
tablespace USERS pctfree 20
storage (initial 10 K next 10 K pctincrease 0)
/
create unique index PERSON_ot_UK1
on PERSON_ot (
last_name,
first_name,
birth_date,
mothers_maiden_name )
tablespace USERS pctfree 20
storage (initial 100 K next 100 K pctincrease 0)
/
drop sequence PERSON_ID
/
create sequence PERSON_ID
start with 1
order
/
Here, we not only created a nested table but also created a primary key constraint on it to prevent duplicate values in the collection In addition, we created a unique index, person_ot_uk1, on the attributes of the person_ot to prevent duplicate entries based on real-world values