MokaByte 59 - Gennaio 2002 
foto dell'autore non disponibile
Mapping object-relational
Come mappare un database relazionale
con framework ad oggetti

II parte
di
Carlo
de Rossi
Nello scorso articolo abbiamo visto cosa siano e come possano essere definiti e manipolati gli oggetti SQL3, ovvero tipi di dati definiti dall'utente all'interno del DBMS Oracle ed usati come argomenti per Stored Procedure, sia all'interno di codice PL-SQL sia per comunicare tramite JDBC con applicazioni d'esempio in Java. In queste ultime, avevamo introdotto l'interfaccia SQLData ed una classe astratta d'utilità dal nome Wrapper, che assume le funzioni in Java del corrispettivo oggetto SQL3.

Array d'oggetti
L'uso di semplici oggetti così definiti é sicuramente utile, ma ancora di più lo é la possibilità di usare array d'oggetti. In Oracle esistono due tipi d'array, le "Nested Table" ed i "Varray": i primi sono array d'oggetti la cui dimensione massima non é specificata, mentre i secondi sono array a grandezza massima fissa ("bounded array"), definita all'atto di dichiarazione dell'array.
In quest'articolo ci occuperemo solo dei primi, più adatti al nostro scopo.
La dichiarazione di un array in Oracle avviene in modo seguente:

CREATE OR REPLACE TYPE A_EMPLOYEE AS TABLE OF O_EMPLOYEE;

Non ci si lasci ingannare dall'uso della parola Table: questo comando DDL non crea nessuna tabella, é solo la dichiarazione di una collezione o di un vettore, di lunghezza teoricamente illimitata, composto da oggetti di tipo O_EMPLOYEE.
L'uso di un tal elemento, é abbastanza semplice (si veda a riguardo anche la sezione Resources), ma richiede l'uso di classi proprietarie del fornitore del driver JDBC, nel nostro caso Oracle.
Nel codice fornito, si guardi ad esempio la classe 'SetEmployees.java':

EmployeeWrap[] wraps = new EmployeeWrap[5];
[...]
// registrazione parametri di IN ed OUT
oracle.sql.ArrayDescriptor emps_ard = new oracle.sql.ArrayDescriptor(type, conn);
oracle.sql.ARRAY emps_array = new oracle.sql.ARRAY(emps_ard, conn, wraps);
((oracle.jdbc.driver.OracleCallableStatement)statement).setArray(2, emps_array);

