giovedì 30 settembre 2010

DBConnector

DBConnector è un client per database, basato sulle API JDB, viste in un precedente articolo. Lo sviluppo del client è stato portato avanti attraverso NetBeans IDE, che favorisce e semplifica di molto il disegno delle GUI! Per la progettazione ho cercato invece di seguire il noto schema MVC (model-view-controller). Potete scaricare a questo indirizzo l'intera cartella contenente i file di progetto, oltre al file jar che esegue l'applicazione stessa. Il file in questione è un archivio tar che va dunque estratto con il comando: tar zxvf DBConnector.tar.gz, da eseguire all'interno della cartella che ospita il file appena scaricato. L'estrazione produce la cartella DBConnector, al suo interno trovate il codice sorgente (cartella /src), la documentazione generata con il comando javadoc (cartella /dist/javadoc) e il file jar per eseguire l'applicazione (nella cartella dist). Quest'ultima avviene digitando il comando: java -jar DBConnector.jar.

Il programma si compone di cinque classi, una dedicata all'avvio dell'applicazione (DBConnectorApp). Una classe descrive l'interfaccia grafica (DBGui), altre due si preoccupano della connessione (DBConnector) e dell'invio dei comandi SQL al database (DBQuest). L'ultima classe è quindi dedicata alla gestione degli eventi (DBController), raccoglie i comandi SQL e li fa eseguire all'oggetto incaricato, quindi aggiorna l'interfaccia grafica riportando errori e risultati. Nell'esempio viene mostrata l'applicazione in esecuzione, il driver usato nell'esempio permette la connessione a un database MySQL che ho progettato in precedenza per un'applicazione web.

venerdì 24 settembre 2010

Una classe davvero utile: la classe Properties

Ho già utilizzato in passato questa classe per aggiugere propietà all'oggetto Session dell'API JavaMail e solo adesso mi rendo conto della sua molteplice utilità. In questo articolo vi spiego un utilizzo davvero interessante di questa classe!

La classe Properties, package java.util.Properties, è in grado di rappresentare un set di coppie chiave-valore in maniera persistente, salvandole e caricandole da uno stream. Le coppie di valori scritte e lette dal file vanno interpretate come stringhe, ogni eventuale valore numerico associato a una chiave va quindi ripristinato con un parsing.

Il costruttore della classe (Properties()) genera una lista vuota, da personalizzare quindi in seguito. Esiste poi un secondo costruttore che permette sia l'istanza dell'oggetto e sia l'inizializzazione dello stesso con una lista di proprietà (Properties(Properties defaults)).

Le coppie di valori possono essere assegnate con:

Object setProperty(String chiave, String valore)
che ritorna il precedente valore aggiunto alla lista, se presente, altrimenti null. La ricerca dei valori avviene invece con:
String getProperty(String chiave)
che cerca la chiave all'interno della lista restituendone il valore precedentemente assegnato (altrimenti null se la chiave non viene trovata). Direi che fin qui nulla di nuovo o di eclatante. All'inizio dell'articolo parlavo però di persistenza e in effetti la classe è in grado di garantirla! Attraverso due metodi possiamo scrivere su file e leggere da file le proprietà impostate in una precedente sessione dell'oggetto. Con il metodo:
void store(OutputStream out, String header) throws IOException
possiamo scrivere le coppie assegnate all'oggetto sullo stream out. Se header è diverso da null una nuova riga di commento verrà aggiunta in testa al file, per spiegare ad esempio l'utilità del file. La scrittura su file provoca la stampa all'interno dello stesso, subito sotto l'header, della data di creazione del file. Con il metodo:
void load(InputStream in) throws IOException

possiamo, invece, leggere la lista di valori dallo stream in. Attenzione, gli ultimi due metodi, poiché lavorano su stream, possono lanciare un IOException (che va in qualche modo gestita, oppure propagata).

L'utilità di questa classe è davvero interessante. Pensate, ad esempio, a tutte quelle volte che dovete salvare su file le opzioni che configurano la vostra applicazione. E di come intercettare queste opzioni, leggendole dal file, ne vogliamo parlare? Insomma, chi deve preoccuparsi di questo compito? Il programmatore, di sicuro. Ecco allora che Properties viene in nostro soccorso, usiamo questa classe per liberarci in modo elegante da un compito!

Dopo aver letto la documentazione sulla classe non ho perso tempo e ho subito scritto un package di supporto per la gestione della classe in questione. Ho dotato la classe del costruttore base, caratterizzando gli oggetti in base al nome del file da usare (più altri campi). I metodi per aggiungere e cercare coppie di valori sono stati sistemati all'interno di istruzioni di controllo per evitare l'assegnazione di valori già esistenti o nulli. Quindi ho aggiunto due metodi per leggere e salvare su file (load() e save()), inserendo i metodi della classe (load() e store()) all'interno di istruzioni utili alla gestione degli stream di input e di output. Ogni metodo (ad eccezione di quello assegnato alla lettura del valore di una chiave, che ritorna una stringa) ritona un valore logico che riflette l'esito dell'operazione. Ho poi dotato la classe di un buffer per gli errori, da consultare non appena un'operazione non va a buon fine. Trovate la classe da me scritta a questo indirizzo. All'interno della classe trovate un metodo main utile al testing della classe, ecco ad esempio cosa genera:

giovedì 23 settembre 2010

JDBC, esempio

State scrivendo un'applicazione che usa un driver JDBC? Ecco i passi da seguire:

  1. Caricare il driver, specifico per il database in uso;
  2. Creare, mediante url, una connessione al database da usare;
  3. Scrivere e inviare il comando SQL alla connessione;
  4. Scorrere il risultato alla ricerca dei valori utili;
  5. Chiudere la connessione al database;

