Abstract
Il driver jdbc dispone di buone capacità di
reflection. Vediamo come servircene per implementare un
algoritmo universale di sincronizzazione di due database.
Data di stesura: 16/11/2002
Data di pubblicazione:
16/11/2002
Ultima modifica: 04/04/2006
Quando si sviluppa una applicazione web, è necessario porre molta
attenzione agli aspetti legati alla sicurezza.
Spesso è il responsabile della sicurezza a dettare le
regole. Supponiamo di voler scrivere una applicazione web per la
pubblicazione di articoli:
Figura 1: Schema delle macchine
DBFRU è il database di fruizione, non protetto dal firewall.
DBAPP è il database di approvazione, protetto sulla sottorete
interna.
Sulla macchina W è installata l'applicazione di fruizione, e sulla
macchina E è installato un editor di articoli.
L'architettura illustrata prevede un utente ROUSER
in sola lettura, usato dalla macchina web W, che legge gli articoli da
DBFRU.
L'editor, presente sulla macchina E è in grado di scrivere sia su
DBAPP che su DBFRU.
La macchina W non può accedere a DBAPP perché il firewall blocca
ogni connessione.
Il DBFRU è un database veloce nelle operazioni di lettura, per esempio
MySql[2].
Il sistemista può decidere di avere N macchine web ognuna con il suo
database, per migliorare le performance. Per semplicità supporremo
che ci sia un solo database.
DBAPP è un database transazionale in grado di gestire complessi
vincoli che non vengono mantenuti su DBAPP. L'applicazione E deve
essere in grado di copiare il contenuto di DBAPP su DBFRU, in modo
anche incrementale.
Per semplificare la trattazione faremo alcune ipotesi di comodo:
Gli schemi dei due database sono identici
Vengono usati solo i tipi base SQL. Inoltre l'aggiornamento è
solo in un senso, da DBAPP a DBPRU.
Benché sia responsabilità del DBA assicurarsi che gli schemi dei
due database siano identici, si richiede un controllo applicativo per
evitare pasticci.
SmartSync
SmartSync[1] è una classe in grado
di sincronizzare completamente una qualsiasi tabella.
Vediamo come è fatto il costruttore:
public SmartSync(String tableName, Connection srcCon,
this.universalInsert="INSERT INTO "+this.targetTable +" VALUES (";
int i=columns;
while(i>1){
this.universalInsert += "?,";
i--;
}
this.universalInsert += " ?)";
}
SmartSync richiede in input tre parametri: il nome della tabella da
processare, la connessione sorgente (es DBAPP) e quella di destinazione
(es DBFRU).
Il costruttore inizializza le variabili d'istanza, e provvede anche ad
eseguire una select sulla tabella sorgente verificando che non sia vuota
(linee 4-14). Poi legge il ResultSetMetaData ed
estrae i tipi delle colonne, servendosi del metodo
getTypes (linee 15-20). La stessa cosa viene fatta
per la tabella destinazione (righe 23-30).
In linea 33 la funzione checkDestTypes() controlla che i tipi
sorgente e destinazione coincidano. In caso di errore viene lanciata
una eccezione specifica (SyncException) che è una sottoclasse di
SQLException.
Se tutto va a buon fine viene creato lo statement sql di insert
(universalInsert).
Il metodo getTypes si serve del ResultSetMetaData per ottenere i tipi
identificati da costanti intere.
Per migliorare il debugging, ho introdotto anche una funzione universale (typeToString)
che traduce questi tipi in stringhe:
int [] getTypes(ResultSetMetaData md) throws SQLException {
int colType[];
int cols=md.getColumnCount();
colType=new int[cols];
int t=0;
String msg=" Types:";
while(t<cols){
// Rec: le pos partono da 1, gli array da zero!
colType[t]=md.getColumnType(t+1);
msg+=typeToString(colType[t]) + " ";
t++;
}
log.debug(msg);
return colType;
}
private String typeToString(int type) {
switch(type) {
case java.sql.Types.ARRAY:return "ARRAY";
case java.sql.Types.BIGINT: return "BIGINT";
case java.sql.Types.BINARY: return "BINARY";
case java.sql.Types.BIT: return "BIT";
case java.sql.Types.BLOB: return "BLOB";
case java.sql.Types.CHAR: return "CHAR";
case java.sql.Types.CLOB: return "CLOB";
case java.sql.Types.DATE: return "DATE";
case java.sql.Types.DECIMAL: return "DECIMAL";
case java.sql.Types.DISTINCT: return "DISTINCT";
case java.sql.Types.DOUBLE: return "DOUBLE";
case java.sql.Types.FLOAT: return "FLOAT";
case java.sql.Types.INTEGER: return "INTEGER";
case java.sql.Types.JAVA_OBJECT: return "JAVA_OBJECT";
case java.sql.Types.LONGVARBINARY: return "LONGVARBINARY";
case java.sql.Types.LONGVARCHAR: return "LONGVARCHAR";
case java.sql.Types.NULL: return "NULL";
case java.sql.Types.NUMERIC: return "NUMERIC";
case java.sql.Types.OTHER: return "OTHER";
case java.sql.Types.REAL: return "REAL";
case java.sql.Types.REF: return "REF";
case java.sql.Types.SMALLINT: return "SMALLINT";
case java.sql.Types.STRUCT: return "STRUCT";
case java.sql.Types.TIME: return "TIME";
case java.sql.Types.TIMESTAMP: return "TIMESTAMP";
case java.sql.Types.TINYINT: return "TINYINT";
case java.sql.Types.VARBINARY: return "VARBINARY";
case java.sql.Types.VARCHAR : return "VARCHAR";
default:
return"UNKNOWN_SQL_TYPE?";
} // switch
}
Al ritorno dal costruttore il chiamante sa di avere un oggetto
SmartSync che ha già validato la compatibilità dei tipi delle
colonne tra i due database: per cui si può procedere tranquillamente
alla sincronizzazione.
Invocando il metodo syncAll() si fa partire il processo
di sincronizzazione:
Per prima cosa si estraggono i dati con una sola query (linea 7).
Poi si inizia il ciclo di inserimento (linee 9-26).
Per ogni inserimento si estraggono i valori di tutte le colonne
(ciclo for in linee 12-23)
Si ottimizza il ciclo servendosi di una PreparedStatement per
effettuare le insert.
Purtroppo l'API del JDBC è allergica agli oggetti null, per cui è
necessario porre attenzione quando manipola un tale oggetto. Quando
si estrae (linea 13) un valore SQL NULL con l'API JDBC, esso
è trasformato in un puntatore java null.
Benché abbiano lo stesso nome, si tratta di due entità differenti.
Per questa ragione le linee 17-22 si preoccupano di modificare il
ciclo che popola la query di insert in modo appropriato. Se si tentasse
di eseguire insertStm.setObject(i,null, type) si riceverebbe
un errore a runtime.
Note d'uso
Siccome si tratta di una sincronizzazione
applicativa, è più lenta di quella che si avrebbe se i due database
riuscissero a parlarsi "da soli", per esempio scambiandosi dump
compressi dei dati.
Per esempio, Oracle[4] è famoso per un
tool, chiamato SQL*Loader che è efficientissimo nel caricamento di enormi quantità di dati da
file di testo; purtroppo SQL*Loader funziona solo con Oracle e non è
intuitivo da usare.
È importante notare che è responsabilità del client che usa
SmartSync effettuare il commit() della transazione di
destinazione.
In caso di errore durante
l'esecuzione di syncAll() il chiamante è in grado di eseguire un
rollback() e di mantenere il database in uno stato consistente.
Sperimentalmente le performance di SmartSync sono soddisfacenti, a patto
che la presenza di router/firewall/switch non rallenti troppo la
connessione al DBFRU.
Sviluppi Futuri
Si è presentato qui solo un assaggio di quello che si può fare
servendosi del driver JDBC.
In un prossimo articolo vedremo che è possibile costruire dei filtri per sincronizzare solo
colonne che soddisfino particolari condizioni di where.
Inoltre è possibile anche sviluppare una serie di classi di mapping
che trasformino delle colonne in altre per migrare
facilmente schemi differenti ma semanticamente compatibili. Potremmo
chiamare questi componenti DataMutators.
Informazioni sull'autore
Giovanni Giorgi, classe 1974. Dopo il diploma di liceo Classico, si è laureato in Informatica nel febbraio 2000, e attualmente lavora nel campo del software finanziario (trading on line, soluzioni web). Appassionato di linguaggi di programmazione, si interessa anche di politica e letteratura.
MySql è un database Open Source con un discreto seguito. È famoso
per la sua velocità ma anche per la carenza di molte features tipiche dei
database relazionali.
http://www.mysql.com
PostgreSQL è un database relazionale open source, che supporta
parecchie features al pari di database commerciali. Inoltre supporta
caratteristiche avanzate, come una forma embrionale di OOP (ereditarietà tra
tabelle, overloading sulle funzioni, operatori ed aggregatori).
http://www.postgresql.com