Java & PostgreSQL: da JDBC a Pl/Java (passando per org.postgresql.driver)

Documenti analoghi
JDBC. A. Bechini Accesso a DataD con Java

Introduzione JDBC interfaccia java.sql driver caricare i driver

CORSO DI ALGORITMI E PROGRAMMAZIONE. JDBC Java DataBase Connectivity

Non si deve fare ALCUN riferimento alla parte specifica di JDBC.

Programmazione Java Avanzata Spring - JDBC

Siti web centrati sui dati Architettura MVC-2: i JavaBeans

JDBC per l accesso Java a DB. Tito Flagella tito@link.it

Architettura MVC-2: i JavaBeans

JDBC versione base. Le classi/interfacce principali di JDBC

Sviluppo Applicazioni Mobile Lezione 12 JDBC. Dr. Paolo Casoto, Ph.D

Basi di dati. Il Linguaggio SQL. K. Donno - Il Linguaggio SQL

Esercitazione su JDBC

Laboratorio di reti II: Gestione di database lato server

SQL. Laboratorio di Progettazione di Basi di Dati (CdS in Informatica e TPS)

Esercitazione 4 JDBC

JDBC di base. Le classi/interfacce principali di JDBC

Scheda 15 Accedere ai DataBase con JDBC

Dispensa di database Access

HBase Data Model. in più : le colonne sono raccolte in gruppi di colonne detti Column Family; Cosa cambia dunque?

PROVA FINALE Ingegneria del software

Manuale Amministratore Legalmail Enterprise. Manuale ad uso degli Amministratori del Servizio Legalmail Enterprise

19. LA PROGRAMMAZIONE LATO SERVER

Java: la libreria delle classi

2.5. L'indirizzo IP identifica il computer di origine, il numero di porta invece identifica il processo di origine.

Server Galileo.

Data Base. Master "Bio Info" Reti e Basi di Dati Lezione 6

Lezione 9. Applicazioni tradizionali

Riccardo Dutto, Paolo Garza Politecnico di Torino. Riccardo Dutto, Paolo Garza Politecnico di Torino

JDBC: Introduzione. Java Database Connectivity (JDBC): parte 1. Schema dei legami tra le classi principali. Principali classi/interfacce di JDBC

MySQL Database Management System

MDAC. Attualmente la versione disponibile di MDAC è la 2.8 ma faremo riferimento alla 2.6. ADO Active Data Objects ADO OLE DB ODBC

Basi di dati e Web (Moduli: Laboratorio e Siti Web centrati sui Dati) Prova scritta del 14 luglio 2008

Guida alla registrazione on-line di un DataLogger

Università degli Studi di Bologna Facoltà di Ingegneria. Tecnologie Web L-A A.A Esercitazione 08 DAO e Hibernate

11/02/2015 MANUALE DI INSTALLAZIONE DELL APPLICAZIONE DESKTOP TELEMATICO VERSIONE 1.0

Al giorno d oggi, i sistemi per la gestione di database

Consiglio regionale della Toscana. Regole per il corretto funzionamento della posta elettronica

GovPay 2.0. Manuale Installazione

Database e reti. Piero Gallo Pasquale Sirsi

GERARCHIE RICORSIVE - SQL SERVER 2008

Servizi Remoti. Servizi Remoti. TeamPortal Servizi Remoti

Corso di Informatica (Programmazione) Lezione 6 (31 ottobre 2008)

Volumi di riferimento

1) GESTIONE DELLE POSTAZIONI REMOTE

Per scrivere una procedura che non deve restituire nessun valore e deve solo contenere le informazioni per le modalità delle porte e controlli

2104 volume III Programmazione

SOSEBI PAPERMAP2 MODULO WEB MANUALE DELL UTENTE

PMF Integration Tools

Tipi primitivi. Ad esempio, il codice seguente dichiara una variabile di tipo intero, le assegna il valore 5 e stampa a schermo il suo contenuto:

Automatizzare i compiti ripetitivi. I file batch. File batch (1) File batch (2) Visualizzazione (2) Visualizzazione

Corso di Informatica Modulo T3 B2 - Database in rete

PSNET UC RUPAR PIEMONTE MANUALE OPERATIVO

MANUALE D'USO DEL PROGRAMMA IMMOBIPHONE

Gestione Risorse Umane Web

Corso di PHP. Prerequisiti. 6.1 PHP e il web 1. Conoscenza HTML Tecnica della programmazione Principi di programmazione web

Tabelle di riferimento Pulsanti Inserire documento Predisposizione doc Approvazione Doc Numerazione Doc Pubblicazione Albo Webservice

Banca dati Professioniste in rete per le P.A. Guida all uso per le Professioniste

MANUALE EDICOLA 04.05

Esercitazione sulle libpq - libreria C per PostgreSQL

Do-Dots Protocollo di comunicazione

per immagini guida avanzata Uso delle tabelle e dei grafici Pivot Geometra Luigi Amato Guida Avanzata per immagini excel

DDL, VINCOLI D INTEGRITÁ, AGGIORNAMENTI E VISTE. SQL è più di un semplice linguaggio di interrogazione

Modulo 4: Ereditarietà, interfacce e clonazione

I file di dati. Unità didattica D1 1

Mac Application Manager 1.3 (SOLO PER TIGER)

Concetto di Funzione e Procedura METODI in Java

flusso delle informazioni... 2 password... 3 password/ inserimento di una nuova richiesta... 4 le condizioni di vendita... 6

Regione Piemonte Portale Rilevazioni Crediti EELL Manuale Utente

A intervalli regolari ogni router manda la sua tabella a tutti i vicini, e riceve quelle dei vicini.

DBMS ed Applicazioni Motivazioni

10.1. Un indirizzo IP viene rappresentato in Java come un'istanza della classe InetAddress.