Per caricare il driver occorre specificare al metodo Class.forName(driver).newInstance() una stringa che identifica il nome del driver.  Ad esempio, la stringa che identifica il driver JDBC per MySQL è com.mysql.jdbc.Driver. Non dimenticate di invocare newIstance(), dopo aver cercato il driver, per creare una nuova istanza del driver! Altre stringhe di driver sono: sun.jdbc.odbc.JdbcOdbcDriver per un bridge (ponte) JDBC-ODBC, oracle.jdbc.driver.OracleDriver per Oracle e org.postgresql.Driver per PostgreSQL.

Anche l'url da usare per la connessione va cercato nella dicumentazione. MySQL e altri database sono sviluppati sul modello client/server. Il server che ospita dati, sia locale che remoto, è quindi identificato da un indirizzo di rete. Per MySQL questo url ha la seguente struttura: jdbc:mysql://indirizzo_host:porta/nomeDataBase. Se il server MySQL è dunque locale occorre indicare come host l'indirizzo localhost oppure il noto ip di loopback 127.0.0.1. Il numero di porta usato di default dal server MySQL è 3306 (se tale numero non è stato modificato dopo l'installazione del server). Con DriverManager.getConnection(url,user,password) viene tentata una connessione al database (specificato nell'url), se il database richiesto non è pubblico occorre allora indicare il nome utente e la password usate in fase di login (come quando usiamo il client MySQL dal terminale).

Terminata la fase di setup per la connessione si possono iniziare a utilizzare i metodi visti in precedenza per l'invio dei comandi SQL. Terminata l'elaborazione chiudete la connessione con il metodo connessione.close().


import java.sql.*;



public class Main {
 public static void main(String[] args) {
  String driver="com.mysql.jdbc.Driver";
  String user="root";
  String password="root";
  String url="jdbc:mysql://localhost:3306/php_training";
  String query="SELECT * FROM contatti WHERE nome='Luca'";
  try {
   System.out.println("Caricamento del driver ("+driver+")...");
   Class.forName(driver).newInstance();
   Connection connessione=DriverManager.getConnection(url,user,password);
   if(!connessione.isClosed()) System.out.println("Connessione...");
   else {
    System.out.println("Connessione non riuscita!");
    System.exit(1);
   }
   Statement stmt=connessione.createStatement();
   System.out.println("Esecuzione della query: "+query);
   System.out.println("Risultato:");
   ResultSet srs = stmt.executeQuery(query);
   while (srs.next()) {
    System.out.println(srs.getString("nome")+" "+srs.getString("cognome"));
   }
   connessione.close();
  }
  catch(Exception errore) {
   System.err.println("Si è verificata un'eccezione: " + errore.getMessage());
  }
 }
}

Ecco cosa riporta la finestra di output in NetBeans IDE (ho usato il database php_training):

mercoledì 22 settembre 2010

iPAQ H3630: seconda vita

In questi giorni ho avuto modo di sostituire la batteria che alimenta l'iPAQ H3630, uno dei miei primi palmari! Il dispositivo se pur vecchio può in ogni caso essere riciclato per altre applicazioni e usi. Ricordo ancora la sua vecchia grafica, in stile Windows 3.1, la versione installata di default era una delle prime release di Windows CE. In seguito da me aggiornata alla più attuale versione di Windows Mobile, non l'ultima viste le scarse risorse (soprattutto in termini di memoria). Il dispositivo usa un processore Intel StrongARM con frequenza di clock di 206MHz, 32MB di memoria RAM e un display touch con area attiva di 320 per 240 pixel (4096 colori). La ROM dedicata al sistema operativo è di soli 16MB, la batteria (a ioni di Litio) permetteva anche 10 ore di autonomia. Era proprio la batteria (oltre che alle risorse) a limitarne l'uso, inutile dirvi che non reggeva nemmeno il processo di carica. L'unica possibilità di utilizzo consisteva nell'alimentazione attraverso la rete elettrica.
Ultimamente visti i prezzi per adesso inaccessibile di lettori per e-book mi era venura in mente di riciclare questo dispositivo proprio come lettore di e-book. A dispetto di tanti dispositivi moderni come telefoni cellulari e palmari, che pure hanno schermi e programmi per leggere e-book (basta cercare e in molti casi si trova sempre qualcosa), lo schermo del vecchio iPAQ è più che sufficiente per leggere qualche libricino. L'unico impedimento era rappresentato, quindi, dalla batteria.
Non è stato difficile trovare in Internet, sul noto negozio di aste online, la batteria per il modello in questione. La sostituzione è alla portata di tutti, anche perchè all'interno della confezione viene quasi sempre aggiunto un particolare cacciavite (non sono sicuro al 100% ma credo che si chiami torx).

I quattro fori dispositi sul retro del dispositivo danno posto alle viti che tengono assieme le due cover dell'involucro. L'apertura avviene, poi, facendo scorrere una carta di credito o qualcosa di ugual spessore lungo uno dei due bordi.

Nell'immagine riporta sopra ho evidenziato i quattro fori detti prima, quando richiudete il dispositivo assicuratevi di non far passare nessun filo (in particolar modo quello che va all'altoparlante) per nessuno di questi fori! Sempre nell'immagine sopra ho evidenziato, infine, con due rettangoli, la presa per l'alimentazione della batteria (a sinistra) e il flat (a destra). Nella batteria da me acquistata un piccola pellicola di plastica copriva (per motivi di sicurezza) i contatti del flat. Pertanto, rimuovete questa protezione prima di forza il flat nello zoccolo! Eseguite un processo di carica completo prima di iniziare a utilizzare il dispositivo, trovate questa informazione all'interno della confezione oppure sulla batteria stessa. Si legge...


JDBC: Java DataBase Connectivity, introduzione

