Java & PostgreSQL:
da JDBC a Pl/Java
(passando per org.postgresql.Driver)
Ing. Luca Ferrari, PhD
[email protected]
Italian PostgreSQL Users Group
ITPug
Synopsis
Questa presentazione vuole fungere da punto di
riferimento per gli sviluppatori Java che vogliano scrivere
applicazioni capaci di gestire i dati contenuti in un cluster
PostgreSQL.
In questa presentazione verranno mostrate le principali
tecniche per la connettività Java verso PostgreSQL
(tramite JDBC), il funzionamento interno del driver JDBC
ufficiale nonché come “estendere” il database
PostgreSQL con tecnologia Java tramite Pl/Java.
Gli esempi di applicazioni/database qui riportati sono a
puro scopo didattico.
Java & PostgreSQL – Luca Ferrari
2 di 182
Database didattico
CREATE TABLE corso
(
corsopk serial NOT NULL, ­­ Chiave surrogata della tabella corso.
corsoid character varying(10), ­­ Chiave reale della tabella corso. descrizione text, ­­ Descrizione del corso requisiti text, ­­ Elenco dei requisiti richiesti data date, ­­ Data in cui si tiene il corso n_ore integer, ­­ Numero di ore del corso.
materiale oid, ­­ Materiale didattico distribuito con il corso ts timestamp with time zone DEFAULT ('now'::text)::timestamp,
CONSTRAINT corso_chiave_surrogata PRIMARY KEY (corsopk),
CONSTRAINT corso_chiave_reale UNIQUE (corsoid),
CONSTRAINT corso_n_ore_check CHECK (n_ore > 0 AND n_ore < 8)
)
Java & PostgreSQL – Luca Ferrari
3 di 182
Database didattico
CREATE TABLE partecipante
(
partecipantepk serial NOT NULL, ­­ Chiave surrogata della tabella nome character varying(20), ­­ Nome del partecipante.
cognome character varying(20), ts timestamp with time zone DEFAULT ('now'::text)::timestamp,
partecipanteid character varying(30),
CONSTRAINT partecipante_chiave_surrogata PRIMARY KEY (partecipantepk),
CONSTRAINT partecipante_chiave_reale UNIQUE (partecipanteid)
)
Java & PostgreSQL – Luca Ferrari
4 di 182
Database didattico
CREATE TABLE j_corso_partecipante
(
j_corso_partecipante_pk serial NOT NULL, ­­ Chiave surrogata corsopk integer NOT NULL,
partecipantepk integer NOT NULL,
CONSTRAINT j_corso_partecipante_chiave_surrogata PRIMARY KEY (j_corso_partecipante_pk),
CONSTRAINT j_corso_partecipante_corsopk FOREIGN KEY (corsopk)
REFERENCES corso (corsopk) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT j_corso_partecipante_partecipantepk FOREIGN KEY (partecipantepk)
REFERENCES partecipante (partecipantepk) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Java & PostgreSQL – Luca Ferrari
5 di 182
JDBC:
applicazioni client
Java & PostgreSQL – Luca Ferrari
6 di 182
JDBC
JDBC (Java DataBase Connectivity) è un'API a
livello SQL, ossia consente di inglobare comandi
SQL nelle proprie applicazioni Java.
JDBC fornisce un'unica API CLI per l'accesso a
database relazioni eterogenei. L'applicazione non
necessita di adattamenti qualora il motore
database cambi!
Applica il famoso concetto “Write Once, Run
Everywhere” alla connettività database.
Si noti che l'idea non è nuovissima: ODBC si propone già come
API unificata.
Non è indispensabile utilizzare JDBC!
Java & PostgreSQL – Luca Ferrari
7 di 182
Application & Driver APIs
JDBC può essere divisa in due parti fondamentali: Application API
e Driver API.
Le Application API comprendono tutti gli elementi che
un'applicazione Java usa per la connessione ad un database.
Le Driver API forniscono la base per la scrittura di un driver
specifico per un determinato motore relazionale (es. PostgreSQL).
Grazie alla suddivisione in due parti, JDBC consente la scrittura di
applicazioni portabili e database-independent.
All'URL
All'URL
http://developers.sun.com/product/jdbc/drivers
http://developers.sun.com/product/jdbc/drivers
èèpossibile
possibileverificare
verificarel'esistenza
l'esistenzadidiun
undriver
driverJDBC
JDBC
per
ogni
motore
relazionale
conosciuto.
per ogni motore relazionale conosciuto.
Java & PostgreSQL – Luca Ferrari
8 di 182
Application API vs Driver API
Java Application
Application
ApplicationAPI
API
Driver
DriverAPI
API
Database
Engine
Java & PostgreSQL – Luca Ferrari
9 di 182
Livelli JDBC
Le Driver API riconoscono quattro modalità di implementazione
differenti, chiamate livelli o type dei driver JDBC:
type 1 (JDBC-ODBC bridge): viene sfruttato un accesso
ODBC (che deve esistere!).
●
type 2 (Native API Drivers): il driver richiama, tramite JNI,
codice nativo (es. C/C++) per la connettività.
●
type 3 (Network Drivers): il driver si collega (via TCP/IP) ad un
componente lato server che si interfaccia a sua volta con il
database server.
●
type 4 (Pure Java): il driver si collega direttamente al
database server e comunica utilizzando un protocollo
opportuno.
●
Java & PostgreSQL – Luca Ferrari
10 di 182
Livelli JDBC: schema riassuntivo
JDBC Type 1
ODBC Driver
JDBC Type 2
Native Driver
Database
Engine
Java Application
JDBC Type 3
Translator (e.g., local
JDBC type 2)
JDBC Type 4
Java & PostgreSQL – Luca Ferrari
11 di 182
JDBC API
Java & PostgreSQL – Luca Ferrari
12 di 182
Classi principali JDBC
Le principali classi/interfaccie della libreria JDBC
(java.sql.* e javax.sql.*) sono:
DriverManager: rappresenta un repository di istanze di
Driver per ogni database server. Ci si rivolge a questa
classe per creare una connessione ad uno specifico
database (tramite un URL).
Driver: rappresenta il componente incaricato di stabilire la
connessione con il database server. Può essere caricato
all'interno del programma (es. tramite reflection) o
specificando la proprietà jdbc.drivers nel momento di
invocazione della JVM:
java ­Djdbc.drivers=org.postgresql.Driver myApp
Java & PostgreSQL – Luca Ferrari
13 di 182
Classi principali JDBC (2)
Connection: rappresenta una connessione al server
database. Tramite di essa è possibile interagire con il
database stesso.
Statement: implementa una query SQL di qualunque tipo;
viene usato per l'esecuzione di un comando sul database
server. Si specializza in due sotto-interfaccie:
PreparedStatement: una query SQL precompilata e
dotata di wrapping dei parametri.
CallableStatement: una query SQL che coinvolge
una stored procedure.
Java & PostgreSQL – Luca Ferrari
14 di 182
Classi principali JDBC (3)
ResultSet: implementazione di un cursore SQL.
Contiene un riferimento alla riga dei risultati dall'esecuzione di
un comando SQL. Disponde di una serie di metodi per il
posizionamento rispetto alle righe e per l'estrazione dei valori
di ogni colonna.
ResultSetMetaData: informazioni aggiuntive su un
ResultSet, quali nomi e numero delle colonne, nome dello
schema corrente, etc.
DatabaseMetaData: fornisce informazioni aggiuntive sul
database stesso, quali versione, stored procedures, foreign
keys, etc.
SQLException: classe base di ogni eccezione JDBC.
Java & PostgreSQL – Luca Ferrari
15 di 182
Tipica applicazione JDBC
Caricamento
Caricamentodel
del
driver
necessario
driver necessario
Istanziazione
Driver
Connessione
Connessionealal
database
database
(Connection)
(Connection)
Registrazione presso
DriverMagaer
[while ResultSet.next()]
<INVIO>
Lettura
Letturadel
del
ResultSet
ResultSet
Simile all'esecuzione del
comando
psql -h host -U utente db
Simile alla scrittura
di una query sulla
linea di comando di
psql
Esecuzione
Esecuzionedello
dello
Statement
Statement
Creazione
Creazionedidiuno
uno
Statement
Statement
Chiusra
Chiusrarisorse
risorseassociate
associateaa
ResultSet
ResultSet ee Statement
Statement
Chiusura
Chiusura
connessione
connessione
(Connection)
(Connection)
Java & PostgreSQL – Luca Ferrari
16 di 182
Classi accessorie
La libreria fornisce una serie di classi aggiuntive
per il mapping fra i tipi SQL e i tipi Java (Types),
la gestione di Large Object Data (Clob, Blob),
date (Date), timestamp (Timestamp) e array
(Array).
Come estensioni sono presenti anche le classi
per la gestione dei data source (DataSource), dei
row set (RowSet) e dei relativi eventi (e.g.,
RowSetEvent, StatetementEvent).
Java & PostgreSQL – Luca Ferrari
17 di 182
Connessione al database
public static void main(String[] args)
throws Exception{
try{
// classe del driver
String driverName = "org.postgresql.Driver";
// url di connessione
String databaseURL =
"jdbc:postgresql://localhost/pgdaydb";
// caricamento della classe del driver
Class driverClass = Class.forName(driverName);
// creazione dell'istanza del driver
Driver driver = (Driver) driverClass.newInstance();
Java & PostgreSQL – Luca Ferrari
18 di 182
Connessione al database
//
//
//
//
a questo punto il driver è registrato
presso il DriverManager (il driver di PostgreSQL
si registra automaticamente al DriverManager
appena la sua classe viene caricata)
// essendo il driver caricato e registrato posso
// ottenere una connessione al database
Connection connection =
DriverManager.getConnection(databaseURL,
"luca",
// username
"xxx"
// password
);
// una volta ottenuta una connessione al database
// e' possibile interagire con esso per via di
// oggetti di tipo Statement
Java & PostgreSQL – Luca Ferrari
19 di 182
Creazione di uno Statement
// creazione di una query e di uno statement
// da usare (la query mostra i corsi
// che contengono java nella descrizione
// e i relativi iscritti)
String query = "SELECT c.corsoid, c.descrizione, c.data,
c.n_ore, p.nome, p.cognome " +
" FROM (corso c LEFT JOIN j_corso_partecipante
j ON c.corsopk = j.corsopk) " +
" LEFT JOIN partecipante p ON p.partecipantepk
= j.partecipantepk " +
" WHERE c.descrizione ilike '%java%' ";
// notare che lo Statement non e' ancora associato
// a nessuna query
Statement statement = connection.createStatement();
// effettuo la query
ResultSet rs = statement.executeQuery(query);
Java & PostgreSQL – Luca Ferrari
20 di 182
Analisi dei risultati: ResultSet
// visualizzo i dati ottenuti dalla query: l'oggetto
// ResultSet (rs) rappresenta un cursore dei risultati
// che può essere scorso.
while( rs.next() ){
System.out.println("######");
// posso ottenere i dati di una riga chiamando i
// metodi getXXX (XXX = tipo di dato) e specificando
// il numero della colonna (partendo da 1) o il suo
// nome simbolico
System.out.println("Id-Corso e descrizione: "
+ rs.getString(1) + " " + rs.getString(2));
System.out.println("Data e numero ore: "
+ rs.getDate(3) + " " + rs.getInt(4));
System.out.println("Iscritto: "
+ rs.getString("nome") + " "
+ rs.getString("cognome"));
}
Java & PostgreSQL – Luca Ferrari
21 di 182
Fine del programma e gestione delle
eccezioni
// chiusura della connessione
connection.close();
}
}catch(SQLException exception){
// eccezione SQL (problema nella query, errore del
// server, etc.)
}catch(ClassNotFoundException ex){
// classe del driver JDBC non trovata
}catch(InstantiationException ex){
// errore di reflection (classe driver non
// istanziabile)
}catch(IllegalAccessException ex){
// errore di reflection (classe driver non
// accessibile)
}
}
Java & PostgreSQL – Luca Ferrari
22 di 182
Esecuzione
Riassunto:
● Caricamento del driver PostgreSQL (necessario solo all'avvio del thread
principale dell'applicazione);
● Creazione di una connessione specificando l'URL di connessione;
● Creazione di uno Statement;
● Esecuzione dello Statement;
● Lettura dei risultati tramite il ResultSet;
● Chiusura delle risorse.
Java & PostgreSQL – Luca Ferrari
23 di 182
Come fa il DriverManager a scegliere il Driver
opportuno?
Il DriverManager stabilisce quale sia il Driver da utilizzare per una
connessione sulla base dell'URL di connessione (ogni driver accetta il
proprio subprotocol):
protocol:subprotocol://HOSTNAME/DB_NAME
HOSTNAME: specifica
l'indirizzo IP/nome dell'host
ove è in esecuzione il
database.
DB_NAME: nome del
database cui ci si intende
connettere.
jdbc:postgres://localhost/pgdaydb
protocol: specifica il
dominio di appartenenza di
questo URL. Indica che l'URL
si riferisce ad una
connessione database
(JDBC).
subprotocol: specifica il driver che si
deve utilizzare. Viene utilizzato da
DriverManager per fare il lookup
dell'istanza di Driver da restituire per
gestire la connessione al database.
Java & PostgreSQL – Luca Ferrari
24 di 182
Considerazioni sul caricamento del driver
Il caricamento e l'istanziazione del driver JDBC può
avvenire attraverso reflection o direttamente:
// reflection
Class driverClass =
Class.forName(“org.postgresql.Driver”);
Driver driverInstance = driverClass.newInstance();
// istanziazione diretta
Driver driverInstance = new org.postgresql.Driver();
I driver hanno sempre un costruttore di default senza argomenti,
I driver hanno sempre un costruttore di default senza argomenti,
e quindi sono sempre istanziabili mediante reflection.
e quindi sono sempre istanziabili mediante reflection.
Java & PostgreSQL – Luca Ferrari
25 di 182
Considerazioni sul ResultSet
Un ResultSet rappresenta un cursore sui risultati di una query. Il
posizionamento fra le righe del cursore avviene tramite metodi specifici, quali
next() e previous().
Ogni ResultSet è legato allo Statement che lo ha generato; se lo
Statement viene alterato il ResultSet è automaticamente invalidato!
Esistono metodi per recuperare il valore di ogni colonna secondo il tipo di
appartenenza (String, int, Date, ...):
<tipo_Java> get<tipo_Java>(int colonna)
<tipo_Java>get<tipo_Java>(String nomeColonna)
Ogni tipo SQL viene mappato in un tipo Java (e viceversa) mediante una specifica
tabella
di
conversione
(http://java.sun.com/j2se/1.3/docs/guide/jdbc/getstart/mapping.
html). Tale tabella può essere estesa dall'utente per mappare i tipi user defined.
SiSipresti
prestiattenzione
attenzionealalfatto
fattoche,
che,contrariamente
contrariamentealla
allalogica
logica
degli
array,
le
colonne
sono
numerate
partendo
da
1!
degli array, le colonne sono numerate partendo da 1!
Java & PostgreSQL – Luca Ferrari
26 di 182
ResultSet: errori frequenti
Si richiama getXXX(..) con un
indice di colonna errato (maggiore)
rispetto al numero di colonne estratte
dalla query.
Si richiama getXXX(..) prima di
aver chiamato almeno una volta
next().
Java & PostgreSQL – Luca Ferrari
27 di 182
Esecuzione di un
INSERT/UPDATE/DELETE
// creazione di una query e di uno statement
// da usare per inserire un nuovo corso
String query = "INSERT INTO corso(corsoid,descrizione)
VALUES('Java_adv', 'Corso avanzato su Java/JDBC') ";
Statement statement = connection.createStatement();
// chiedo al server di eseguire la query e
// di fornirmi il numero di righe "toccate"
// tutte le query che modificano i dati vanno eseguite
// attraverso il metodo executeUpdate(..)
int rows = statement.executeUpdate(query);
if( rows > 0)
System.out.println("Inserimento effettuato con
successo:" + rows + " righe inserite");
// chiusura della connessione
connection.close();
Java & PostgreSQL – Luca Ferrari
28 di 182
Esecuzione
Riassunto:
● Caricamento del driver PostgreSQL (necessario solo all'avvio del thread
principale dell'applicazione);
● Creazione di una connessione specificando l'URL di connessione;
● Creazione di uno Statement;
● Esecuzione dello Statement;
● Lettura del valore di ritorno dell'esecuzione dello Statement, tale valore
indica il numero di righe toccate dalla query;
● Chiusura delle risorse.
Java & PostgreSQL – Luca Ferrari
29 di 182
Compilazione del driver
Java & PostgreSQL – Luca Ferrari
30 di 182
Driver JDBC per PostgreSQL
PostgreSQL fornisce un driver JDBC conforme ai livelli 2,3 e 4. Il sito
web del progetto è http://jdbc.postgresql.org, dal quale è
possibile scaricare il driver (in formato binario o sorgente), navigare la
documentazione Javadoc della parte public delle API.
E' presente una mailing list specifica: pgsql­[email protected]; gli attuali mantainer del Driver sono Dave Cramer, Kris Jurka, Oliver
Jowett.
Java & PostgreSQL – Luca Ferrari
31 di 182
Decompressione dei sorgenti
I sorgenti vengono rilasciati sotto forma di archivio
compresso (tarball):
postgresql­jdbc­8.2­504.src.tgz
E' possibile decomprire
programma tar:
i
sorgenti
usando
il
tar xzvf postgresql­jdbc­8.2­504.src.tgz
ottenendo una directory che contiene l'albero dei
sorgenti
ls ­l postgresql­jdbc­8.2­50
­rw­r­­r­­ 1 luca luca 259 2006­12­01 13:02 build.properties
­rw­r­­r­­ 1 luca luca 21469 2006­11­29 05:00 build.xml
drwxr­xr­x 2 luca luca 4096 2006­12­01 13:22 doc
­rw­r­­r­­ 1 luca luca 1542 2005­01­11 09:25 LICENSE
drwxr­xr­x 3 luca luca 4096 2006­12­01 13:22 org
­rw­r­­r­­ 1 luca luca 3769 2004­10­23 00:24 README
­rwxr­xr­x 1 luca luca 495 2004­11­07 23:15 update­translations.sh
Java & PostgreSQL – Luca Ferrari
32 di 182
Compilazione: requisiti
Per compilare il driver JDBC è necessario utilizzare Apache
Ant (http://ant.apache.org), uno strumento di compilazione
automatico per Java.
Ant è uno strumento concettualmente simile a make, ma
ottimizzato per la gestione dei progetti Java. Esso stesso è
scritto in Java, e questo ne facilita la fruibilità nello sviluppo
di componenti Java.
Ant necessita della presenza di un file di build (solitamente
build.xml) che contiene i task del processo di compilazione.
E' possibile specificare proprietà di compilazione mediante
file di proprietà Java (file properties).
Java & PostgreSQL – Luca Ferrari
33 di 182
Compilazione: build
Il file di build del driver è build.xml, e può essere omesso
nell'invocazione di ant:
[luca@fluca:/sviluppo/java/contrib­src/postgresql­jdbc­8.2­504.src]$ ant
Buildfile: build.xml
all:
prepare:
check_versions:
check_driver:
driver:
compile:
[javac] Compiling 144 source files to /sviluppo/java/contrib­src/postgresql­jdbc­8.2­
504.src/build
[javac] Note: Some input files use or override a deprecated API.
[javac] Note: Recompile with ­Xlint:deprecation for details.
[javac] Note: Some input files use unchecked or unsafe operations.
[javac] Note: Recompile with ­Xlint:unchecked for details.
jar:
[jar] Building jar: /sviluppo/java/contrib­src/postgresql­jdbc­8.2­
504.src/jars/postgresql.jar
BUILD SUCCESSFUL
Total time: 5 seconds
[luca@fluca:/sviluppo/java/contrib­src/postgresql­jdbc­8.2­504.src]$
Java & PostgreSQL – Luca Ferrari
34 di 182
Compilazione: risultato
Al termine del processo di compilazione viene generato un
file postgresql.jar nella sottodirectory jars:
[luca@fluca:/sviluppo/java/contrib­src/postgresql­jdbc­8.2­504.src]$ ls ­l jars/
total 436
­rw­r­­r­­ 1 luca luca 441337 2007­03­31 11:22 postgresql.jar
[luca@fluca:/sviluppo/java/contrib­src/postgresql­jdbc­8.2­504.src]$
Il file jar prodotto deve essere incluso nel classpath
corrente affinché i programmi Java possano caricare il
driver.
$ export CLASSPATH=`pwd`/jars/postgresql.jar:$CLASSPATH
$ echo $CLASSPATH
/sviluppo/java/contrib­src/postgresql­jdbc­8.2­504.src/jars/postgresql.jar:
$
Java & PostgreSQL – Luca Ferrari
35 di 182
Compilazione: risultato (2)
Un altro file importante viene generato: Driver.java
$ ls ­l org/postgresql/Driver.java*
­rw­r­­r­­ 1 luca luca 29517 2007­03­31 11:21 org/postgresql/Driver.java
­rw­r­­r­­ 1 luca luca 29523 2005­11­24 07:18 org/postgresql/Driver.java.in
$
Prima
della
compilazione infatti il solo file
Driver.java.in è presente. Il file Driver.java viene
generato dinamicamente durante il processo di build,
a seconda del livello JDBC del driver.
Java & PostgreSQL – Luca Ferrari
36 di 182
Compilazione in Eclipse
Definire un nuovo
progetto basato su un
file Ant esistente.
(1)
(2) Selezionare il file di
build dalla directory dei
sorgenti; specificare le
proprietà del progetto.
Java & PostgreSQL – Luca Ferrari
37 di 182
Compilazione in Eclipse (2)
(3) Ricercare il file
build.xml
tramite il modulo Ant
di Eclipse.
Java & PostgreSQL – Luca Ferrari
38 di 182
Compilazione in Eclipse (3)
(4) Eseguendo il file di build il progetto viene
compilato.
Java & PostgreSQL – Luca Ferrari
39 di 182
JDBC 2, 3 o 4?
Il driver JDBC di Postgresql supporta solo i
livelli JDBC 2,3 e 4.
L'albero dei sorgenti
include i package per
ogni versione JDBC.
$ ls org/postgresql/ ­l
...
drwxr­xr­x 3 luca luca 4096 2006­12­01 13:23 jdbc2
drwxr­xr­x 2 luca luca 4096 2006­12­01 13:23 jdbc3
drwxr­xr­x 2 luca luca 4096 2006­12­01 13:23 jdbc4
...
$
La
decisione
su
quale
livello
di
driver
(org.postgresql.Driver) viene presa durante il processo
di build in base alla versione della JVM.
In particolare la versione viene stabilita sulla base della Java
Virtual Machine installata sul sistema.
Java & PostgreSQL – Luca Ferrari
40 di 182
Driver.java.in
Il file org.postgresql.Driver.java.in è un file che viene
tramutato nel driver Java vero e proprio durante il
processo di build Ant.
Al suo interno il file Driver.java.in contiene dei
segnaposti per le classi JDBC del livello opportuno.
Java & PostgreSQL – Luca Ferrari
41 di 182
Scelta del livello JDBC
Il
file
contiene
build.xml
un target,
check_versions,
che
controlla quale versione
di JVM sia disponibile
e, di conseguenza,
quale versione di driver
sia la più appropriata.
A seconda del livello stabilito
viene impostata a true la
proprietà jdbcX.
Java & PostgreSQL – Luca Ferrari
42 di 182
Scrittura del livello JDBC
Il target driver si occupa della scrittura del file
Driver sostituendo i marcaposto al suo interno con
le classi del livello JDBC scelto.
Si noti che viene utilizzata la funzionalità di filtro
(filter) di Ant per la sostituzione dei marcaposto
(token) con le classi concrete.
Diverse altre classi sono specificate con lo stesso
procedimento.
Java & PostgreSQL – Luca Ferrari
43 di 182
Scrittura del livello JDBC (2)
Viene fatto un controllo sulla proprietà jdbcX,
impostata
precedentemente
dal
target
check_versions.
(1)
Java & PostgreSQL – Luca Ferrari
44 di 182
Scrittura del livello JDBC (3)
(2) Vengono impostati i token marcaposto da
sostituire nel file Driver.java.in; ogni token
specificato in build.xml trova corrispondenza con
@token@ in Driver.java.in Java & PostgreSQL – Luca Ferrari
45 di 182
Scrittura del livello JDBC (4)
(3) Il file Driver.java.in viene copiato in Driver.java
applicando le condizioni di filtro.
Java & PostgreSQL – Luca Ferrari
46 di 182
JDBC:
tipi di Statement
Java & PostgreSQL – Luca Ferrari
47 di 182
PreparedStatement
Un PreparedStatement è un oggetto che memorizza uno
statement SQL precompilato, che può essere parametrizzato
e che può eseguire più efficientemente di uno Statement
normale.
L'idea è di precompilare un comando SQL parziale e di
completarlo successivamente, al momento in cui
l'esecuzione sia necessaria.
Lo statement precompilato può poi essere riutilizzato più
volte, cambiando eventualmente i parametri di esecuzione.
Un PreparedStatement è una sottointerfaccia di Statement!
Essendo
Essendouno
unostatement
statementparametrizzabile,
parametrizzabile,PreparedStatement
PreparedStatementfornisce
forniscedei
deimetodi
metodi
setXXX
setXXXeegetXXX
getXXXper
perimpostare
impostarei iparametri
parametrididiesecuzione.
esecuzione.
Java & PostgreSQL – Luca Ferrari
48 di 182
Prepared Statement
// creazione di una query di inserimento e dello
// statement precompilato per la sua esecuzione
String query = "INSERT INTO corso(corsoid,descrizione)
VALUES(?,?) ";
PreparedStatement statement =
connection.prepareStatement(query);
// inserisco alcuni corsi fornendo gli opportuni
// parametri al server
int rows = 0;
for(int i = 0; i < 10; i++){
String corsoid = "corso_" + i;
String descrizione = "Nessuna descrizione ";
// impostazione dei parametri nello statement
statement.setString(1, corsoid);
statement.setString(2, descrizione);
// eseguo lo statement (non devo specificare una query
// poiché lo statement è già stato precompilato)
rows += statement.executeUpdate();
}
Java & PostgreSQL – Luca Ferrari
49 di 182
Considerazioni: PreparedStatement
Un PreparedStatement utilizza una sintassi speciale per
le query SQL: ogni carattere ? viene usato come un
marcatore per un parametro che verrà specificato in
seguito.
In maniera duale ad un ResultSet vengono forniti dei
metodi setXXX(numpar, value).
Il vantaggio dei PreparedStatement è che possono essere
memorizzati e precompilati dal database server, aumentado le
prestazioni nel caso di cicli (l'ottimizzatore potrebbe però
stabilire piani diversi a seconda della selettività dei parametri).
Si
Sipresti
prestiattenzione
attenzionealalfatto
fattoche
che
i iparametri
parametrisono
sononumerati
numeratipartendo
partendoda
da1!1!
Java & PostgreSQL – Luca Ferrari
50 di 182
Statement vs PreparedStatement
Statement:
PreparedStatement:
✔ query statiche o poco variabili;
✔ query parametriche;
✔ nessuna
necessità
escaping dei parametri.
✔ query
base
da
eseguire
ciclicamente a seconda di
parametri run-time;
di
✔ escaping di parametri usati nella
query stessa.
L'utilizzo di PreparedStatement è preferibile in molte situazioni!
Java & PostgreSQL – Luca Ferrari
51 di 182
CallableStatement
L'interfaccia CallableStatement viene usata in maniera
analoga ai PreparedStatement per l'invocazione di una
Stored Procedure.
La sintassi utilizzata per l'invocazione è la seguente:
{ call stored_procedure(?,?,...) }
Viene specificato il nome della stored procedure da invocare,
seguito dalla lista di eventuali parametri.
La funzione viene invocata con il metodo executeQuery() che
restituisce un ResultSet.
Java & PostgreSQL – Luca Ferrari
52 di 182
JDBC:
Utilizzo avanzato
Java & PostgreSQL – Luca Ferrari
53 di 182
Esecuzione di comandi arbitrari
Si supponga di voler creare una vista definita come segue:
CREATE VIEW vw_partecipanti AS SELECT c.corsoID, c.descrizione, c.data, p.cognome, p.nome FROM partecipante p JOIN corso c ON c.corsoPK = p.corsoPK ORDER BY c.corsoID, p.cognome, p.nome
Come si può notare
l'esecuzione della query non
produce alcun risultato (in
termine di numero di righe
modificate) pur alterando la
struttura del database.
Java & PostgreSQL – Luca Ferrari
54 di 182
Esecuzione di comandi arbitrari
// creazione di uno statement
Statement statement = connection.createStatement();
// preparazione della query
String sql = "CREATE VIEW vw_partecipanti AS SELECT c.corsoID, c.descrizione, c.data, p.cognome, p.nome FROM partecipante p JOIN corso c ON c.corsoPK = p.corsoPK ORDER BY c.corsoID, p.cognome, p.nome";
int result = statement.executeUpdate(sql);
System.out.println("Risultato di esecuzione: " + result);
Java & PostgreSQL – Luca Ferrari
55 di 182
JDBC: creazione della procedura
// creazione di uno statement
Statement statement = connection.createStatement();
// preparazione della funzione
StringBuffer buffer = new StringBuffer(1000);
buffer.append( "CREATE OR REPLACE FUNCTION ...”); buffer.append(" RETURNS void AS $BODY$ DECLARE ...”);
// chiamata della stored procedure
int result = statement.executeUpdate(buffer.toString());
System.out.println("Risultato della creazione della procedura: " + result);
Java & PostgreSQL – Luca Ferrari
56 di 182
JDBC: invocazione della procedura
// invocazione della funzione
String sql = "{ call f_nuovo_corso(?,?,?) }";
// preparazione della chiamata e impostazione dei parametri
CallableStatement cStatement = connection.prepareCall(sql);
cStatement.setInt(1, 2);
cStatement.setString(2, "Corso2009");
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.DAY_OF_MONTH, 7);
calendar.set(Calendar.MONTH, 7);
calendar.set(Calendar.YEAR, 2009);
cStatement.setDate(3, new java.sql.Date(calendar.getTimeInMillis()) );
// invocazione della funzione
ResultSet resultSet = cStatement.executeQuery();
// in questo caso non ho risultati.... Java & PostgreSQL – Luca Ferrari
57 di 182
CallableStatement
L'interfaccia CallableStatement viene usata in maniera
analoga ai PreparedStatement per l'invocazione di una Stored
Procedure.
La sintassi utilizzata per l'invocazione è la seguente:
{ call stored_procedure(?,?,...) }
{ ?= stored_procedure(?,?,...) }
Viene specificato il nome della stored procedure da invocare,
seguito dalla lista di eventuali parametri.
L'invocazione avviene tramite il metodo executeQuery() che
restituisce un ResultSet.
E' possibile registrare parametri di input/output e ottenerne il valore
direttamente dallo statement.
Java & PostgreSQL – Luca Ferrari
58 di 182
Batch
L'interfaccia Statement consente l'esecuzione batch di
una serie di comandi SQL:
public void addBatch(String sql)
mediante tale metodo è possibile assegnare una serie di
istruzioni SQL da eseguire sequenzialmente in un
momento successivo.
L'esecuzione del gruppo di comandi avviene mediante il
metodo executeBatch(), che restituisce i singoli valori
di ritorno dell'esecuzione di ogni comando.
public int[] executeBatch()
Java & PostgreSQL – Luca Ferrari
59 di 182
Batch: un primo esempio
// creo uno statement
Statement statement = connetion.createStatement();
// imposto i comandi da eseguire nel batch
String sql = “INSERT INTO ...”;
// aggiungo questa istruzione al batch
statement.addBatch(sql);
// nuova istruzione da aggiungere al batch
sql = “INSERT INTO ...”;
statement.addBatch(sql);
...
// eseguo il batch
int result[] = statement.executeBatch();
// controllo i risultati di ogni comando
for(int i=0; i<result.length; i++)
System.out.println(“Comando “+(i+1)+” risultato “+ result[i]);
Java & PostgreSQL – Luca Ferrari
60 di 182
Batch & PreparedStatement
L'utilizzo dei batch agevola i casi in cui siano presenti cicli.
E' possibile utilizzare oggetti PreparedStatement al fine di
unire
i
vantaggi
di
un'esecuzione
batch
e
della
parametrizzazione dei comandi SQL.
// query parametri
String sql = “UPDATE corso SET descrizione=?
WHERE corsoID=?”;
PreparedStatement statement = connection.prepareStatement(sql);
for(int i=0; i< 10; i++){
statement.setString(1, descrizione[i]);
statement.setString(2, id[i]);
statement.addBatch();
}
// esecuzione
int result[] = statement.executeBacth();
Java & PostgreSQL – Luca Ferrari
61 di 182
Batch: considerazioni
Solitamente l'utilizzo di un batch risulta più
efficiente che l'esecuzione delle singole
operazioni in modo separato (ottimizzazione da
parte del driver).
Si presti attenzione al fatto che un batch è
differente da una transazione, tuttavia può essere
usato per rendere più compatto (e quindi
leggibile) il codice di una transazione.
Java & PostgreSQL – Luca Ferrari
62 di 182
Scrollable ResultSet
Uno scrollable ResultSet è un tipo particolare
ResultSet che offre una funzionalità di scrolling.
di
E' possibile muoversi attraverso le righe in avanti e indietro,
saltare righe, posizionarsi in modo relativo o assoluto, etc.
Il tipo di scrolling deve essere specificato a livello di creazione
dello Statement, specificando il tipo di scrolling (e di
aggiornabilità)
come
parametri
del
metodo
createStatement(..), in modo da consentire al database
di gestire le risorse.
IlIldriver
driverPostgreSQL
PostgreSQLutilizza
utilizzainternamente
internamenteun
unVector
Vectorper
per
contenere
contenereleletuple
tupledel
delResultSet;
ResultSet;mediante
medianteun
unindice
indice
riesce
a
garantire
lo
scrolling
di
tali
tuple.
riesce a garantire lo scrolling di tali tuple.
Java & PostgreSQL – Luca Ferrari
63 di 182
Tipo di scrolling
Il tipo di un ResultSet può essere specificato tramite tre valori
predefiniti:
ResultSet.TYPE_FORWARD_ONLY (1003) è il classico
ResultSet che può essere consultato solo in avanti.
ResultSet.TYPE_SCROLL_INSENSITIVE
(1004)
può
essere consultato in entrambe le direzioni ma non riflette
cambiamenti fatti al database mentre è aperto.
ResultSet.TYPE_SCROLL_SENSITIVE (1005) può essere
consultato in entrambe le direzioni e riflette ogni
cambiamento apportato.
Il tipo di scrolling può essere ricavato a tempo di esecuzione
tramite il metodo
int ResultSet.getType();
Java & PostgreSQL – Luca Ferrari
64 di 182
Posizionamento all'interno del ResultSet
L'interfaccia ResultSet mette a disposizione diversi metodi di
posizionamento:
beforeFirst(), afertLast() restituiscono true se si ci
si trova oltre la prima o l'ultima riga.
next(), previous() muovono di una riga in avanti o
indietro il cursore.
absolute(int), relative(int) spostano il cursore alla
riga specificata in modo assoluto o relativo (dalla
posizione corrente). Un indice negativo considera lo
spostamento all'indietro (es. -1 sposta all'ultima riga nel
caso di posizionamento assoluto o alla riga precedente nel
caso di posizionamento relativo).
getRow() ritorna il numero di riga (valore assoluto) a cui
si è posizionati correntemente.
Java & PostgreSQL – Luca Ferrari
65 di 182
Esempio di scrollable ResultSet
// ottengo il result set di una query
ResultSet rs = statement.executeQuery();
while( rs.next() ){
// esecuzione in avanti (solo righe pari)
int numero_riga = rs.getRow();
if( (numero_riga % 2) == 0 ){
System.out.println("######");
System.out.println("Id-Corso e descrizione: "
+ rs.getString(1) + " " + rs.getString(2));
}
}
// torno indietro e processo solo le righe dispari
while( rs.previous() ){
int numero_riga = rs.getRow();
}
if( (numero_riga % 2) != 0 ){...}
Java & PostgreSQL – Luca Ferrari
66 di 182
ResultSet aggiornabili
JDBC v2 permette di aggiornare (inserire,
modificare, cancellare) le righe di una query
usando
direttamente
il
ResultSet
corrispondente.
In sostanza non si è obbligati a creare un
comando di update/insert ma si può modificare il
valore di una colonna direttamente operando sui
risultati di una query.
E' un tentativo di nascondere i dettagli SQL e di
rendere il sistema più Object-Oriented.
Java & PostgreSQL – Luca Ferrari
67 di 182
ResultSet aggiornabili (2)
Per rendere tutto ciò possibile l'interfaccia ResultSet
include una serie di metodi simili a quelli per la
definizione dei parametri di un PreparedStatement.
Ad esempio:
updateString(int column, String value);
updateInt(int column, int value);
...
Esistono
poi
dei
metodi
speciali
per
aggiornare/inserire/cancellare e aggiornare i valori della
riga corrente:
updateRow();
insertRow();
deleteRow();
refresh();
Java & PostgreSQL – Luca Ferrari
68 di 182
Tipo di aggiornabilità
Il tipo di un ResultSet, relativamente alla sua aggiornabilità, può
essere specificato tramite due valori predefiniti:
ResultSet.CONCUR_READ_ONLY (1007) è
ResultSet che non può essere aggiornato.
il
classico
ResultSet.CONCUR_UPDATABLE (1008)
può
essere
aggiornato e quindi supporta inserimento, modifica e
cancellazione di righe.
Il tipo di aggiornabilità può essere ricavato a tempo di
esecuzione tramite il metodo
int ResultSet.getType();
Java & PostgreSQL – Luca Ferrari
69 di 182
Inserimento di una riga
L'inserimento di una riga in un ResultSet è
un'operazione leggermente più complessa
rispetto all'aggiornamento.
Occorre infatti istruire il ResultSet affinché
prepari posto per i valori della riga che si vuole
inserire, assegnare tali valori e confermare
(salvare) l'inserimento della nuova riga.
Se il ResultSet è di tipo scroll sensitive la riga
inserita sarà visibile senza bisogno di
aggiornamenti del ResultSet stesso.
Java & PostgreSQL – Luca Ferrari
70 di 182
Inserimento di una riga:
un approccio visuale
La procedura di inserimento di una nuova riga è simile alla
procedura usata in molti programmi visuali di accesso e modifica
dei database:
(1) viene ottenuto il ResultSet
relativo ad una specifica query;
(2) si passa oltre l'ultima riga,
usando una riga vuota come
spazio
temporaneo
per
l'inserimento dei valori. Si usa il
metodo
moveToInsertRow();
(3) vengono salvati i valori
inseriti. Si usa il metodo
insertRow().
Java & PostgreSQL – Luca Ferrari
71 di 182
Inserimento di una riga in un ResultSet
// processo il result set ed inserisco una nuova riga (max 4)
// ad ogni riga pari (non significa DOPO ogni riga pari!)
while (rs != null && rs.next()) {
int numero_riga = rs.getRow();
if( ((numero_riga % 2) == 0) && inserite < 4 ){
// inserisco una nuova riga in fondo al result set
rs.moveToInsertRow();
rs.updateString(1, "CorsoRS" + numero_riga);
rs.updateString(2, "Prova di inserimento da ResultSet");
rs.insertRow();
// torno alla riga cui ero prima dell'inserimento
rs.moveToCurrentRow();
inserite++;
}
String corsoID = rs.getString(1);
String descrizione = rs.getString(2);
System.out.println("Riga numero " + numero_riga + " ­ " + corsoID + " = " + descrizione);
}
Java & PostgreSQL – Luca Ferrari
72 di 182
JDBC:
Transazioni
Java & PostgreSQL – Luca Ferrari
73 di 182
JDBC e transazioni
Il driver JDBC lavora in modalità auto-commit: ogni
istruzione eseguita tramite uno Statement viene
automaticamente confermata.
E' come se si eseguissero transazioni monocomando.
Per prendere il controllo sulla transazione e
segnarne l'inizio e la fine (BEGIN-END) è
necessario disabilitare l'auto-commit del driver e
forzare un esplicito commit/rollback.
Java & PostgreSQL – Luca Ferrari
74 di 182
Schema di funzionamento
// disabilito auto commit
connection.setAutoCommit(false);
// effettuo del lavoro, INSERT, UPDATE, ecc. e tengo
// traccia se devo fare un rollback
...
rollback = true;
// tutto fatto
if( ! rollback )
connection.commit();
else
connection.rollback();
connection.setAutoCommit(true);
// ripristino auto-commit
Java & PostgreSQL – Luca Ferrari
75 di 182
Transazioni: livello di isolamento
E' possibile impostare il livello di isolamento di una transazione.
I livelli di isolamento sono memorizzati come costanti (interi)
nell'oggetto Connection e possono valere:
Connection.TRANSACTION_READ_COMMITTED impedisce che
ci siano dirty-reads, ma consente unrepeatable e phantom reads;
Connection.TRANSACTION_READ_UNCOMMITTED
non
consente nessun tipo di dirty, unrepeatable e phantom reads;
Connection.TRANSACTION_REPEATABLE_READ consente solo
phantom reads;
Connection.TRANSACTION_SERIALIZABLE
sono serializzabili.
Java & PostgreSQL – Luca Ferrari
le
transazioni
76 di 182
Transazioni: riassunto & considerazioni
L'inizio di una transazione deve essere eplicitato a livello di
connessione (Connection) disabilitando la modalità di
auto-commit.
Tutte le operazioni eseguite tramite uno Statement dopo
aver disabilitato l'auto-commit fanno parte della
transazione.
La fine di una transazione deve forzare un esplicito commit
o rollback sulla connessione.
Una volta terminata la transazione è necessario riabilitare
l'auto-commit, altrimenti le istruzioni succesive faranno
parte di una nuova transazione (il driver non ri-abilita da
solo l'auto-commit!).
Java & PostgreSQL – Luca Ferrari
77 di 182
JDBC:
DataSource
Java & PostgreSQL – Luca Ferrari
78 di 182
DataSource
Le API JDBC 2 introducono nel package javax.sql l'interfaccia
DataSource che si comporta come un wrapper attorno alla
connettività di un un database.
In sostanza un DataSource è un'oggetto che contiene tutte le
informazioni che servono per la connettività verso la sorgente dati
(database) quali URL, username, password, auto-commit mode, etc.
Lo scopo dei DataSource è quello di disaccoppiare l'impostazione
dei parametri di connettività (tipicamente amministrativi) dal loro
utilizzo (tipicamente applicativo).
Diverse applicazioni possono condividere la stessa DataSource (ad
esempio tramite JNDI); una modifica nel DataSource (ad esempio
modifica all'URL) non necessita nessun intervento sulle applicazioni
che lo utilizzano.
Java & PostgreSQL – Luca Ferrari
79 di 182
ConnectionPoolDataSource
Un
tipo
particolare
di
sorgente
dati
è
il
ConnectionPoolDataSource che consente di gestire le
connessioni in pool.
L'idea è quella di mantenere le connessioni al database in una
cache, in modo da renderle disponibili a diversi componenti
man mano che vengono richieste. Non venendo aperte/chiuse
di continuo, le connessioni vengono erogate con tempi di
risposta più bassi rispetto all'approccio classico.
L'utilizzo delle sorgenti dati con pool richiede però qualche
passaggio in più rispetto all'uso delle sorgenti dati semplici.
Viene comunque garantito il disaccoppiamento fra la parte
amministrativa e quella applicativa.
Java & PostgreSQL – Luca Ferrari
80 di 182
DataSource & PostgreSQL
I driver JDBC di PostgreSQL mettono a disposizione due tipi
principali
di
DataSource
(contenute
nel
package
org.postgresql.ds):
PGSimpleDataSource un DataSource senza alcun tipo di
pooling. Implementa javax.sql.DataSource e dispone di
un metodo getConnection() che fornisce la connessione
al database. Ad esempio:
DataSource dataSource = new PGSimpleDataSource();
...
Connection connection = dataSource.getConnection();
Java & PostgreSQL – Luca Ferrari
81 di 182
DataSource & PostgreSQL
PGPoolingDataSource un DataSource con capacità di
pooling
delle
connessioni.
Implementa
javax.sql.ConnectionPoolDataSource e mette a
disposizione un metodo getPooledConnection() che
fornisce un oggetto javax.sql.PooledConnection, al
quale si può richiedere la connessione tramite
getConnection(). Ad esempio:
ConnectionPoolDataSource dataSource = new PGConnectionPoolDataSource();
...
PooledConnection pooledConnection = dataSource.getPooledConnection();
Connection connection = pooledConnection.getConnection();
Java & PostgreSQL – Luca Ferrari
82 di 182
Impostazione delle proprietà di una sorgente dati
Le proprietà delle sorgenti dati vengono impostati tramite metodi set
(e lette dai relativi metodi get secondo le specifiche Java Beans).
Solitamente gli ambienti server mettono a disposizione dei file di
configurazione (es. XML) che consentono di specificare le varie
proprietà. L'ambiente container (es. Tomcat) si farà poi carico di
leggere tale file di configurazione e di impostare le proprietà di
connessione nella sorgente dati.
serverName
Indirizzo IP o nome del server database
databaseName
Nome del database cui collegarsi
portNumber
Porta a cui collegarsi tramite TCP/IP
user
Utente da usare per la connessione
password
Password per l'utente di cui sopra
initialConnections Numero di connessioni da creare all'avvio
maxConnections
Massimo numero di connessioni istanziabili
Java & PostgreSQL – Luca Ferrari
83 di 182
Esempio di uso di DataSource
// creazione di un data source per PostgreSQL
PGConnectionPoolDataSource dataSource = new org.postgresql.ds.PGConnectionPoolDataSource();
// impostazione dei parametri di connessione
dataSource.setDatabaseName("pgdaydb");
dataSource.setUser("luca"); dataSource.setPassword(null);
dataSource.setServerName("localhost"); dataSource.setDefaultAutoCommit(true);
// ora il datasource andrebbe esportato e reso disponibile
// ad altre applicazioni, ad esempio tramite JNDI
...
// prelevo la sorgenti dati
ConnectionPoolDataSource source = (ConnectionPoolDataSource) dataSource;
// oppure
// PooledConnection pooledConnection = // source.getPooledConnection();
Java & PostgreSQL – Luca Ferrari
84 di 182
Spring
La libreria Spring fornisce un sottoinsieme di
classi di utilità per la connessione a database e la
realizzazione di DAO.
<!­­ il template jdbc che deve essere inizializzato con una datasource ­­>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor­arg>
<ref bean="datasource" />
</constructor­arg>
</bean>
<!­­ inmpostazione della datasource ­­>
<bean id="datasource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.postgresql.Driver" />
<property name="url" value="jdbc:postgresql://192.168.1.2/hrpmdb" />
<property name="username" value="hrpm" />
<property name="password" value="HrPm" />
</bean>
Java & PostgreSQL – Luca Ferrari
85 di 182
Spring lato applicativo
Lato applicativo occorre ottenere il jdbctemplate e
usare uno dei suoi metodi per effettuare le query.
// insert the address in the database
this.jdbcTemplate.update( this.getInsertQuery(), toInsert.getPrimaryKey(), toInsert.getStreet(), toInsert.getCity(), toInsert.getCountry(), toInsert.getState(), toInsert.getPhone(), toInsert.getZipCode(), toInsert.getFax() );
Java & PostgreSQL – Luca Ferrari
86 di 182
Java Transaction API
Java & PostgreSQL – Luca Ferrari
87 di 182
Java Transaction API (JTA)
La Java Transaction API (JTA) è un insieme di interfaccie e
classi che forniscono una astrazione verso un sistema di
transazioni distribuite.
Una transazione distribuita è una transazione che coinvolge
più risorse (database) allo stesso momento. La necessità di
mantenere coerenti le risorse (database) richiede protocolli
appositi per la decisione di commit/rollback globale.
La JTA si appoggia su una implementazione specifica del
server, chiamata Java Transaction Service (JTS). Il JTS è
responsabile di implementare il Transaction Manager, che è il
componente che prende la decisione globale sul
commit/rollback.
Java & PostgreSQL – Luca Ferrari
88 di 182
Transazioni distribuite in Java
Java Application
User
UserTransaction
Transaction
Interface
Interface
Transaction
TransactionManager
Manager
JDBC
JDBCDriver
Driver
Application Server
Database
Engine
Java & PostgreSQL – Luca Ferrari
Database
Engine
89 di 182
Transazioni distribuite in Java
L'interfaccia
javax.transaction.UserTransaction
consente di definire i confini di una transazione distribuita,
ossia quale sezione di codice debba essere
eseguito come transazione. La sua implementazione
dipende dal container che si sta utilizzando.
L'interfaccia
javax.transaction.TransactionManager
consente la gestione delle risorse di transazione al
container.
L'interfaccia
javax.transaction.xa.XAResource
rappresenta una risorsa XA da gestire.
Java & PostgreSQL – Luca Ferrari
90 di 182
Transazioni distribuite: Commit a due
fasi
Il protocollo di commit a due fasi (Two Phase Commit) è un algoritmo
distribuito che consente a tutti i nodi che partecipano alla transazione di
accordarsi all'unisono per un commit o un rollback.
(fase 1: pre-commit) Il coordinatore invia un query-to-commit a tutti i partecipanti.
Ogni partecipante effettua le operazioni, aggiorna i propri log (undo e redo) e invia un
messaggio di commit o rollback al coordinatore.
(fase 2: commit) Quando il coordinatore ha tutti i messaggi dei partecipanti prende la
decisione:
se tutti hanno inviato un commit il coordinatore conferma il commit ai
partecipanti che rendono permanenti le modifiche e inviano un
acknowledgement. Il coordinatore chiude la transazione quando riceve tutti gli
acknoweldgement.
se almeno un partecipante ha inviato un rollback il coordinatore invia un
rollback ai partecipanti, che annullano la loro transazione locale e confermano
il rollback al coordinatore.
Java & PostgreSQL – Luca Ferrari
91 di 182
Risorse X/Open CAE
X/Open CAE specifica gli oggetti e le interfaccie da usare
nelle transazioni distribuite (Distributed Transaction
Processing: the XA specification): definisce cosa un gestore
di risorse debba fare per supportare le transazioni distribuite.
Il supporto XA del driver è fondamentale per consentire l'uso
con transazioni distribuite.
Il driver JDBC di PostgreSQL supporta XA dalla versione 8.1,
anche se senza supporto per il transaction interleaving
(capacità di usare la stessa connessione per differenti
transazioni contemporanee).
Java & PostgreSQL – Luca Ferrari
92 di 182
JTA & Driver JDBC
Nonostante il Transaction Manager sia il
componente più importante, il driver JDBC deve
fornire supporto a XADatasource, XAConnection e
XAResource.
Le classi di più alto livello JTA, quali ad esempio
UserTransaction e TransactionManager non sono
nello scope di un driver JDBC quanto di un
application server.
Java & PostgreSQL – Luca Ferrari
93 di 182
Identificazione di una transazione
Una transazione è identificata da un Transaction ID
composto da tre parti fondamentali:
format ID: specifica il naming schema delle
transazioni (es. OSI CCR – Commitment,
Concurrency e Recovery).
branch qualifier: un branch rappresenta una singola
richiesta ad un resource manager.
global transaction id: un identificativo globale della
transazione in tutto il sistema.
L'implementazione di un transaction
dipende dal container/sistema utilizzato.
id (javax.transaction.xa.Xid)
Java & PostgreSQL – Luca Ferrari
94 di 182
Creazione di uno Xid
public class PgDayXid implements Xid{
// Il formatID specifica quale specifica si sta usando. // Se vale 0 allora si sta usando la specifica OSI­CCR // (OSI­Commitment, Concurrency and Recovery).
// Se vale ­1 allora l'Xid non è valido, // mentre se è superiore a zero allora indica // una specifica proprietaria.
protected int formatID = 0;
// identificativo globale della transazione corrente. (0­64 bytes)
protected byte[] globalTransactionId = null;
// identificativo del ramo della transazione corrente. (0­64 bytes)
protected byte[] branchQualifier = null;
public byte[] getBranchQualifier() { return this.branchQualifier; }
public int getFormatId() { return this.formatID; }
public byte[] getGlobalTransactionId() { return this.globalTransactionId; }
Java & PostgreSQL – Luca Ferrari
95 di 182
Creazione di uno Xid
public void setGlobalTransactionId(byte[] gtid){
// tengo conto solo dei primi 64 bytes...
if( gtid != null && gtid.length > 64 ){
System.arraycopy( gtid, 0, this.globalTransactionId, 0, 64 );
}
else
this.globalTransactionId = gtid;
}
public void setBranchQualifier(byte[] bq){
...
}
public void setFormatId(int id){
...
}
}
Java & PostgreSQL – Luca Ferrari
96 di 182
Utilizzo di JTA
public static void main(String[] args) throws Exception {
// 1) creazione di un datasource agganciato al database
PGXADataSource dataSource = new PGXADataSource();
dataSource.setDatabaseName("pgday");
// nome del database
dataSource.setUser("luca");
// username
dataSource.setPassword("fluca");
// password dell'utente
dataSource.setServerName("localhost");
// indirizzo di connessione
// del database
// 2) ottengo una connessione al database
System.out.println("Tentativo di connessione al database...");
XAConnection connection = dataSource.getXAConnection();
System.out.println("Connesso!");
// 3) ottengo una risorsa per proteggere la transazione
XAResource resource = connection.getXAResource();
Java & PostgreSQL – Luca Ferrari
97 di 182
Utilizzo di JTA
// 4) devo avere un id della transazione che sto per usare
PgDayXid identifier = new PgDayXid();
identifier.setBranchQualifier(
new byte[] {0x01, 0x02, 0x03, 0x04, 0x05});
identifier.setGlobalTransactionId(
new byte[] {0x05, 0x04, 0x03, 0x02, 0x01});
identifier.setFormatId(100);
// 5) eseguo la transazione
try{
// 6) inizio della transazione
resource.start(identifier, XAResource.TMNOFLAGS);
// 7) esecuzione di operazioni JDBC
Connection jdbcConnection = connection.getConnection();
jdbcConnection.setAutoCommit(false); // bug del driver!
Statement statement = jdbcConnection.createStatement();
String sql = "INSERT INTO corso(corsoID, descrizione)
VALUES('XA1', 'Corso su JTA')";
int inserted = statement.executeUpdate(sql);
System.out.println("Sono state inserite " + inserted + " righe");
Java & PostgreSQL – Luca Ferrari
98 di 182
Utilizzo di JTA
// 8) ho terminato la transazione
resource.end(identifier, XAResource.TMSUCCESS);
// 9) controllo se il transaction manager consente il commit
// (fase di precommit)
int commit = resource.prepare(identifier); System.out.println("Valore del prepare " + commit);
if( commit == XAResource.XA_OK )
// commit definitivo
resource.commit(identifier, // identificativo transazione
false); // false = commit 2 fasi
else
resource.rollback(identifier);
}catch(XAException e){
e.printStackTrace();
}
}
Java & PostgreSQL – Luca Ferrari
99 di 182
Sospensione di una transazione
E' possibile sospendere una transazione distribuita
per dare modo al processo corrente di eseguire
operazioni locali.
E' necessario terminare la transazione distribuita e
avviarla nuovamente specificando come flag
rispettivamente TMSUSPEND e TMRESUME.
// 8) sospensione della transazione distribuita
resource.end(identifier, XAResource.TMSUSPEND);
// 8 bis) effettuo degli statement fuori dalla transazione // distribuita usando lo stesso oggetto Statement
statement.executeUpdare(...);
// 8 tris) riprendo la transazione distribuita
resource.start(identifier, XAResource.TMRESUME);
Java & PostgreSQL – Luca Ferrari
100 di 182
JDBC & SSL
Java & PostgreSQL – Luca Ferrari
101 di 182
Connessioni SSL
PostgreSQL supporta nativamente le connessioni tramite
SSL/TLS da parte dei client (si veda il capitolo 16 del manuale).
Solitamente il server PostgreSQL accetta sia connessioni in
chiaro che cifrate sulla stessa porta, negoziando dinamicamente il
tipo di connessione ricevuta.
Una connessione in chiaro invia i pacchetti (es. le query e i risultati)
senza cifrarli, e quindi questi sono osservabili sulla rete. Una
connessione SSL utilizza uno schema di cifratura per rendere
illeggibili da terzi i pacchetti in transito.
Java & PostgreSQL – Luca Ferrari
102 di 182
Connessioni SSL: impostazioni del server
Il server deve essere stato compilato con il supporto per SSL, e i
certificati devono essere stati generati e si devono trovare nella
directory $PGDATA.
Java & PostgreSQL – Luca Ferrari
103 di 182
Connessioni SSL: test
Per verificare il funzionamento del supporto SSL è possibile usare il
client da riga di comando.
Di default viene usata
una connessione in
chiaro
La suite di cifratura è
disponibile
(connessione esplicita a
localhost)
Java & PostgreSQL – Luca Ferrari
104 di 182
Java & SSL
Ci sono due modi principali di utilizzo di SSL nelle connessioni a
PostgreSQL:
connessione verificata: il certificato del server è stato
correttamente importato nel keystore (personale o di sistema) di
Java, ad indicare che il certificato è noto e corretto. In tal caso, il
driver JDBC di PostgreSQL effettua i controlli necessari per
validare il certificato ed evitare attacchi man-in-the-middle.
connessione non verificata: non è possibile importare il
certificato nel keystore, e quindi ci si fida al volo (con i rischi che
ne derivano) del certificato presentato dal server. In questo caso
occorre usare una socket factory messa a disposizione dal driver
JDBC di PostgreSQL.
Java & PostgreSQL – Luca Ferrari
105 di 182
Connessione verificata
Il driver JDBC di PostgreSQL si occupa di tutta la parte di
scambio di chiavi e connessione, ma occorre importare il
certificato del server nel keystore:
(1) il certificato del server deve essere tradotto in una forma che
keytool può comprendere. A tal fine lo si converte come file DER
(ASN1-DER; formato senza header) dal formato PEM (standard
per openssl, presenta un header testuale).
Java & PostgreSQL – Luca Ferrari
106 di 182
Connessione verificata
(2) il certificato deve essere importato nel keystore (in questo
caso privato dell'utente). Al certificato viene associata l'alias
pgday, che rappresenta una sorta di username (in realtà è il
proprietario del certificato). Il keystore generato, essendo usato
per la verifica di un'identità e non strettamente per la cifratura,
viene anche denominato trustore.
Java & PostgreSQL – Luca Ferrari
107 di 182
Connessione verificata
(3) l'URL di connessione deve contenere il parametro ssl=true,
al fine di indicare al driver di usare la connessione SSL (viene
usata la factory socket di default).
Connection connection = DriverManager.getConnection(
"jdbc:postgresql://localhost/pgday?loglevel=2&ssl=true", "luca", "fluca");
(4) occorre specificare come parametri alla JVM quale
keystore usare e quale password.
[luca@fluca:~]$ java ­Djavax.net.ssl.trustStore=$HOME/keystore ­Djavax.net.ssl.trustStorePassword=aglets it.pgday.lferrari.PgJDBC1
Java & PostgreSQL – Luca Ferrari
108 di 182
Connessione verificata
La connessione impiega qualche secondo per essere
convertita da normale a cifrata. Il traffico in circolazione
non è più in chiaro.
Java & PostgreSQL – Luca Ferrari
109 di 182
Connessione non verificata
E' possible stabilire una connessione SSL senza aver verificato
(importato) il certificato del server. A tal scopo è sufficiente
impostare la factory di socket SSL come parametro della URL di
connessione a org.postgresql.ssl.NonValidatingFactory. Non è più
necessario specificare il keystore e la sua password.
Connection connection = DriverManager.getConnection(
"jdbc:postgresql://localhost/pgday?loglevel=2&ssl=true
&sslfactory=org.postgresql.ssl.NonValidatingFactory",
"luca", "fluca");
[luca@fluca:~]$ java it.pgday.lferrari.PgJDBC1
Java & PostgreSQL – Luca Ferrari
110 di 182
org.postgresql.Driver
Java & PostgreSQL – Luca Ferrari
111 di 182
Registrazione del Driver PostgreSQL
Il Driver si auto-registra in modo statico richiamando il
metodo (static) registerDriver di DriverManager.
La registrazione statica anziché nel costruttore del Driver
stesso (come molti alti driver fanno) impedisce una
registrazione multipla dello stesso driver all'interno della
JVM.
static {
static {
try{
try{
java.sql.DriverManager.registerDriver(new Driver());
java.sql.DriverManager.registerDriver(new Driver());
}
}
catch (SQLException e)
catch (SQLException e)
{ e.printStackTrace(); }
{ e.printStackTrace(); }
}
// org.postgresql.Driver
}
// org.postgresql.Driver
Java & PostgreSQL – Luca Ferrari
112 di 182
Come il DriverManager sceglie il Driver appropriato...
private static Connection getConnection(String url, Properties info, private static Connection getConnection(String url, Properties info, ClassLoader callerCL) throws SQLException {
ClassLoader callerCL) throws SQLException {
java.util.Vector drivers = null;
java.util.Vector drivers = null;
...
...
synchronized (DriverManager.class){ synchronized (DriverManager.class){ drivers = readDrivers; drivers = readDrivers; }
}
...
...
SQLException reason = null;
SQLException reason = null;
for (int i = 0; i < drivers.size(); i++) {
for (int i = 0; i < drivers.size(); i++) {
DriverInfo di = (DriverInfo)drivers.elementAt(i); DriverInfo di = (DriverInfo)drivers.elementAt(i); Connection result = di.driver.connect(url, info);
Connection result = di.driver.connect(url, info);
if (result != null) {
if (result != null) {
class DriverInfo {
return (result);
class DriverInfo {
return (result);
Driver driver;
} Driver driver;
} Class driverClass;
// codice di gestione Class driverClass;
// codice di gestione String driverClassName;
// errori ...
String driverClassName;
// errori ...
}
// java.sql.DriverManager
}}
}
// java.sql.DriverManager
...
...
}}
// java.sql.DriverManager
// java.sql.DriverManager
Java & PostgreSQL – Luca Ferrari
113 di 182
connect(..)
Il metodo connect(..) del driver PostgreSQL effettua la
connessione effettiva al database:
si verifica che l'URL di connessione sia gestibile ed appropriato
tramite il metodo acceptsURL(..);

se non è stato specificato un timeout di login viene tentata la
connessione direttamente nel thread chiamante (metodo
makeConnection(..));

se è stato specificato un timeout di login viene creato un nuovo
thread (ConnectionThread), che cercherà di effettuare la
connessione (metodo run()) mentre il thread chiamante resterà
in attesa per un tempo pari al timeout specificato (metodo
getResult(..)).

Java & PostgreSQL – Luca Ferrari
114 di 182
connect(..)
public java.sql.Connection connect(String url, Properties info) public java.sql.Connection connect(String url, Properties info) throws SQLException {
throws SQLException {
...
...
try { // se non ho nessun timeout tento la connessione
try { // se non ho nessun timeout tento la connessione
// direttamente (opzione di default)
// direttamente (opzione di default)
long timeout = timeout(props);
long timeout = timeout(props);
if (timeout <= 0)
if (timeout <= 0)
return makeConnection(url, props);
return makeConnection(url, props);
// tento la connessione tramite un altro thread
// tento la connessione tramite un altro thread
ConnectThread ct = new ConnectThread(url, props);
ConnectThread ct = new ConnectThread(url, props);
new Thread(ct, new Thread(ct, "PostgreSQL JDBC driver connection thread").start();
"PostgreSQL JDBC driver connection thread").start();
return ct.getResult(timeout);
return ct.getResult(timeout);
} catch (PSQLException ex1) {
} catch (PSQLException ex1) {
throw ex1;
throw ex1;
} catch (Exception ex2) {
} catch (Exception ex2) {
throw new PSQLException(...);
throw new PSQLException(...);
}
}
}}
// org.postgresql.Driver
// org.postgresql.Driver
Java & PostgreSQL – Luca Ferrari
115 di 182
PGStream: un oggetto magico
PGStream rappresenta un wrapper attorno
connessione verso il database PostgreSQL.
ad
una
Esso
contiene
un
riferimento
ad
un
oggetto
java.net.Socket che rappresenta la connessione al
database e agli stream per la lettura e la scrittura dei dati su
tale socket (sottoclassi specifiche di OutpuStream e
InputStream).
PGStream mette a disposizione dei metodi di utilità per la
scrittura/lettura di byte, interi (2,4 byte), char, stringhe, etc.
che vengono usati per l'invio di informazioni secondo il
protocollo di comunicazione V2 o V3.
Java & PostgreSQL – Luca Ferrari
116 di 182
Interfacce particolari
QueryExecutor: gestisce i messaggi specifici di una
versione di protocollo (es. V3) per l'esecuzione di query. Una
implmentazione
è
ad
esempio
org.postgresql.core.v3.QueryExecutorImpl.
ResultHandler: gestisce la costruzione progressiva di
ResultSet (aggiunge una tupla man mano che viene ricevuta
dalla rete), warning ed errori.
SimpleQuery: gestione dei dati di una query singola.
Java & PostgreSQL – Luca Ferrari
117 di 182
Comunicazione
FrontEnd-BackEnd
La comunicazione fra il lato applicativo (FrontEnd – driver) e il
server (BackEnd) avviene tramite l'uso di un protocollo a scambio
di messaggi.
Il protocollo, giunto alla sua terza revisione (PostgreSQL >= 7.4)
viene comunemente denominato protocollo V3.
Java & PostgreSQL – Luca Ferrari
118 di 182
Protocollo V3:
formato dei messaggi
Un messaggio è un pacchetto contenente diversi
byte rappresentati l'informazione.
message_type
(1 byte)
length
(4 bytes)
content
(length – 4 bytes)
il primo byte del messaggio identifica il
tipo del messaggio stesso
i successivi quattro byte specificano la
lunghezza del messaggio stesso (incluso il
dato di lunghezza stesso)
i rimanenti byte rappresentano
contenuto del messaggio
Java & PostgreSQL – Luca Ferrari
il
119 di 182
PreparedStatement vs Portal
La fase di parse genera un PreparedStatement, ossia
un oggetto che rappresenta l'interpretazione lessicale di
una query.
Tale oggetto subirà poi il bind dei parametri venendo
promosso in un Portal, un oggetto pronto ad eseguire la
query.
Sostanzialmente un Portal è un handle per una query
pronta da eseguire o che sta eseguendo, una sorta di
cursore (che funziona anche per query non SELECT).
Un portale puo' essere identificato da un nome, oppure
rimanere unnamed se si usa il portale di default.
Java & PostgreSQL – Luca Ferrari
120 di 182
Protocollo V3:
Simple & Extended Query
Il protocollo prevede due modalità principali di
funzionamento:
Simple Query: viene inviato un messaggio con la
query e questa viene immediatamente eseguita; i
risultati vengono poi inviati al frontend.
Extended Query: viene inviata la query ma non
viene eseguita immediatamente. In una fase
successiva si fa il bind dei parametri della query, si
esegue la query e si inviano i risultati al frontend.
Simple Query : Statement = Extended Query : PreparedStatement
Java & PostgreSQL – Luca Ferrari
121 di 182
Simple Query
BackEnd
FrontEnd
Query
contiene la string SQL
della query da eseguire
RowDescription
indica il layout delle colonne
ritornate dalla query
RowData
dati di una singola riga
RowData
RowData
ReadyForQuery
indica che il backend è pronto
ad accettare una nuova query
Java & PostgreSQL – Luca Ferrari
122 di 182
Extended Query
BackEnd
FrontEnd
Parse (stringa SQL, [nome prepared statement, nome portale])
ParseComplete
Bind (lista parametri, [nome prepared statement, nome portale])
BindComplete
Execute ([nome portale])
RowData
dati di una singola riga
RowData
RowData
Sync
sincronizzazione per errori
ReadyForQuery
il backend è pronto
Java & PostgreSQL – Luca Ferrari
123 di 182
Query: overview
Viene creato un oggetto Statement dal
Connection corrente. Tipicamente si passa
attraverso AbstractJdbc2Connection, che
richiama il tipo di connessione corretto (es.
Jdbc4Connection).
Il metodo executeQuery(..) viene sempre
richiamato su AbstractJdbc2Statement,
che mediante QueryExecutor ottiene un
oggetto SimpleQuery che implementa il
protocollo di comunicazione FrontEnd e
BackEnd opportuno (es. v3).
Java & PostgreSQL – Luca Ferrari
124 di 182
Query: overview
Il
metodo
execute(..)
di
AbstractJdbc2Statement accetta l'oggetto
query da eseguire e richiama il metodo
execute(..) sul QueryExecutor. Al metodo
viene
anche
passato
un
oggetto
ResultHandler che si occupa di gestire
risultati (ResultSet), errori e warning.
Il QueryExecutor invia i messaggi necessari
al server, in particolare tramite il metodo
sendQuery(..) e sendOneQuery(..) invia
la query da eseguire in formato testuale.
Java & PostgreSQL – Luca Ferrari
125 di 182
Query: overview
Il QueryExecutor processa la risposta del server tramite
processResult(..). Questo metodo utilizza l'oggetto
PGStream per leggere i messaggi in arrivo dal server.
Viene usato un meccanismo ad eventi: a seconda del
messaggio letto viene richiamato un metodo opportuno sul
ResultHandler. In questo modo l'handler può, ad
esempio, memorizzare le tuple man mano che vengono
lette.
Si richiede al ResultHandler di restituire il ResultSet.
Questo viene ritornato indietro lungo lo stack fino al lato
applicativo.
Java & PostgreSQL – Luca Ferrari
126 di 182
Creazione di uno Statement
public java.sql.Statement createStatement() throws SQLException {
public java.sql.Statement createStatement() throws SQLException {
// crea uno statement per un Resultset classico
// crea uno statement per un Resultset classico
return createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, return createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet.CONCUR_READ_ONLY);
}}
// org.postgresql.jdbc2.AbstractJdbc2Connection
// org.postgresql.jdbc2.AbstractJdbc2Connection
public java.sql.Statement createStatement(int resultSetType, public java.sql.Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) int resultSetConcurrency, int resultSetHoldability) throws SQLException {
throws SQLException {
// creazione di uno statement di livello 4
// creazione di uno statement di livello 4
Jdbc4Statement s = new Jdbc4Statement(this, resultSetType, Jdbc4Statement s = new Jdbc4Statement(this, resultSetType, resultSetConcurrency, resultSetHoldability);
resultSetConcurrency, resultSetHoldability);
s.setPrepareThreshold(getPrepareThreshold());
s.setPrepareThreshold(getPrepareThreshold());
return s;
return s;
}}
// org.postgresql.jdbc4.Jdbc4Connection
// org.postgresql.jdbc4.Jdbc4Connection
La creazione di uno Statement implica ancora una volta il passaggio fra le classi dei vari
livelli JDBC. In particolare si parte dal livello 2 (il minimo) e si arriva fino al livello 4 creando
un Jdbc4Statement.
Java & PostgreSQL – Luca Ferrari
127 di 182
Esecuzione di una query
public ResultSet executeQuery(String p_sql) throws SQLException {
public ResultSet executeQuery(String p_sql) throws SQLException {
// se ho una query preprata (PreparedStament) non processare
// se ho una query preprata (PreparedStament) non processare
// un'altra query SQL (p_sql)
// un'altra query SQL (p_sql)
if (preparedQuery != null)
if (preparedQuery != null)
throw new PSQLException(...);
throw new PSQLException(...);
// eseguo la query specificata
// eseguo la query specificata
if (!executeWithFlags(p_sql, 0))
if (!executeWithFlags(p_sql, 0))
throw new PSQLException(...);
throw new PSQLException(...);
// sono stati ritornati troppi result set in una sola volta
// sono stati ritornati troppi result set in una sola volta
if (result.getNext() != null)
if (result.getNext() != null)
throw new PSQLException(...);
throw new PSQLException(...);
return (ResultSet)result.getResultSet();
return (ResultSet)result.getResultSet();
}}
// org.postgresql.jdbc2.AbstractJdbc2Statement
// org.postgresql.jdbc2.AbstractJdbc2Statement
Il metodo fondamentale per l'esecuzione di una query è executeWithFlags. Mediante
tale metodo viene creato un oggetto SimpleQuery che si occuperà dello scambio di
messaggi con il backend per il protocollo relativo.
Java & PostgreSQL – Luca Ferrari
128 di 182
Esecuzione di una query
public boolean executeWithFlags(String p_sql, int flags) public boolean executeWithFlags(String p_sql, int flags) throws SQLException {
throws SQLException {
// controlla se lo statement è chiuso (nel caso solleva // controlla se lo statement è chiuso (nel caso solleva // una eccezione)
// una eccezione)
checkClosed();
checkClosed();
// effettua alcune sostituzione nella query (es. escaping)
// effettua alcune sostituzione nella query (es. escaping)
p_sql = replaceProcessing(p_sql);
p_sql = replaceProcessing(p_sql);
Query simpleQuery = Query simpleQuery = connection.getQueryExecutor().createSimpleQuery(p_sql);
connection.getQueryExecutor().createSimpleQuery(p_sql);
execute(simpleQuery, null, QueryExecutor.QUERY_ONESHOT | flags);
execute(simpleQuery, null, QueryExecutor.QUERY_ONESHOT | flags);
this.lastSimpleQuery = simpleQuery;
this.lastSimpleQuery = simpleQuery;
// result è un tipo ResultWrapper, che contiene // result è un tipo ResultWrapper, che contiene // anche il ResultSet
// anche il ResultSet
return (result != null && result.getResultSet() != null);
return (result != null && result.getResultSet() != null);
}}
// org.postgresql.jdbc2.AbstractJdbc2Statement
// org.postgresql.jdbc2.AbstractJdbc2Statement
public Query createSimpleQuery(String sql) {
public Query createSimpleQuery(String sql) {
return parseQuery(sql, false);
return parseQuery(sql, false);
}}
// org.postgresql.core.v3.QueryExecutorImpl
// org.postgresql.core.v3.QueryExecutorImpl
Java & PostgreSQL – Luca Ferrari
129 di 182
Esecuzione di una query
protected void execute(Query queryToExecute, protected void execute(Query queryToExecute, ParameterList queryParameters, int flags) throws SQLException {
ParameterList queryParameters, int flags) throws SQLException {
// chiusura di query precedenti e pulizia di warning ed errori
// chiusura di query precedenti e pulizia di warning ed errori
// che potevano provenire da query precedenti
// che potevano provenire da query precedenti
StatementResultHandler handler = new StatementResultHandler();
StatementResultHandler handler = new StatementResultHandler();
result = null;
result = null;
connection.getQueryExecutor().execute(queryToExecute,
connection.getQueryExecutor().execute(queryToExecute,
queryParameters,
queryParameters,
handler,
handler,
maxrows,
maxrows,
fetchSize,
fetchSize,
flags);
flags);
result = firstUnclosedResult = handler.getResults();
result = firstUnclosedResult = handler.getResults();
}}
// org.postgresql.jdbc2.AbstractJdbc2Statement
// org.postgresql.jdbc2.AbstractJdbc2Statement
Si prepara un handler per il risultato ed eventuali errori e si passa la query e l'handler al
metodo execute(..) del QueryExecutor (la sua implementazione per il protocollo V3)
affinché gestisca i messaggi.
Java & PostgreSQL – Luca Ferrari
130 di 182
Esecuzione di una query
(lettura risultati)
protected void processResults(ResultHandler handler, int flags) protected void processResults(ResultHandler handler, int flags) throws IOException {
throws IOException {
Vector tuples = new Vector(); Field[] fields = null;
Vector tuples = new Vector(); Field[] fields = null;
...
...
while (!endQuery) {
while (!endQuery) {
c = pgStream.ReceiveChar();
c = pgStream.ReceiveChar();
switch (c){
switch (c){
case 'D': // messaggio di tipo DataRow
case 'D': // messaggio di tipo DataRow
Object tuple = null;
Object tuple = null;
try {
try {
tuple = pgStream.ReceiveTupleV3();
tuple = pgStream.ReceiveTupleV3();
} catch(OutOfMemoryError oome) { ... }
} catch(OutOfMemoryError oome) { ... }
if (!noResults)
if (!noResults)
tuples.addElement(tuple);
tuples.addElement(tuple);
break;
break;
...
...
// org.postgresql.core.v3.QueryExecutorImpl
// org.postgresql.core.v3.QueryExecutorImpl
Java & PostgreSQL – Luca Ferrari
131 di 182
Esecuzione di una query
(lettura risultati)
case 'C': // command status (fine esecuzione execute)
case 'C': // command status (fine esecuzione execute)
String status = receiveCommandStatus();
String status = receiveCommandStatus();
...
...
if (fields != null || tuples != null) { if (fields != null || tuples != null) { handler.handleResultRows(currentQuery, fields, tuples, null);
handler.handleResultRows(currentQuery, fields, tuples, null);
fields = null; tuples = null;
fields = null; tuples = null;
}
}
else
else
interpretCommandStatus(status, handler);
interpretCommandStatus(status, handler);
...
...
break;
break;
case 'Z': // ready for query
case 'Z': // ready for query
receiveRFQ();
receiveRFQ();
endQuery = true; // termina il ciclo while
endQuery = true; // termina il ciclo while
// pulizia degli oggetti in memoria e delle code
// pulizia degli oggetti in memoria e delle code
...
...
break;
break;
...
...
}
}
}
}
}}
// org.postgresql.core.v3.QueryExecutorImpl
// org.postgresql.core.v3.QueryExecutorImpl
Java & PostgreSQL – Luca Ferrari
132 di 182
Query: traffico di rete
Analizzando il traffico di rete con uno sniffer è possibile vedere i singoli messaggi
inviati da e per il server. Nell'esempio qui sopra si nota il messaggio di Parse,
con la query inviata, seguito da vari messaggi uno dei quali Execute.
Java & PostgreSQL – Luca Ferrari
133 di 182
Query: traffico di rete
In questo caso si vede il messaggio RowDescription che precede una serie di
messaggi DataRow, ciascuno con indicazione del numero, lunghezza e valore
delle colonne.
Java & PostgreSQL – Luca Ferrari
134 di 182
Query: traffico di rete
Messaggio Parse contenente la
query e il nome dello statement
di riferimento sul server (null).
Il server invia un
RowDescription e di seguito
tutte le righe tramite una serie di
DataRow.
Il server invia i messaggi di
chiusura, fra i quali
ReadyForQuery.
Java & PostgreSQL – Luca Ferrari
135 di 182
Esecuzione di una query parametrica
statement2.setInt(1, corsoPK); statement2.setInt(1, corsoPK); // lato applicativo
// lato applicativo
La fase di bind in memoria prevede che il parametro sia associato alla sua posiziona nella
query (index), al suo valore (rappresentato come stringa) e al suo tipo (OID) affinché il
BackEnd possa capire come trattare il dato stesso.
I parametri sono contenuti in oggetti che implementano ParameterList a seconda del
protocollo utilizzato (ad es. V3ParameterList).
public void setInt(int parameterIndex, int x) throws SQLException {
public void setInt(int parameterIndex, int x) throws SQLException {
checkClosed(); checkClosed(); bindLiteral(parameterIndex, Integer.toString(x), Oid.INT4);
bindLiteral(parameterIndex, Integer.toString(x), Oid.INT4);
}}
private void bindLiteral(int paramIndex,String s,int oid) private void bindLiteral(int paramIndex,String s,int oid) throws SQLException {
throws SQLException {
if(adjustIndex)
if(adjustIndex)
paramIndex­­;
paramIndex­­;
// registro il parametro in un contenitore V3ParameterList
// registro il parametro in un contenitore V3ParameterList
preparedParameters.setLiteralParameter(paramIndex, s, oid);
preparedParameters.setLiteralParameter(paramIndex, s, oid);
} // org.postgresql.jdbc2.AbstractJdbc2Statement
} // org.postgresql.jdbc2.AbstractJdbc2Statement
Java & PostgreSQL – Luca Ferrari
136 di 182
Esecuzione di una query parametrica
Il metodo executeQuery(), invocato dopo il bind dei parametri, richiama il metodo
executeWithFlags(..) già visto in precedenza e usato anche per query non
parametriche.
Ripercorrendo
lo
stack
di
chiamata
si
giunge
a
QueryExecutor.execute(..) che questa volta ha impostato i parametri della query. Da
qui si passa poi a QueryExecutor.sendOneQuery(..) che provvede a sostituire ad ogni
parametro (indicato con ?) un $n (con n valore della posizione partendo da 1). In coda alla
query compare poi l'array degli OID dei tipi dei parametri.
public java.sql.ResultSet executeQuery() throws SQLException {
public java.sql.ResultSet executeQuery() throws SQLException {
if (!executeWithFlags(0))
if (!executeWithFlags(0))
throw new PSQLException(...);
throw new PSQLException(...);
if (result.getNext() != null)
if (result.getNext() != null)
throw new PSQLException(...);
throw new PSQLException(...);
return (ResultSet) result.getResultSet();
return (ResultSet) result.getResultSet();
} // org.postgresql.jdbc2.AbstractJdbc2Statement
} // org.postgresql.jdbc2.AbstractJdbc2Statement
FE=> Parse(stmt=null,query="SELECT cognome, nome FE=> Parse(stmt=null,query="SELECT cognome, nome FROM partecipante WHERE corsoPK=$1",oids={23})
FROM partecipante WHERE corsoPK=$1",oids={23})
Java & PostgreSQL – Luca Ferrari
137 di 182
Esecuzione di una query parametrica
private void sendBind(SimpleQuery query, SimpleParameterList params, private void sendBind(SimpleQuery query, SimpleParameterList params, Portal portal) throws IOException {
Portal portal) throws IOException {
// informazioni su portale e statement (se esistono)
// informazioni su portale e statement (se esistono)
String statementName = query.getStatementName();
String statementName = query.getStatementName();
byte[] encodedStatementName = query.getEncodedStatementName();
byte[] encodedStatementName = query.getEncodedStatementName();
byte[] encodedPortalName = (portal == null ? null : byte[] encodedPortalName = (portal == null ? null : portal.getEncodedPortalName());
portal.getEncodedPortalName());
...
...
pgStream.SendChar('B'); pgStream.SendChar('B'); // messaggio Bind
// messaggio Bind
pgStream.SendInteger4((int)encodedSize); // dimensione messaggio
pgStream.SendInteger4((int)encodedSize); // dimensione messaggio
if (encodedPortalName != null)
// eventuale portale
if (encodedPortalName != null)
// eventuale portale
pgStream.Send(encodedPortalName); pgStream.Send(encodedPortalName); pgStream.SendChar(0); pgStream.SendChar(0); if (encodedStatementName != null)
if (encodedStatementName != null)
pgStream.Send(encodedStatementName); pgStream.Send(encodedStatementName); // eventuale statement
// eventuale statement
pgStream.SendChar(0); pgStream.SendChar(0); ...
...
// org.postgresql.core.v3.QueryExecutorImpl
// org.postgresql.core.v3.QueryExecutorImpl
Java & PostgreSQL – Luca Ferrari
138 di 182
Esecuzione di una query parametrica
// invio tipo e numero dei parametri
// invio tipo e numero dei parametri
for (int i = 1; i <= params.getParameterCount(); ++i)
for (int i = 1; i <= params.getParameterCount(); ++i)
pgStream.SendInteger2(params.isBinary(i) ? 1 : 0); pgStream.SendInteger2(params.isBinary(i) ? 1 : 0); pgStream.SendInteger2(params.getParameterCount()); pgStream.SendInteger2(params.getParameterCount()); ...
...
for (int i = 1; i <= params.getParameterCount(); ++i) {
for (int i = 1; i <= params.getParameterCount(); ++i) {
if (params.isNull(i))
if (params.isNull(i))
pgStream.SendInteger4( ­1); // dimensione ­1 => NULL
pgStream.SendInteger4( ­1); // dimensione ­1 => NULL
else {
else {
// dimensione del parametro
// dimensione del parametro
pgStream.SendInteger4(params.getV3Length(i)); pgStream.SendInteger4(params.getV3Length(i)); try{ // valore del parametro
try{ // valore del parametro
params.writeV3Value(i, pgStream); params.writeV3Value(i, pgStream); }catch (PGBindException be) {
}catch (PGBindException be) {
bindException = be;
bindException = be;
}
}
}
}
}
}
...
...
} // org.postgresql.core.v3.QueryExecutorImpl
} // org.postgresql.core.v3.QueryExecutorImpl
Java & PostgreSQL – Luca Ferrari
139 di 182
Esecuzione di INSERT/UPDATE
I passi fondamentali sono simili a quanto visto in precedenza:
si
deve
ottenere
(AbstractJdbc2Connection)
(AbstractJdbc2Statement);
dall'oggetto
uno
Connection
Statement
si richiede allo Statement (AbstractJdbc2Statement) di
eseguire
la
modifica
mediante
il
metodo
JDBC
executeUpdate(String sql);
si ottiene come valore di ritorno il numero di record modificati nella
base di dati (command status)
Il command status viene interpretato letteralmente, ossia dalla stringa di command
status inviata dal BackEnd si estraggono i valori ritornati dal server.
Java & PostgreSQL – Luca Ferrari
140 di 182
Esecuzione di un INSERT/UPDATE
public int executeUpdate(String p_sql) throws SQLException {
public int executeUpdate(String p_sql) throws SQLException {
...
...
// eseguo la query tramite il QueryExecutor
// eseguo la query tramite il QueryExecutor
// non mi aspetto risultati (QUERY_NO_RESULTS)
// non mi aspetto risultati (QUERY_NO_RESULTS)
if (executeWithFlags(p_sql, QueryExecutor.QUERY_NO_RESULTS))
if (executeWithFlags(p_sql, QueryExecutor.QUERY_NO_RESULTS))
// non dovrei avere alcun risultato
// non dovrei avere alcun risultato
throw new PSQLException(...);
throw new PSQLException(...);
// restituisco il numero di record aggiornati/inseriti
// restituisco il numero di record aggiornati/inseriti
return getUpdateCount();
return getUpdateCount();
} // org.postgresql.jdbc2.AsbtractJdbc2Statement
} // org.postgresql.jdbc2.AsbtractJdbc2Statement
public boolean executeWithFlags(String p_sql, int flags) public boolean executeWithFlags(String p_sql, int flags) throws SQLException {
throws SQLException {
Query simpleQuery = Query simpleQuery = connection.getQueryExecutor().createSimpleQuery(p_sql);
connection.getQueryExecutor().createSimpleQuery(p_sql);
execute(simpleQuery, null, QueryExecutor.QUERY_ONESHOT | flags);
execute(simpleQuery, null, QueryExecutor.QUERY_ONESHOT | flags);
this.lastSimpleQuery = simpleQuery;
this.lastSimpleQuery = simpleQuery;
return (result != null && result.getResultSet() != null);
return (result != null && result.getResultSet() != null);
}}
// org.postgresql.jdbc2.AsbtractJdbc2Statement
// org.postgresql.jdbc2.AsbtractJdbc2Statement
Java & PostgreSQL – Luca Ferrari
141 di 182
Esecuzione di un INSERT/UPDATE
protected void processResults(ResultHandler handler, int flags) protected void processResults(ResultHandler handler, int flags) throws IOException {
throws IOException {
.... .... case 'C': // Command Status (end of Execute)
case 'C': // Command Status (end of Execute)
// ottiene la stringa risultato dell'esecuzione della query
// ottiene la stringa risultato dell'esecuzione della query
// ad esempio INSERT 0 1 oppure UPDATE 1
// ad esempio INSERT 0 1 oppure UPDATE 1
String status = receiveCommandStatus();
String status = receiveCommandStatus();
if (fields != null || tuples != null){ if (fields != null || tuples != null){ // qui c'e' un ResultSet, quindi una select // qui c'e' un ResultSet, quindi una select handler.handleResultRows(currentQuery, fields, tuples, null);
handler.handleResultRows(currentQuery, fields, tuples, null);
fields = null; tuples = null;
fields = null; tuples = null;
}
}
else {
else {
// qui non c'e' ResultSet, quindi una scrittura sul DB
// qui non c'e' ResultSet, quindi una scrittura sul DB
interpretCommandStatus(status, handler);
interpretCommandStatus(status, handler);
}
}
...
...
} // org.postgresql.core.v3.QueryExecutorImpl
} // org.postgresql.core.v3.QueryExecutorImpl
Java & PostgreSQL – Luca Ferrari
142 di 182
Pl/Java
Java & PostgreSQL – Luca Ferrari
143 di 182
Installazione di Pl/Java
Pljava si basa su un modulo del kernel denominato
pljava.so; la versione base è compilata per architettura 32
bit e verso PostgreSQL 8.3. Per una corretta installazione
su piattaforme e database differenti occorre:
usare un JDK fra la versione 4 e 5 (non la 6 perché cambiano le API JDBC);
installare gli header per lo sviluppo di PostgreSQL della versione corrente (ad
esempio postgresql­server­dev­8.4);
scaricare tramite CVS l'ultima versione dei sorgenti di Pl/Java;
verificare che JAVA_HOME e il PATH puntino al compilatore Java corretto;
lanciare la compilazione con il comando make.
Java & PostgreSQL – Luca Ferrari
144 di 182
Installazione di Pl/Java
Occorre poi apportare alcune modifiche a
postgresql.conf
affinché
il
server-cluster
postmaster possa caricare l'estensione Java:
dynamic_library_path = '$libdir:/sviluppo/java/pljava/org.postgresql.pljava/build/objs'
custom_variable_classes = 'pljava'
pljava.classpath='/sviluppo/java/pljava/org.postgresql.pljava/build
/pljava.jar'
Java & PostgreSQL – Luca Ferrari
145 di 182
Introduzione a Pl/Java
Pl/Java è una estensione di PostgreSQL per supportare il
linguaggio Java come linguaggio interno.
Pl/Java è simile, concettualmente, ad altri Pl/xxx come ad
esempio Pl/Perl. La differenza principale con altri linguaggi
è che, essendo Java compilato, non è possibile scrivere
direttamente codice Java all'interno del server, bensì si
deve istruire il server per richiamare del codice Java in
formato bytecode. In altre parole usare Pl/Java significa
sempre:
Scrivere del codice Java.
Scrivere del codice SQL per richiamare Java.
Java & PostgreSQL – Luca Ferrari
146 di 182
JNI?
Il backend PostgreSQL non è scritto in Java!
Occorre quindi trovare un modo per far dialogare
un pezzo di codice Java con il backend. Ci sono
due soluzioni possibili:
RPC
JNI
Pl/Java si basa su JNI per svariate ragioni,
principalmente:
Piu' semplice ed efficiente
Il codice Java risiede sul server, quindi è locale al
backend (non c'è necessità di usare chiamate
remote)
Java & PostgreSQL – Luca Ferrari
147 di 182
1 backend = 1 JVM ?
// backend.c
static void initializeJavaVM(void){
...
jstat = JNI_createVM(&s_javaVM, &vm_args);
...
}
Nel file backend.c, che inizializza il collegamento fra il backend vero e
proprio e Java, si ha che per ogni connessione viene avviata una JVM
tramite JNI.
In sostanza si ha una JVM per ogni connessione utente.
Ciò è coerente con il design di PostgreSQL che prevede un processo
(backend appunto) per ogni connessione utente, inoltre garantisce la
protezione dello spazio utente tramite l'astrazione dei processi e infine
consente di gestire la priorità dei processi stessi tramite gli strumenti del
sistema operativo (ps, nice, ...).
Java & PostgreSQL – Luca Ferrari
148 di 182
Installazione di Pl/Java
PlJava viene fornito con uno strumento (Java) per
l'installazione del supporto Java presso un
determinato database: deploy.jar. Occorre
eseguire questo programma Java (che richiede la
presenza del driver JDBC PostgreSQL) per installare
il supporto dinamico a Java nel database:
java org.postgresql.pljava.deploy.Deployer ­install ­database hrpmdb ­user postgres ­password postgres
Il programma provvede a creare le funzioni handler e lo
schema speciale sqlj nel database.
Java & PostgreSQL – Luca Ferrari
149 di 182
Vedere cosa succede dietro le quinte
E' bene abilitare il livello di log_min_messages a
debug3 per ottenere un po' di informazioni circa
l'utilizzo di Java nel server. Nei log si troveranno
messaggi circa la creazione dell'istanza della JVM
e il caricamento delle classi.
2010­06­01 13:55:03 CEST DEBUG: find_in_dynamic_libpath: trying "/usr/lib/postgresql/8.4/lib/pljava"
2010­06­01 13:55:03 CEST DEBUG: find_in_dynamic_libpath: trying "/sviluppo/java/pljava/org.postgresql.pljava/build/objs/pljava"
2010­06­01 13:55:03 CEST DEBUG: find_in_dynamic_libpath: trying "/usr/lib/postgresql/8.4/lib/pljava.so"
2010­06­01 13:55:03 CEST DEBUG: find_in_dynamic_libpath: trying "/sviluppo/java/pljava/org.postgresql.pljava/build/objs/pljava.so"
2010­06­01 13:55:03 CEST DEBUG: Using integer_datetimes
2010­06­01 13:55:03 CEST DEBUG: Added JVM option string "­
Djava.class.path=/sviluppo/java/pljava/org.postgresql.pljava/build"
2010­06­01 13:55:03 CEST DEBUG: Added JVM option string "­Dsqlj.defaultconnection=jdbc:default:connection"
2010­06­01 13:55:03 CEST DEBUG: Added JVM option string "vfprintf"
2010­06­01 13:55:03 CEST DEBUG: Added JVM option string "­Xrs"
2010­06­01 13:55:03 CEST DEBUG: Creating JavaVM
2010­06­01 13:55:03 CEST DEBUG: JavaVM created
2010­06­01 13:55:03 CEST DEBUG: Getting Backend class pljava.jar
Java & PostgreSQL – Luca Ferrari
150 di 182
Un primo esempio di funzione
CREATE FUNCTION getsysprop(VARCHAR)
RETURNS VARCHAR
AS 'java.lang.System.getProperty'
LANGUAGE java;
Java & PostgreSQL – Luca Ferrari
151 di 182
Scrivere funzioni PL/Java
Ogni metodo invocabile in PostgreSQL deve
essere un metodo statico (i parametri devono
corrispondere a quelli passati alla funzione SQL).
Ogni funzione Java deve essere invocata tramite
una funzione SQL.
Le classi devono essere contenute in un file jar
caricato e impostato nel classpath del database.
Il classpath viene
amministratori.
gestito
solo
dagli
utenti
ATTENZIONE: se vengono riportate eccezioni di sicurezza
significa che la classe è presente nel classpath del processo
postmaster e quindi viene caricata senza passare dal motore
database!
Java & PostgreSQL – Luca Ferrari
152 di 182
Un secondo esempio di funzione
Java & PostgreSQL – Luca Ferrari
153 di 182
Parametri e tipi di ritorno: un esempio
Java & PostgreSQL – Luca Ferrari
154 di 182
NULL vs null
Il tipo NULL del linguaggio SQL viene tradotto
nel tipo null di Java.
Però per SQL è lecito avere NULL anche dove
c'è uno scalare, mentre per Java no!
Di conseguenza le funzioni che lavorino con
scalari (in ingresso/uscita) e che debbano
gestire tipi NULL (SQL) devono usare le classi
wrapper.
Java & PostgreSQL – Luca Ferrari
155 di 182
Funzioni che restituiscono un SETOF
Pl/Java richiede che una funzione che ritorna un SETOF (lato
SQL) restituisca un Iterator (lato Java). Non verranno
accettati altri tipi di ritorno!
E' comunque possibile usare un qualunque tipo di
dato/struttura che implementi l'interfaccia Iterator.
Java & PostgreSQL – Luca Ferrari
156 di 182
Creazione di Trigger
Il codice Java dipende dalle librerie Pl/Java
Ogni funzione trigger non restituisce nulla (void) e accetta
come parametro un oggetto TriggerData con le
informazioni sull'invocazione del Trigger.
Tramite TriggerData è possibile selezionare il result set
new oppure old e su questi agire (old è in sola lettura).
Java & PostgreSQL – Luca Ferrari
157 di 182
Esempio di Trigger
Si vuole creare un trigger che modifichi una stringa di testo con le metainformazioni sul trigger stesso.
Java & PostgreSQL – Luca Ferrari
158 di 182
Esempio di Trigger
Java & PostgreSQL – Luca Ferrari
159 di 182
Esempio piu' complesso di Trigger
Si supponga di voler tenere traccia del numero globali di
invocazioni della funzione trigger, del numero di update di una riga
e di impedire le cancellazioni delle righe che sono state aggiornate
un numero pari di volte.
Java & PostgreSQL – Luca Ferrari
160 di 182
Codice della funzione Trigger
public class Trigger {
public static int globalCounter = 0;
public static void triggerJavaMethod( TriggerData triggerData ) throws SQLException{
// e' un trigger per update?
if( triggerData.isFiredByUpdate() ){
// prelevo il result set nuovo
ResultSet newRS = triggerData.getNew();
// prelevo il vecchio result set
ResultSet oldRS = triggerData.getOld();
// inserisco il contatore globale
newRS.updateInt("global_counter", ++globalCounter);
// incremento il contatore di update
newRS.updateInt("update_counter", oldRS.getInt("update_counter") + 1);
}
else if( triggerData.isFiredByDelete() ){
ResultSet oldRS = triggerData.getOld();
if( ( oldRS.getInt("update_counter") % 2 ) == 0 )
throw new TriggerException(triggerData, "Tupla non ..");
}
}
Non esiste ancora un metodo efficace per gestire l'abort di un trigger!
Lanciare una eccezione non funziona appieno: blocca la transazione
nel backend!
Java & PostgreSQL – Luca Ferrari
161 di 182
Esecuzione del Trigger
Java & PostgreSQL – Luca Ferrari
162 di 182
Maneggiare tipi complessi
I tipi complessi devono essere trattati tramite un ResultSet
aggiornabile.
Si rinuncia al paradigma OOP per passare ad un paradigma in stile
record-based.
E' comodo usare un ResultSetGenerator:
La funzione (lato SQL) richiama un metodo (lato Java) statico che
restituisce
un
oggetto
che
implementa
l'interfaccia
ResultSetProvider.
ResultSetProvider
contiene
due
metodi:
assignRowValues(..) che permette l'update di una cella
(riga/colonna) nel ResultSet e close() usato per rilasciare le
risorse.
ResultSetProvider.assignRowValues(..)
restituisce
false se non ci sono altre righe da processare, true se
ancora una riga deve essere inserita nel ResultSet.
Java & PostgreSQL – Luca Ferrari
163 di 182
Maneggiare tipi complessi: generare delle tuple
public class RowGenerator implements ResultSetProvider {
private static int NUM_ROWS = 10;
private static int NUM_COLS = 5;
public boolean assignRowValues(ResultSet rs, int rowNumber)
throws SQLException {
if( rowNumber <= NUM_ROWS ){
for( int j = 1; j <= NUM_COLS; j++ )
rs.updateString(j, "Riga " + rowNumber + " Colonna " + j);
return true;
}
else
return false;
}
public void close() throws SQLException {
System.out.println("Chiusura del row set provider");
}
public static ResultSetProvider generateRows(){
return new RowGenerator();
}
}
Java & PostgreSQL – Luca Ferrari
164 di 182
Maneggiare tipi complessi: invocazioni SQL
Java & PostgreSQL – Luca Ferrari
165 di 182
JDBC & Pl/Java =~ SQL MED
E' possibile chiamare JDBC da Pl/Java, in modo da poter
interrogare in modo riflessivo il database al quale si è connessi.
In questo modo si possono ottenere (e restituire) i dati
appartenenti ad altre tabelle/relazioni.
Java & PostgreSQL – Luca Ferrari
166 di 182
JDBC & Pl/Java =~ SQL MED
public class RowGenerator implements ResultSetHandle {
private String databaseName;
private String username;
private String password;
private String tableName;
public RowGenerator(String database, String username, String password, String table ){
this.databaseName = database;
this.username = username;
this.password = password;
this.tableName = table;
}
// funzione usata lato SQL
public static ResultSetHandle generateRows(String database, String username, String password, String table){
return new RowGenerator( database, username, password, table );
}
Java & PostgreSQL – Luca Ferrari
167 di 182
JDBC & Pl/Java = SQL MED
// funzione usata per ottenere le tuple
public ResultSet getResultSet() throws SQLException {
org.postgresql.Driver driver = new org.postgresql.Driver();
Connection connection = DriverManager.getConnection( this.databaseName, this.username, this.password );
Statement statement = connection.createStatement();
return statement.executeQuery(" SELECT * FROM "+ this.tableName );
}
}
Se la funzione viene creata come linguaggio untrusted allora è possibile usare la
connessione verso ogni database, e quindi implementare un SQL MED completo.
ATTENZIONE: i driver devono trovarsi nel classpath!
Java & PostgreSQL – Luca Ferrari
168 di 182
Utilizzo dei SavePoint
public static void executeInsert(int counter) throws SQLException{
Connection connection = DriverManager.getConnection("jdbc:default:connection");
Statement statement = connection.createStatement();
Savepoint save = null;
// effettuo 10 inserimenti
for( int i = 0; i < counter; i++ ){
statement.execute("INSERT INTO java_table(nome) VALUES('inserimento­" + i + "')");
if( i >= (counter / 2) && save == null )
save = connection.setSavepoint();
}
// rollback connection.rollback( save );
}
Java & PostgreSQL – Luca Ferrari
169 di 182
SavePoint: risultato
Java & PostgreSQL – Luca Ferrari
170 di 182
Listener
private static Logger logger = Logger.getAnonymousLogger();
public static void executeInsert(int counter) throws SQLException{
Connection connection = DriverManager.getConnection("jdbc:default:connection");
Session session = SessionManager.current();
session.addSavepointListener( new SavepointListener() {
public void onStart(Session arg0, Savepoint arg1, Savepoint arg2)
throws SQLException {
logger.info("Savepoint START " + arg0 + " savepoints " + arg1 + " " + arg2);
}
public void onCommit(Session arg0, Savepoint arg1, Savepoint arg2)
throws SQLException {
logger.info("Savepoint COMMIT" + arg0 + " savepoints " + arg1 + " " + arg2);
}
public void onAbort(Session arg0, Savepoint arg1, Savepoint arg2)
throws SQLException {
logger.info("Savepoint ABORT " + arg0 + " savepoints " + arg1 + " " + arg2);
}
});
Java & PostgreSQL – Luca Ferrari
171 di 182
Listener
E' possibile agganciare dei listener per le
transazioni (TransactionListener) e per la
gestione dei SavePoint (sotto-transazioni)
(SavePointListener).
Java & PostgreSQL – Luca Ferrari
172 di 182
JDBC & Pl/Java =~ SQL MED
public class RowGenerator implements ResultSetHandle {
private String databaseName;
private String username;
private String password;
private String tableName;
public RowGenerator(String database, String username, String password, String table ){
this.databaseName = database;
this.username = username;
this.password = password;
this.tableName = table;
}
public static ResultSetHandle generateRows(String database, String username, String password, String table){
return new RowGenerator( database, username, password, table );
}
public ResultSet getResultSet() throws SQLException {
Connection connection = DriverManager.getConnection( this.databaseName, this.username, this.password );
Statement statement = connection.createStatement();
return statement.executeQuery(" SELECT * FROM "+ this.tableName );
}
}
Java & PostgreSQL – Luca Ferrari
173 di 182
Singleton e PooledObjects
Pl/Java consente la gestione di oggetti in un pool,
ovvero un recipiente di oggetti che possono essere
riciclati per scopi futuri. In un certo senso questo
aiuta nel paradigma dei singleton.
Ogni oggetto che deve essere gestibile da un pool
deve implementare l'interfaccia PooledObject e
implementare i metodi per la attivazione e
disattivazione.
Il costruttore dell'oggetto deve accettare il pool di
appartenenza (ObjectPool) sul quale puo'
operare.
Java & PostgreSQL – Luca Ferrari
174 di 182
Esempio di PooledObject
public class Pooled implements PooledObject{
private long creationTimeMillis;
private ObjectPool myPool;
public Pooled( ObjectPool myPool ){
super();
this.creationTimeMillis = Calendar.getInstance().getTimeInMillis();
this.myPool = myPool;
}
public void activate() throws SQLException {
System.out.println("Oggetto <" + creationTimeMillis + "> attivato!");
}
public void passivate() throws SQLException {
System.out.println("Oggetto <" + creationTimeMillis + "> disattivato!");
}
Java & PostgreSQL – Luca Ferrari
175 di 182
Esempio di PooledObject
public void remove() {
System.out.println("Oggetto <" + creationTimeMillis + "> rimosso!");
}
public static void createPooledObject() throws SQLException{
// ottengo la sessione corrente
Session session = SessionManager.current();
// ottengo il pool di oggetti
ObjectPool pool = session.getObjectPool( Pooled.class );
// ottengo una istanza dal pool
Pooled myPooled = (Pooled) pool.activateInstance();
// ci faccio qualcosa....
// poi lo reinserisco nel pool
pool.passivateInstance(myPooled);
}
}
Java & PostgreSQL – Luca Ferrari
176 di 182
Esempio di PooledObject
Se si invoca ripetutamente la funzione di
generazione dell'oggetto si nota che è sempre
lo stesso oggetto ad essere usato.
Se l'oggetto non viene reinserito nel pool, allora
un nuovo viene creato e usato.
Java & PostgreSQL – Luca Ferrari
177 di 182
Multithreading
Problema: Java è un linguaggio che supporta il multithreading, ma il backend PostgreSQL è un singolo
processo senza supporto ai thread.
Il supporto al multithreading di Java deve essere
comunque garantito!
Se si pensa al backend come ad un ennesimo thread,
allora tutti i thread (Java e di backend) devono
sincronizzarsi in modo coerente: si utilizza un singolo lock!
Viene
definito
un
oggetto
particolare,
Backend.THREADLOCK sul quale tutti si sincronizzano.
Java & PostgreSQL – Luca Ferrari
il
178 di 182
Backend.THREADLOCK
Java & PostgreSQL – Luca Ferrari
179 di 182
Funzionamento del lock
L'idea è semplice: il lock funziona come un mutex.
Il processo backend detiene il lock dall'inizio.
Quando il processo chiama una funzione Java (ad
esempio perché scatta un trigger) rilascia il lock.
La funzione Java acquisisce il lock.
Quando la funzione Java termina il backend
riacquisisce il lock.
Così facendo il lock garantisce che ci sia sempre e
solo un flusso di esecuzione: o il backend è in
attesa della terminazione di Java oppure Java è in
attesa della terminazione di un flusso di backend.
Java & PostgreSQL – Luca Ferrari
180 di 182
Funzionamento del lock
Backend
Backend
Java
Java
SELECT example1();
Esecuzione di una query
risultati
<fine>
Java & PostgreSQL – Luca Ferrari
181 di 182
Utilizzo del lock
Tutti i metodi della classe Portal.java (e delle classi
che rappresentano oggetti del backend) utilizzano il lock
come prima istruzione al fine di ottenere il lock dal
backend. Ovviamente il lock va acquisito qualora si
debba procedere con un metodo nativo.
public int fetch(boolean forward, int count) throws SQLException { synchronized(Backend.THREADLOCK) { return _fetch(m_pointer, System.identityHashCode(
Thread.currentThread()), forward, count); }
Java & PostgreSQL – Luca Ferrari
182 di 182