Manuale utente Volta Control

Definizione di domini

Tecnologia e Applicazioni Internet 2011/12

12 - Introduzione alla Programmazione Orientata agli Oggetti (Object Oriented Programming OOP)

Guida Rapida all uso del License Manager di ROCKEY4Smart (V )

Java: Compilatore e Interprete

Registratori di Cassa

Indice generale. Capitolo 3 Introduzione a PHP...43 Sintassi e istruzioni di base Variabili, operatori e commenti Array...

PORTALE CLIENTI Manuale utente

SITI-Reports. Progetto SITI. Manuale Utente. SITI-Reports. ABACO S.r.l.

Sviluppata da: Lo Russo - Porcelli Pag. 1 di 6 6FRSR utilizzare il DBMS Postgresql per imparare il linguaggio SQL.

Istruzioni di installazione di IBM SPSS Modeler Text Analytics (licenza per sito)

COLLI. Gestione dei Colli di Spedizione. Release 5.20 Manuale Operativo

Laboratorio di Basi di Dati e Web

Portale tirocini. Manuale utente Per la gestione del Progetto Formativo

Procedura SMS. Manuale Utente

Cenni di programmazione distribuita in C++ Mauro Piccolo

Progettazione : Design Pattern Creazionali

Airone Gestione Rifiuti Funzioni di Esportazione e Importazione

Come modificare la propria Home Page e gli elementi correlati

Corso basi di dati Installazione e gestione di PWS

Dipartimento per le Libertà Civili e l Immigrazione

Excel. A cura di Luigi Labonia. luigi.lab@libero.it

Funzioni in C. Violetta Lonati

Installazione di GFI WebMonitor

Mida Directory. Introduzione. Mida Directory

1. RETI INFORMATICHE CORSO DI LAUREA IN INGEGNERIA INFORMATICA SPECIFICHE DI PROGETTO A.A. 2013/ Lato client

Per chi ha la Virtual Machine: avviare Grass da terminale, andando su Applicazioni Accessori Terminale e scrivere grass

Transcript:

Java & PostgreSQL: da JDBC a Pl/Java (passando per org.postgresql.driver) Ing. Luca Ferrari, PhD fluca1978@gmail.com 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. 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) ) 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) ) 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 ) 5 di 182

JDBC: applicazioni client 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! 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. 8 di 182

Application API vs Driver API Java Application Application ApplicationAPI API Driver DriverAPI API Database Engine 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. 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 11 di 182

JDBC API 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 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. 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. 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) 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). 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(); 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 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); 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")); } 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) } } 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. 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. 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. 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! 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(). 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(); 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. 29 di 182

Compilazione del driver 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 jdbc@postgresql.org; gli attuali mantainer del Driver sono Dave Cramer, Kris Jurka, Oliver Jowett. 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 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). 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]$ 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: $ 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. 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. 37 di 182

Compilazione in Eclipse (2) (3) Ricercare il file build.xml tramite il modulo Ant di Eclipse. 38 di 182

Compilazione in Eclipse (3) (4) Eseguendo il file di build il progetto viene compilato. 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. 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. 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. 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. 43 di 182

Scrittura del livello JDBC (2) Viene fatto un controllo sulla proprietà jdbcx, impostata precedentemente dal target check_versions. (1) 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 45 di 182

Scrittura del livello JDBC (4) (3) Il file Driver.java.in viene copiato in Driver.java applicando le condizioni di filtro. 46 di 182

JDBC: tipi di Statement 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. 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(); } 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! 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! 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. 52 di 182

JDBC: Utilizzo avanzato 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. 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); 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); 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... 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. 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() 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]); 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(); 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. 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. 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(); 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. 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 ){...} 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. 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(); 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(); 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. 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(). 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); } 72 di 182

JDBC: Transazioni 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. 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 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. 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!). 77 di 182

JDBC: DataSource 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. 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. 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(); 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(); 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 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(); 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> 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() ); 86 di 182

Java Transaction API 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. 88 di 182

Transazioni distribuite in Java Java Application User UserTransaction Transaction Interface Interface Transaction TransactionManager Manager JDBC JDBCDriver Driver Application Server Database Engine 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. 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. 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). 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. 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) 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; } 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){... } } 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(); 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"); 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(); } } 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); 100 di 182

JDBC & SSL 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. 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. 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) 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. 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). 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. 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 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. 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 110 di 182

org.postgresql.driver 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 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 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(..)). 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 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. 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. 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. 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 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. 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 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 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 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). 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. 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. 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. 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. 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 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. 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 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 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. 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. 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. 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 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}) 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 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 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. 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 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 142 di 182

Pl/Java 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. 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' 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. 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) 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,...). 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. 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 150 di 182

Un primo esempio di funzione CREATE FUNCTION getsysprop(varchar) RETURNS VARCHAR AS 'java.lang.system.getproperty' LANGUAGE java; 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! 152 di 182

Un secondo esempio di funzione 153 di 182

Parametri e tipi di ritorno: un esempio 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. 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. 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). 157 di 182

Esempio di Trigger Si vuole creare un trigger che modifichi una stringa di testo con le metainformazioni sul trigger stesso. 158 di 182

Esempio di Trigger 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. 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! 161 di 182

Esecuzione del Trigger 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. 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(); } } 164 di 182

Maneggiare tipi complessi: invocazioni SQL 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. 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 ); } 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! 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 ); } 169 di 182

SavePoint: risultato 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); } }); 171 di 182

Listener E' possibile agganciare dei listener per le transazioni (TransactionListener) e per la gestione dei SavePoint (sotto-transazioni) (SavePointListener). 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 ); } } 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. 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!"); } 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); } } 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. 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. il 178 di 182

Backend.THREADLOCK 179 di 182