Java DataBase Connectivity, da ora in avanti JDBC, è una libreria Java che implementa i driver per l'accesso e la gestione dei database nelle applicazioni Java.vEsistono diverse implementazioni dei driver JDBC, che possiamo classificare così come segue:

  • Driver di tipo 1: sono driver che implementano le API JDBC facendo corrispondere le chiamate ad altre API per l'accesso ai dati. Effettuano cioè un ponte (bridge) verso altre tipologie di driver, traducendo quindi ogni chiamata in chiamate dell'altro driver. Il driver JDBC-ODBC è un esempio di driver di tipo 1;
  • Driver di tipo 2: sono driver che, essendo scritti parzialmente in Java e parzialmente con il codice nativo del database, traducono ogni chiamata in una chiamata interna al database;
  • Driver di tipo 3: sono driver scritti interamente in Java e comunicano con un server middleware usando un protocollo indipendente dal database. Il server comunica, poi, le richieste del client all'origine dei dati;
  • Driver di tipo 4: sono driver scritti interamente in Java che si connettono direttamente all'origine dei dati, senza server intermedio;

I driver di tipo 1 e 2, poiché scritti con codice nativo, possono limitare l'applicazione Java in termini di portabilità. L'impiego di questi driver implica di conseguenza l'installazione delle librerie di supporto usate nel codice nativo del driver. Ad esempio, il driver di tipo 1 JDBC-ODBC dipende anche dal driver ODBC. Il driver di tipo 2 è da preferire a quello di tipo di 1 (ogni chiamata è tradotta in una chiamata interna al database). In ogni caso, se il produttore di database ne ha previsto uno, è preferibile usare un driver scritto interamente in Java.

I driver di tipo 3 e 4 hanno in genere prestazioni migliori, per i motivi scritti sopra. La portabilità dell'applicazione è quindi maggiore (non dovendo installare altro software). I driver di tipo 4 usano un protocollo nativo per scambiare i dati con il database, se possibile si consiglia di usare sempre driver di tipo 4. I driver di tipo 3 usano invece un protocollo di rete per dialogare con un server che interagisce con il database per conto del client. E' proprio quest'ulteriore passaggio a degradare le prestazioni del driver!

L'uso di JDBC prevede in primo luogo la creazione di una connessione verso il database, che può avvenire seguendo uno dei due meccanismi di seguito elencati:

  • DriverManager: carica un driver specifico utilizzando un indirizzo registrato che sintetizza la connessione al database;
  • DataSource: da preferire al precedente meccanismo poiché offre maggiori informazioni sui dati;

Per stabilire una connession sono necessari due passaggi: caricare un driver ed eseguire la connessione (usando il driver). Il caricamento di un driver si risolve in una riga di codice:

Class.forName("NOME_DEL_DRIVER");

Il nome del driver viene indicato nella documentazione del driver. La chiamata a Class.forName realizza automaticamente un'istanza  del driver registrandola con DriverManager. Solo dopo aver caricato un driver si può tentare di aprire una connessione verso il database desiderato. Vediamo come funzionano i due meccanismi.

La classe DriverManager fa parte dell'interfaccia Driver e realizza pertanto i metodi richiesti dalla stessa. Il metodo più usato è getConnection che ritorna un oggetto Connection. Esistono più implementazioni di questo metodo, che ovviamente  differiscono per la firma e quindi dai parametri richiesti dal metodo. La forma base è la seguente:

DriverManager.getConnection(String url);
dove url è l'url al database (nella forma jdbc:subprotocol:subname). Altre varianti di getConnection permettono poi di specificare proprietà, come:
DriverManager.getConnection(String url,Properties info);
dove info rappresenta un set di proprietà per la connessione. In molti casi va specificata la password e il nome utente assegnati allo stesso sul database. Il metodo:
DriverManager.getConnection(String url,String user,String password);
permette di fare proprio questo! In caso di errore tutti e tre i metodi appena visti lanciano un'eccezione di tipo SQLException. Altri metodi utili di DriverManager sono setLoginTimeout(int x) che attende x secondi prima di eseguire il login, setLogWriter(PrintWriter out) che imposta out come standard output per gli eventi di logging e println(String messaggio) che stampa su out un evento utile all'operazione di logging.
Con DataSource anziché fornire informazioni specifiche sul driver è sufficiente fornire un nome logico che punta alla sorgente di dati. Ecco un esempio:
InitialContext ic=new InitialContext();
DataSource ds=ic.lookup("java:comp/env/jdbc/myDB");
Connection con=ds.getConnection();
DataSource ds=(DataSource)org.apache.derby.jdbc.ClientDataSource();
ds.setPort(1527);
ds.setHost("localhost");
ds.setUser("APP");
ds.setPassword("APP");
Connection con=ds.getConnection();

L'interfaccia ResultSet fornisce i metodi per recuperare e manipolare i risultati generati dalle query eseguite. Gli oggetti ResultSet (una struttura dati in grado di ospitare i dati generati da una query) possono avere diverse funzionalità e caratteristiche. Tali caratteristiche possono stabilire il tipo di dati, la concorrenza e i cursori per lo scorrimento dei risultati. Il tipo di un oggetto ResultSet stabilisce i modi in cui il cursore può essere manipolato e le modifiche concorrenti che possono essere apportate ai dati. Esistono tre tipologie di ResultSet:

  • TYPE_FORWARD_ONLY: non è possibile scorrere a piacimento il risultato ottenuto (eseguendo una query), il cursore si sposta sempre e solo in avanti, dalla prima all'ultima riga;
  • TYPE_SCROLL_INSENSITIVE: è possibile scorrere il risultato in entrambe le direzioni oppure usare scostamenti relativi per l'accesso a una riga del risultato;
  • TYPE_SCROLL_SENSITIVE: è possibile scorrere il risultato in entrambe le direzioni oppure usare scostamenti relativi per l'accesso a una riga del risultato. La differenza con l'altro tipo di cursore risiede nella possibilità di rendere visibili i cambiamenti nei dati ad una applicazione;