Nelle precedenti righe di codice creato un array della classe EmployeeWrap, e tramite questo un ArrayDescriptor, ovvero una classe d'utilità proprietaria di Oracle. Da questa si costruisce un oggetto ARRAY che é usato come normale Input Parameter di una Stored Procedure, senza avere alcun bisogno di un nuovo Wrapper: si occupa il driver di gestire la mappatura in maniera trasparente al programmatore.
Dall'analisi dello spezzone di codice si può vedere come l'oggetto ARRAY sia costruito a partire da un array di Wrapper, essendo quest'ultima classe un tipo a tutti gli effetti, e da una connessione, in quanto il driver deve poter leggere dal database la definizione dell'oggetto SQL3 corrispondente per preparare un adeguato SQL stream.
Attenzione ad una cosa: il driver riporta un errore se l'oggetto Connection non é implementato con una classe OracleConnection o da essa derivata. Questo può avvenire qualora si usi un ConnectionPool tramite l'interfaccia DataSource (come é corretto fare all'interno di un Application Server): si riesce ad ovviare facilmente all'inconveniente andando ad estendere questa classe (com.beasys.wle.jdbc.driver.Connection in BEA WebLogic Enterprise Server) e riportando l'originale OracleConnection, che essendo un membro protetto della classe summenzionata é accessibile dalla classe che lo estende.
Chiaramente gli Array possono essere anche creati a partire da primitive, come ad esempio i long ed i loro corrispondenti Number nel DB:

CREATE OR REPLACE TYPE A_NUMBER AS TABLE OF NUMBER(7);

In modo del tutto analogo a quanto fatto precedentemente, possiamo usare l'array di number come un normale tipo di dato. Nella letteratura un tipo strutturato (ovvero un oggetto) che abbia solo un attributo basato su di un tipo di dato primitivito, é indicato come "Distinct Data Type".
Sempre dall'esempio precedentemente citato:

statement.registerOutParameter(1,
    oracle.jdbc.driver.OracleTypes.ARRAY, "A_NUMBER");
statement.execute();

// legge la Primary Key assegnata all'oggetto
ResultSet rs = statement.getArray(1).getResultSet();
while (rs.next())
   System.out.println("PKEY = " + rs.getLong(1));

Riguardo all'uso di Collections in PL/SQL, consiglio di guardare la documentazione di Oracle9i, citata nel paragrafo Resources. Analizzando brevemente la Stored Procedure setEmployee

PKEYS A_NUMBER := A_NUMBER();
[...]
FOR i IN 1..EMPLOYEES.COUNT LOOP
PKEYS.extend();
PKEYS(PKEYS.COUNT):= SET_EMPLOYEE(EMPLOYEES(i));

Qui vediamo come gli array siano riferiti per indice, ed hanno metodi per essere manipolati.
Per maggiori informazioni si veda la documentazione di Oracle riportata nel paragrafo Resources a fine articolo.

 

 

Oggetti Composti
Il naturale passo successivo dopo aver visto oggetti semplici, ovvero i cui attributi sono delle primitive, e gli array d'oggetti é quello di analizzare un oggetto complesso che contenga anche attributi che non siano delle semplici primitive. Introduciamo quindi la definizione di un oggetto di tipo Department che si trova in relazione '1 a molti' con l'oggetto Employee.
La dichiarazione del corrispondente oggetto SQL3 é la seguente:

CREATE OR REPLACE TYPE O_DEPARTMENT AS OBJECT (
DEPTNO NUMBER (2),
DNAME VARCHAR2 (14),
LOC VARCHAR2 (13),
EMPS A_EMPLOYEE
);

Come si nota, l'attributo 'emps' é di tipo A_EMPLOYEE.
Per usare questo nuovo tipo di dato necessitiamo, come abbiamo visto nel precedente articolo, di un Wrapper, ovvero di una classe che si occupi di rappresentare in ambiente Java l'oggetto SQL3. La classe 'DepartmentWrap' é del tutto analoga agli altri Wrapper, differisce solo nel modo di lettura e di scrittura dell'array:

java.util.ArrayList _list = new java.util.ArrayList();
java.sql.Array array;
Object[] objs;
int i;

array = stream.readArray();

objs = (Object[])array.getArray(OrmTest.getInstance().getMap());
_list.ensureCapacity(objs.length);

for (i = 0; i < objs.length; i++){
   try{
     _list.add(((EmployeeWrap)objs[i])._obj);
   }
   catch (NullPointerException e){
      System.out.println("JDBC PROBLEM : " + e.getMessage());
   }
}

Employee[] emps = (Employee[])_list.toArray(new Employee[0]);
_obj = new Department(depno, depname, loc, emps);


Il metodo readArray dell'interfaccia SQLInputStream consente di leggere un Array, il cui tipo base dev'essere contenuto nella Type Map affinché il driver sia in grado di identificarlo.
Gi array sono supportati in maniera pienamente utilizzabile solo dalla versione 8.1.7 di Oracle: la versione 8.1.6 del thin Driver ha un baco per cui un array di dimensione N viene riportato come un array di dimensione N+1 dove il primo elemento é uguale a "null".
Attenzione inoltre alle compatibilità tra Driver e Database: se le versioni sono diverse, lo stream potrebbe non essere costruito correttamente.
Vediamo adesso come lo stesso Wrapper scriva un oggetto di tipo Department ed il relativo Array di Employee nel DB:

Department obj = (Department)_obj;

stream.writeLong(obj.depno);
stream.writeString(obj.depname);
stream.writeString(obj.loc);

EmployeeWrap[] vars_wraps = new EmployeeWrap[obj.emps.length];

for (int i = vars_wraps.length - 1; i >= 0; i--)
   vars_wraps[i] = new EmployeeWrap(obj.emps[i]);

Connection _conn = com.acme.resource.ResourceManager.getInstance().getNativeConnection();

oracle.sql.ArrayDescriptor v_ard =
    new oracle.sql.ArrayDescriptor("A_EMPLOYEE", _conn);
oracle.sql.ARRAY v_array =
   
new oracle.sql.ARRAY(v_ard, _conn, vars_wraps);

stream.writeArray(v_array);

Dall'analisi delle precedenti righe si vede come il codice sia molto simile a quello con cui si scriveva direttamente un Array nel DB e visto precedentemente: valgono ovviamente le stesse considerazioni allora esposte. Ovviamente il codice qui riportato é solo un esempio che non ha la pretesa d'essere completo: mancano la gestione degli errori, i controlli per eventuali oggetti uguali a "null" e così via.
Consiglio di usare le Nested Table di Oracle solo dopo averci preso confidenza, in quanto un uso errato dal lato PL-SQL porta ad un degradamento inaccettabile della performance.

Gli esempi per gli array e l'oggetto composto sono contenuti nel file Zip allegato all'articolo. Consiglio inoltre di leggersi molto attentamente sia la documentazione di Oracle riguardo l'uso di collections in SQL e PL-SQL, nonché il paragrafo relativo agli SQL3 Data Types del Tutorial su JDBC 2.0 di Maydene Fisher disponibile presso il sito della Java Developer Connection.

 

 

Conclusione
Abbiamo così concluso la descrizione, iniziata nell'articolo precedente, delle possibilità, offerte dagli oggetti SQL3 in combinazione con Java e JDBC, che useremo per il design di un ORM Framework tra Oracle9i e un'implementazione Java e CORBA(oggetto del prossimo articolo).

 

 

Legenda degli acronimi

 

 

Resources
- JDC on JDBC: http://developer.java.sun.com/developer/Books/JDBCTutorial/index.html
- SQL3 Objects Tutorial: http://java.sun.com/docs/books/tutorial/jdbc/jdbc2dot0/sql3.html
- Oracle Site OTN (Oracle Technology Network) JDBC Developer's Guide and Reference:
http://download-west.oracle.com/otndoc/oracle9i/901_doc/java.901/a90211.pdf

 

 

Esempi
Scarica gli esempi descritti in questo articolo


MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems; tutti i diritti riservati 
E' vietata la riproduzione anche parziale 
Per comunicazioni inviare una mail a info@mokabyte.it