JDBC restituisce i risultati in un oggetto ResultSet, quindi la nostra applicazione deve necessariamente dichiarare un'istanza di tale classe per raccogliere i risultati. Tale oggetto viene restituito ad esempio quando eseguiamo una chiamata a executeQuery o getResultSet. I metodi appena citati appartengono alla classe Statement, che permette di definire oggetti per l'esecuzione di codice SQL (il cui risultato è quindi ospitato in un oggetto ResultSet).
Statement stmt=con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);
ResultSet srs = stmt.executeQuery("SELECT * FROM contatti");
Il primo argomento è una delle tre costanti definite in ResultSet per indicare il tipo di cursore: TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE e TYPE_SCROLL_SENSITIVE. Il secondo argomento specifica (con due costanti definite sempre in ResultSet) se un set di risultati è di sola lettura oppure aggiornabile: CONCUR_READ_ONLY e CONCUR_UPDATABLE. Un altro metodo della classe Statement è int executeUpdate(String sql) che esegue codice SQL che non produce oggetti ResultSet (come operazioni di INSERT, UPDATE o DELETE). Ricorda che se specifichi il tipo di cursore devi sempre precisare, poi, se questo è di sola lettura oppure aggiornabile. L'istanza di un semplice oggetto Statement, con createStatement, invece, dichiarerà di default un  oggetto ResultSet di tipo TYPE_FORWARD_ONLY e accessibile nella modalità CONCUR_READ_ONLY.
Avete notato la variabile srs nel codice precedente? Si tratta di un oggetto ResultSet che racchiude al suo interno il risultato della query! Tale oggetto può essere manipolato attraverso svariati metodi. L'istanza di tale oggetto posiziona di default un puntatore per lo scorrimento della tabella (il risultato di un query è in ogni caso una tabella) sulla prima riga del risultato. Particolarmente utili sono i metodi:
  • next(): sposta il cursore sulla riga successiva a quella puntata, se esiste;
  • previous(): sposta il cursore sulla riga precedente a quella puntata, se esiste;
  • first(): sposta il cursore sulla prima riga dell'oggetto ResultSet;
  • last: sposta il cursore sull'ultima riga dell'oggetto ResultSet;
  • relative(int n): sposta il cursore di n righe (in avanti o dietro, dipende dal segno di n) rispetto alla riga puntata;
  • absolute(int n): sposta il cursore di n righe a partire dalla prima riga dell'oggetto ResultSet;
Tutti i metodi detti sopra ritornano un valore booleano, true in caso di successo, altrimenti false. Altri metodi che solitamente utilizzo sono: deleteRow() che cancella la riga puntata, int findColumn(String ColumnName) che ritorna l'indice per accedere alla riga che contiene la colonna ColumnName e Array getArray(String ColumnName) che ritorna un array composto da tutte le occorrenze della colonna ColumnName. Come posso leggere i campi dati di una riga?

Finché il puntatore dell'oggetto ResultSet insiste su una riga dell'oggetto è possibile accedere ai campi dati attraverso uno dei tanti metodi getXXX (fornendo il nome della colonna come parametro). Esistono metodi per estrarre valori numerici (come int getInt(String ColumnName), float getFloat(String ColumnName), double getDouble(String ColumnName) e long getLong(String ColumnName)) e stringhe (come String getString(String ColumnName)). Tutto dipende dal tipo di informazione da estrarre (esistono altri metodi per altri tipi di dati, vi consiglio di dare uno sguardo alla documentazione in linea). Quasi tutti i metodi per la ricerca dei campi dati hanno anche la versione basata sulla ricerca numerica della colonna (anziché usare il nome della colonna viene usato un numero che fa riferimneto all'ordine numerico della colonna nella tabella).

while(srs.next()){
 String username=srs.getString("username");
 System.out.println("Username: "+username);
}
Se usiamo l'ultima versione di NetBeans IDE possiamo sviluppare fin da subito applicazioni JDBC, in caso contrario dobbiamo necessariamente scaricare il driver e configurarlo all'interno del nostro classpath.

giovedì 16 settembre 2010

Una rubrica con... MySQL e PHP

Ricordate il database realizzato con MySQL per una rubrica di contatti telefonici? Ho realizzato una semplice interfaccia con PHP e funzioni JavaScript per contrallare l'input. La cartella con tutti i file utili all'applicazione è scaricabile a questo indirizzo. Per rispettare l'input atteso dal database MySQL, nella pagina aggiungi.html, ho pensato a delle funzioni JavaScript che, attraverso l'uso di espressioni regolari, stabiliscono la presenza o meno di un possibile input. La struttura seguita è più o meno la seguente (per i numeri cambia il pattern in [0-9]{6,30}):

cognome=document.getElementById("NOME_CAMPO").value;
pattern=new RegExp("[a-zA-Z]{2,30}");
if (pattern.test(cognome)) return true;
else return false;

Il bottone per l'invio dei dati viene allora abilitato solo se le funzioni che controllano i campi dati del form (nome, cognome e numero di telefono) danno esito positivo! Tale controllo è poi necessario anche in fase di ricerca, nella pagina cerca.html. Particolare attenzione va infine fatta in fase di inserimento dati all'interno del database. Un contatto può avere uno o più numeri di telefoni! Pertanto, durante l'aggiunta di un numero occorre stabilire prima la presenza nel contatto all'interno della tabella contatti e successivamente procedere con l'aggiunta del nominativo, se quest'ultimo non è stato già inserito! Verificando che il numero sia effettivamente un nuovo numero per il contatto indicato. Tutto questo avviene all'interno del file aggiungi.php. Per eseguire l'applicazione su Apache2 occorre collocare l'intera cartella nella home del server (riga DocumenRoot del file /etc/apache2/sites-available/default) oppure effetuarne un collegamento simbolico in /etc/apache2/sites-enabled. Vi lascio qualche immagine dell'applicazione:








PHP e MySQL: le funzioni per il database

PHP e MySQL, entrambi prodotti open source, hanno sempre avuto un buon rapporto. Recentemente PHP ha aperto la porta anche ad altri database, aggiungendo per ognuno di essi un modulo. Il supporto di PHP verso MySQL rimane in ogni caso la strada più naturale per favorire l'integrazione di un database all'interno di un'applicazione web. Il modulo MySQL per PHP va abilitato all'interno della configurazione del server web (che per Apache2 avviene nel file /etc/php5/apache2/conf.d/mysql.ini). Questo ci permetterà l'accesso e la gestione del database attraverso l'uso di semplici istruzioni!
Prima di iniziare a interagire con un database occorre stabilire una connessione verso un server MySQL, ecco l'utilità della funzione mysql_connect($host,$user,$password). Dove:
  • $host: è il nome o indirizzo del server MySQL, può comprendere anche il numero di porta;
  • $user: è il nome dell'utente che si collega al server;
  • $password: è password assegnata all'utente di nome $user sull'host con nome o indirizzo $host;

Questa funzione restituisce un collegamento alla connessione verso il server MySQL, da usare nelle successive istruzioni. Solitamente a questa istruzione viene fatta seguire l'istruzione die(), che stampa un messaggio ed esce dallo script in caso di errore.

Se vi occorre una connessione persistente verso il database esiste, inoltre, la funzione mysql_pconnect(). Avete mai usato MySQL da riga di comando? La funzione mysql_connect() esegue per voi (in locale o in remoto) l'istruzione mysql -u NOME_UTENTE -p! Il secondo passaggio da compiere consiste nella scelta del database. Se da riga di comando, nel client MySQL, selezioniamo il database con use database NOME_DATABASE, in PHP dobbiamo invece affidarci alla funzione mysql_select_db($database,$connessione). Dove:

  • $database: è il nome del database;
  • $connessione: è il link alla connessione precedentemente ottenuto, in caso di successo, dall'istruzione mysql_connect();

Questa funzione ritorna un valore booleano: true in caso di successo, altrimenti false. Cosa deve accadere se la funzione non trova il database passato come argomento? Nella maggior parte dei casi tutto il codice PHP potrebbe non avere senso. Ecco allora come usare il valore restituito:

Adesso possiamo interrogare il database! Come? Con la funzione mysql_query($query,$connessione). Dove:

  • $query: è la stringa con la query da passare al server MySQL. Può pertanto contenere sia operazioni di inserimento che di modifica. E perché no? Anche istruzioni per la rimozione di occorrenze all'interno delle tabelle che compongono il database. Un limite a queste operazioni è ovviamente stabilito dai permessi dati all'utente che interagisce con il database;
  • $connessione: è il link alla connessione precedentemente ottenuto, in caso di successo, dalla funzione mysql_connect();
Questa funzione ritorna nel caso di SELECT, SHOW, DESCRIBE ed EXPLAIN un oggetto (o meglio, una risorsa) che contiene il risultato generato dalla query. Se la query contiene invece INSERT, UPDATE, DELETE o DROP ritorna true in caso di successo, altrimenti false.

Come possiamo esaminare il risultato restituito da mysql_query?
PHP offre più di una funzione! Prima ancora di andare a leggere il risultato contenuto nella variabile $risposta è forse utile conoscere il numero di righe che troveremo nell'oggetto. L'estrazione delle informazioni contenute in queste righe si esaurisce quasi sempre all'interno di un ciclo (come vedremo fra poco). Meglio stabilire fin dall'inizio se la query ha davvero generato un numero di righe utili, presumibilmente almeno uno! La funzione mysql_affected_rows($connessione) fa proprio questo: legge sulla connessione (quindi, variabile $connessione) il numero di righe generate come risposta all'ultima query di INSERT, UPDATE, REPLACE o DELETE.

Una delle funzioni PHP più usate per l'intercettazione delle informazioni all'interno delle righe ritornate da una query è la funzione mysql_fetch_array($risposta,TIPO_DI_INDIC) che ritorna un array di stringhe, indicizzato sia sui nomi dei campi dati che sugli indici numerici dell'array! Nei parametri passati alla funzione, $risposta è la variabile oggetto generata da mysql_query(), TIPO_DI_INDIC è una costante (opzionale) il cui valore stabilisce come deve essere indicizzato l'array. Può assumere uno di questi valori:
  • MYSQL_BOTH: indicizza l'array sia sui nomi dei campi dati che sugli indici dell'array;
  • MYSQL_ASSOC: indicizza l'array solo sui nomi dei campi dati;
  • MYSQL_NUM: indicizza l'array solo con indici numerici;
La funzione mysql_fetch_array() svolge anche un utile lavoro di scorrimento dell'array stesso. Attraverso un puntatore tiene la posizione dell'ultima riga letta, restituendone i valori a ogni chiamata. Quando non esistono più righe la funzione restituisce il valore false! Tale valore, allora, può essere restituito fin dall'inizio, se la query non genera alcun risultato!

Oppure:
La funzione mysql_fetch_assoc($risultati), a differenza di mysql_fetch_array(), raccoglie i risultati in un array associativo. Indicizzato, cioè, solo sui nome dei campi dati. Qualche volta può verificarsi un errore, perché non stamparlo sul monitor? Le funzioni mysql_errno($connessione) e mysql_error($connessione) ritornano rispettivamente il numero che identifica l'errore e la stringa che lo descrive (sono funzioni da non dimenticare se vogliamo aggiungere all'applicazione un database dedicato ai file di log). Se il nostro script va avanti per diverse righe dichiarando a destra e a sinistra variabili per contenere le risposte alle varie query è preferibile liberare le risorse occupate dalle variabili non più utilizzate con la funzione mysql_free($risultati) (che ritorna true in caso di successo, altrimenti false). A tale proposito, per non tenere sempre impegnate le risorse inutilizzate vi ricordo di chiuedere la connessione se non devono essere esguite sulla stessa altre query. La funzione mysql_close($connessione) chiude la connessione generata all'inizio da mysql_connect() e ora referenziata nella variabile $connessione. Questa deve essere una delle ultime istruzioni, non rimanete connessioni aperte! 
Altre utili funzioni PHP per MySQL sono: mysql_get_client_info() (che ritorna una stringa che descrive il tipo e la versione del client MySQL usato dall'utente che accede alla pagina PHP), mysql_info($connessione) (che ritorna informazioni sull'ultima query eseguita sul server MySQL) e mysql_stat($connessione) (che ritorna informazioni sullo stato del servizio e quindi del server MySQL).

giovedì 9 settembre 2010

Una rubrica con... MySQL

Avete mai pensato di implementare una rubrica telefonica? Si possono usare molte tecnologie e linguaggi di programmazione. Una soluzione sicuramente elegante e professionale consiste nell'adozione di un database e lasciare al linguaggio di programmazione l'interazione fra utente e dati. Con MySQL possiamo realizzare in pochi passi il database per la nostra applicazione, se questa cosa vi interessa vi dico come procedere.
La rubrica che ho in mente è abbastanza semplice, deve contenere il nome e il cognome dei nostri contatti e l'eventuale numero di telefono. Un contatto può non avere necessariamente un numero di telefono. Una domanda che merita fin dall'inizio una risposta è la seguente: quanti numeri di telefono può avere un contatto? La risposta a questo quesito condiziona la progettazione del database. Se la cardinalità del numero di telefono di un contatto è fissata ad 1 non si hanno molti problemi, un unica tabella raccoglierà: nome, cognome e numero di telefono. Sappiamo bene che questa limitazione non ci rende felici. Ecco allora che modelleremo il database per contenere zero o più numeri telefonici (contenti?).
Nello schema che vi propongo ho individuato due entità: contatti e rubrica. Nell'entità contatti ho messo gli attributi nome e cognome. In rubrica, invece, ho il solo attributo telefono. Un numero di telefono può essere assegnato ad uno o più contatti.


Nell'entità contatti ho poi introdotto la chiave primaria id, in rubrica ho scelto come chiave primaria l'attributo telefono. Queste sono le istruzioni SQL per la descrizione del database:
CREATE DATABASE rubrica_telefonica;
USE rubrica_telefonica;
CREATE TABLE contatti (
 id INT(3) NOT NULL AUTO_INCREMENT,
 nome VARCHAR(30) NOT NULL,
 cognome VARCHAR(30) NOT NULL,
 UNIQUE (nome, cognome),
 PRIMARY KEY (id)
);

CREATE TABLE rubrica (
 telefono VARCHAR(30) NOT NULL,
 contatto INT(3) NOT NULL REFERENCES contatti(id),
 PRIMARY KEY (telefono,contatto)
);
Ho poi aggiunto i permessi per permettere a un utente l'interazione con il database e le tabelle appena create:
GRANT INSERT,SELECT
ON rubrica_telefonica.*
TO php_user IDENTIFIED BY 'php_user';
Osservazione: grazie alla riga UNIQUE (nome, cognome) non potranno esistere occorrenze dell'entità contatti che hanno lo stesso attributo nome e cognome. Potranno tuttavia esistere contatti con lo stesso nome o cognome. Il database per lo nostra applicazione è pronto per essere usato. Vi riporto, per comodità, qualche utile query per l'estrazione dei dati:

Ricerca 1: per nome
SELECT telefono, cognome
FROM rubrica R JOIN contatti C ON R.contatto=C.id
WHERE C.nome='Luca'
Ricerca 2: per cognome
SELECT telefono, nome
FROM rubrica R JOIN contatti C ON R.contatto=C.id
WHERE C.cognome='Petrosino'
Ricerca 3: per nome e cognome
SELECT telefono, cognome
FROM rubrica R JOIN contatti C ON R.contatto=C.id
WHERE C.nome='Luca' AND C.cognome='Petrosino'
Ricerca 4: per numero
SELECT nome, cognome
FROM rubrica R JOIN contatti C ON R.contatto=C.id
WHERE R.telefono='0123456789'
Ricerca 5: il numero finisce per... 789
SELECT nome, cognome, telefono
FROM rubrica R JOIN contatti C ON R.contatto=C.id
WHERE R.telefono LIKE "%789"
Ricerca 6: il numero inizia per... 012
SELECT nome, cognome, telefono
FROM rubrica R JOIN contatti C ON R.contatto=C.id
WHERE R.telefono LIKE "012%"
Ricerca 7: contiene il numero... 345
SELECT nome, cognome, telefono
FROM rubrica R JOIN contatti C ON R.contatto=C.id
WHERE R.telefono LIKE "%012%"

venerdì 3 settembre 2010

MySQL: la gestione dei permessi

"Chi può fare cosa?" Una domanda che ricorre spesso in informatica. In MySQL la gestione dei permessi per gli utenti avviene con le clausole GRANT e REVOKE, sulla riga di comando del client. Per la concessione di privilegi bisogna osservare la seguente sintassi:
GRANT priv_type [column_list][,priv_type [column_list]]...
ON {*|*.*|db_name.*|db_name.tbl_name}
TO username [IDENTIFIED BY[PASSWORD] 'password'][,IDENTIFIED BY [PASSWORD] 'password']
Per la rimozione di permessi, invece:
REVOKE priv_type [column_list][,priv_type [column_list]]...
ON {*|*.*|db_name.*|db_name.tbl_name}
FROM username
I permessi che possiamo assegnare o revocare con con GRANT o REVOKE possono essere identificati dalle seguenti parole chiavi: SELECT (lettura dei valori in tabella), INSERT (scrittura di nuovi valori in tabella), UPDATE (modifica di valori in tabella), DELETE (eliminazione di valori presenti in tabella). Tali permessi, inoltre, possono essere combinati se separati con una virgola. Con la parola chiave ALL si abilitano (oppure vengono negati) all'utente tutti i permessi. Altri utili permessi sono: CREATE (per creare tabelle e/o database) e DROP (per cancellare tabelle e/o database).
Con la clausola ON possiamo estendere il permesso a uno o più database, e tabelle. Il simbolo * concede un permesso a tutti i database, la notazione *.* la estende invece a tutte le tabelle di tutti i database. Si può ovviamente essere più precisi se indichiamo il nome del database. La stringa nome_database.* estende il permesso a tutte le tabelle del database con con nome nome_database. Più precisa, infine, è la sintassi nome_database.nome_tabella che denota la tabella con nome nome_tabella del database con nome nome_database.
L'ultima clausola, TO, convoglia i permessi (da far valere nei confronti di tabelle e/o database) verso l'account di uno o più utenti. E' in questa clausola che occorre fornire il nome dell'utente coinvolto nella gestione dei permessi, facendo poi seguire alla parola chiave IDENTIFIED BY la password usata dallo stesso! Inoltre, se al nome dell'utente, dopo il simbolo @, indichiamo il nome dell'host da usare per la connessione obblighiamo l'utente a collegarsi al database sempre attraverso lo stesso host indicato nella GRANT. Se non specificato, di default, un utente può collegarsi da qualsiasi host. Per la revoca dei permessi, con REVOKE, è sufficiente indicare solo il nome dell'utente.

giovedì 2 settembre 2010

PHP: recuperare i parametri da un form

Il passaggio di informazioni dal client al server può avvenire attraverso due metodi (ormai noti): GET e POST. Il metodo GET passa le eventuali informazioni al server attraverso l'url, accodandole all'indirizzo della pagina richiesta!
La regola usata anche in altri linguaggi di scripting e tecnologie per il web è la seguente: la query string (così viene detta la stringa con i parametri da passare al server) viene fatta seguire all'url della pagina, separata da quest'ultimo con il carattere ? Ogni nome e valore di variabile viene quindi aggiunto nella query string osservando la seguente sintassi: nome_variabile=valore, più valori sono poi separati tra loro dal carattere &. Ad esempio, l'indirizzo:
http://mio_sito/pagina.php?variabile1=valore1&variabile2=valore2
accede alla pagina pagina.php del mio_sito passando i valori delle variabili: variabile1 e variabile2. PHP memorizza la query string nell'array $_GET, le cui chiavi sono i nomi delle variabili passate. Pertanto, con l'istruzione $_GET['variabile1'] si accede al valore della variabile1 (che è valore1).
Alla semplicità e alla vulnerabilità del metodo GET (che usa una query string visibile nella barra degli indirizzi del browser, inadeguato dunque per la comunicazione di informazioni sensibili) pone rimedio il metodo POST che trasmette i nome ed i valori delle variabili in un header (a parte) della richiesta http. PHP usa per il metodo POST l'array $_POST, indicizzato anche in questo caso con i nomi delle variabili. Come prima, con l'istruzione $_POST['variabile1'] accediamo a valore1. Di seguito mostro due screen shot di un form per la registrazione, prima di inviare i dati:

e dopo l'invio dei dati:

Con le istruzioni: $_POST['user'], $_POST['password'], $_POST['sesso'] e $_POST['ruolo'] accedo rispettivamente al nome utente, alla password, al sesso e al ruolo inviato dall'utente. I nomi di tali campi dati sono stati precedentemente assegnati al form (la prima immagine) sicché attraverso la request riesco adesso a intercettarne i valori!

Apache2 e modulo PHP: visualizzare gli errori nel browser

L'installazione del modulo PHP per Apache2 usa una configurazione un po restrittiva per gli errori, visualizzati solo all'interno del file di log. La visualizzazione degli stessi all'interno del browser non può che velocizzare lo sviluppo dell'applicazione, soprattutto le prime volte (quando carichiamo più volte la pagina a caccia di eventuali errori).
Se avete letto l'articolo precedente sapete allora su quale file agire per modificare tale comportamento. Il file php.ini è strutturato in più sezioni ampiamente commentate. Per quanto riguarda gli errori bisogna cercare all'interno del file le voci error_reporting ed error_display. PHP usa vari livelli di debug per gli errori (anche questi elencati nel file), di default viene assegnata la stringa E_ALL & ~E_NOTICE alla variabile error_reporting. Questo abilita il modulo PHP alla scrittura di tutti egli errori (nel file di log) ad eccezione degli avvisi di warning. Se si verificano tali errori, lo sviluppatore vedrà all'interno del suo browser un'inutile pagine bianca! Per abilitare la visualizzazione di tutti gli errori nel browser occorre aprire il file php.ini con il nostro editor preferito. Ad esempio con:
sudo mousepad /etc/php5/apache2/php.ini
e modificare la riga error_reporting=E_ALL & ~E_NOTICE in error_reporting=E_ALL (questo abilita tutti i livelli di debug solo nel file di log). Per la visualizzazione all'interno del browser va infine modificata la riga error_display=Off in error_display=On. Se il server web è in esecuzione occorre poi riavviarlo: sudo /etc/init.d/apache2 restart. Per mettere alla prova il modulo PHP sulla nuova configurazione vi consiglio di scrivere una pagina contenente almeno un errore: riuscite a leggere nel browser l'errore che viene rilevato?

mercoledì 1 settembre 2010

ext4 su SSD: si, ma senza la funzione journaling

Uno dei file system senza dubbio più usato nei sistemi GNU/Linux è il file system ext2 (second extended file system). La semplicità e la stabilità di questo file system sono alla base del successo di ext2. Per questo troviamo sempre ext2 fra le possibili scelte di file system quando ci apprestiamo a installare Linux sul nostro computer.
ext4 (fourth extended filesystem), rilasciato nel 2006, segue ad ext3 e introduce nuove e interessanti funzionalità. Il suo sviluppo consiste essenzialmente in alcune estensioni aggiuntive fatte su ext3 (non senza polemiche, ext4 nasce come fork di ext3 ed è stato successivamente integrato nel kernel a partire dalla versione 2.6.28). Ecco alcune (delle principali) caratteristiche di ext4:
  • Indirizzamento a 48 bit, la conseguenza di tutto ciò si traduce nell'aumento della dimensione massima per un file;
  • Introduzione di extent per la gestione di blocchi: un extent rappresenta un gruppo di blocchi contigui appartenenti a un file e favorisce la non deframmentazione del disco. In altre parole per un file viene creato un extent di blocchi contigui, ove possibile, anziché memorizzare il contenuto del file in tanti blocchi non contigui (che comportano tra l'altro la gestione, a volte molto onerosa, di una mappa di blocchi liberi);
  • Introduzione dell'allocazione multiblocco: l'ispirazione nasce sempre sui predecessori, ext3 usa un'allocatore per blocchi (presi dalla mappa di blocchi liberi) che assegna al file 4KB alla volta! La chiamata avviene tuttavia in maniera iterativa e l'allocatore viene chiamato n volte! Con n pari alla dimensione del file da scrivere diviso la dimensione del blocco. Non vi sembra un lavoro eccessivo? ext4, invece, alloca con una sola chiamata i blocchi necessari al file (definendo un extent);
  • Uso dell'allocazione ritardata: va subito detto, per tranquillizzare molti utenti, che il contenuto del file system non subisce perdite di informazioni. Se un file deve essere scritto sul file system ne ritroveremo comunque il contenuto! Questa caratteristica ha un impatto a mio avviso eccezionale sulle prestazioni del file system (che poi ritroviamo in una maggiore reattività del sistema). Durante la scrittura di un file, ext3 allocherebbe di continuo blocchi utili al file stesso, pur non sapendo se quest'ultimo possa effettivamente servire (pensate ad esempio a un file temporaneo, prima scritto e poi cancellato dopo essere stato consumato; oppure a un file in download in continua crescita). ext4 invece pone i dati del file in una cache (solitamente la memoria ram del computer), almeno finché può farlo, riportandone poi il contenuto (in blocchi contigui, extent) sul filesystem;
  • Preallocazione di blocchi: avete mai usato Torrent o programmi di P2P simili? Quando ha inizio il download del file viene allocato un file (riempito man mano che il download procede) avente le stesse dimensioni del file finale. L'ispirazione, in questo caso, nasce da lì: è importante per alcune applicazioni (soprattutto quelle real time) disporre dello spazio per un file che verrà poi scritto successivamente;
  • Uso di H-tree per superare il limite del numero di cartelle in una cartella e migliorare la gestione delle cartelle di grossa dimensione;
  • Controllo più rapido con fsck: in ext4 esiste una tabella con gli i-node inutilizzati sicché fsck saprà bene cosa fare (ovvero saltare il controllo di quel gruppo);
  • Uso della funzione journaling: è sicuramente una delle funzioni che contribuisce di più alla consistenza del file system! Immaginate di scrivere in un diario e sotto forma di un unico blocco le operazioni da fare per portare a termine una determinata cosa. Solo al completamento dell'ultima operazione potete allora affermare di aver completato l'operazione scritta sul diario, se una sola delle operazioni in sequenza non avviene si può in ogni caso tornare dietro e ripristinare tutto com'era prima (operazione di rollback). ext4 realizza una cosa come questa quando deve scrivere sul file system! Se durante un processo di scrittura viene a mancare, ad esempio, la corrente il sistema operativo leggendo un file di log si accorge di un'operazione incompleta e ripristina lo stato del file system. Tutto questo comporta tuttavia un lavoro aggiuntivo per la cpu e il disco, in alcuni casi tale funzione può comunque essere disattivata.
Non vi è dubbio dei vantaggi portati da ext4, potete sperimentarli stesso voi. Vi assicuro che (su PC di ultima generazione) avvertirete l'incremento. Ho sperimentato di persona l'uso di questo file system anche sull'Aspire One (il modello 110L, con 512MB di memoria ram e disco SSD) e ne consiglio l'uso a patto di disabilitare la funzione di journaling. Purtroppo il disco SSD usato di default in questo modello non ha prestazioni eccezionali, anche se va comunque detto che rispetto ai sistemi Windows questo disco non sembra essere lo stesso (lì la situazione è ancora più drammatica).
Vi dico come disabilitare questa funzione in Xubuntu (10.04), procuratevi un pen-drive usb con una distribuzione live di Linux (quella usata per installare il sistema operativo che adesso state usando va bene). Avviate l'Aspire One in modalità live, in questo modo il disco SSD non verrà neppure montato e noi possiamo applicare in tutta tranquillità le modifiche al disco. Avviato?
Mi raccomando, non montate la partizione sul disco SSD (ad esempio andando a vedere nel file manager il contenuto dello stesso, di default il sistema operativo monterà la partizione di quel disco, non fatelo!) Aprite ora una finestra di terminale e date il comando:
sudo tune2fs -O ^has_journal /dev/sda1
sostuite sda1 con il nome della vostra partizione, qualora dovesse essere diversa da quella mostrata nell'esempio. Se non si sono verificati errori di battitura (potete copiare e incollare il comando direttamente nel vostro terminale) potete chiudere le finestre aperte e riavviare l'Aspire One, questa volta facendo caricare il sistema operativo presente sul disco (non dimenticate di staccare la pen-drive usb).
Per verificare l'avvenuta disabilitazione della funzione di journal possiamo dare il seguente comando:
sudo dumpe2fs -h /dev/sda1 | grep features
modificate sempre la partizione sda1 usata nell'esempio se non corrisponde a quella da voi usata. Il comando può essere usato prima e dopo la modifica al file system. Se eseguito prima della modifica questo comando ritorna un elenco delle funzioni abilitate sul file system e fra queste, quindi, anche quella di journaling. Dopo la modifica, invece, tale voce scompare e simboleggia l'avvenuta modifica al file system.