Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Prepared Statement in JDBC

Introduzione al funzionamento pratico dei Prepared Statement per fare query sui database
Introduzione al funzionamento pratico dei Prepared Statement per fare query sui database
Link copiato negli appunti

JDBC rappresenta un'astrazione che consente di interfacciarsi con un DataBase generico, prescindendo dalle specifiche del singolo produttore.

Questo vuol dire che, dal punto di vista del codice Java, non sarà necessario preoccuparsi delle specifiche implementative associate al singolo DBMS per agire su un qualsivoglia DataBase, in quanto tale compito è delegato a una libreria proprietaria con la quale JDBC comunica direttamente.

Normalmente, una operazione su un DataBase si riconduce ai seguenti passi:

  1. Caricamento del Driver specifico del DB che si intende utilizzare
  2. Apertura di una connessione al DB, associata all'utilizzo di un oggetto di tipo java.sql.Connection
  3. Creazione di un oggetto di tipo java.sql.Statement
  4. Esecuzione di uno o più comandi sul DB attraverso i metodi messi a disposizione dall'oggetto Statement precedentemente creato
  5. Elaborazione del risultato
  6. Chiusura del DB

In temini di codice, questo si identifica con le seguenti istruzioni (si noti che le stringhe DbDriver e DbURL andranno sostituite con valori opportuni, dipendenti dal DB che si sta utilizzando):

Listato 1. Processo di uso di un DB

Connection connection = null;
String DbDriver "xxx";      // La stringa contenente il nome del driver
String DbURL = "yyy";    // La stringa contenente la URL del DB
String username = "admin"; // UserID di connessione
String password = "password"; // Password associata alla username

try
{
    Class.forName(DbDriver); // Carica il Driver del DBMS
    
    // Apertura connessione
    connection = DriverManager.getConnection(DbURL, username, password);
    
    // Creazione oggetto Statement
    Statement statement = connection.createStatement();
    
    // Esecuzione di un comando sul DB
    ResultSet rs = statement.executeQuery("SELECT * FROM Employee");
    
    // Elaborazione del risultato
    while (rs.next())
    {
        // Elabora il risultato
    }
    
    // Chiusura della connessione
    connection.close();
}
catch (ClassNotFoundException ex){}
catch (SQLException ex){}
catch (Exception ex){}

Il problema degli Statement

Spesso, durante la fase di sviluppo di un progetto, si è soliti suddividere i compiti tra più squadre in modo da ottimizzare i tempi di produzione. Accade, in questi casi, che ogni team possa decidere di utilizzare un proprio DBMS per eseguire determinate parti di codice, senza la necessità di preoccuparsi di quale poi sia il DB da utilizzare in fase di deployment o di uniformarsi al DB utilizzato dagli altri gruppi di sviluppo.

Nonostante JDBC garantisca l'indipendenza dalla piattaforma, il problema che può nascere è che il linguaggio SQL utilizzato negli statement potrebbe non esserlo.

Un esempio classico è rappresentato dall'utilizzo degli apici e doppi apici all'interno di una query. Si consideri la seguente stringa:

update Employee SET Nome = "Marco" WHERE Cognome = "Altese"

Se la si esegue su MySQL essa non darà alcun problema e aggiornerà la tabella Employee come richiesto.

Se il DB utilizzato fosse Sybase la stringa di aggiornamento precedente produrrà un errore (quindi un'eccezione sul codice Java).

Un discorso analogo, sempre in tema di apici, lo si può fare quando si desidera che un singolo apice risulti presente all'interno del valore che si intende assegnare a un campo. Per esempio, se si desidera impostare il campo cognome con la stringa D'Arrigo, si dovrà procedere in maniera differente a seconda del DB utilizzato. Per alcuni Data Base sarà sufficiente impostare la stringa come D''Arrigo (si faccia attenzione al fatto che si stanno utilizzando due singoli apici concatenati e non un singolo carattere di tipo doppio apice) mentre per altri sarà opportuno utilizzare una sequenza di tipo Escape e scrivere D'Arrigo.

Una soluzione potrebbe essere quella di ricorrere a uno stratagemma di tipo programmatico e scrivere un metodo che converta ad hoc eventuali stringhe non congruenti con il DB utilizzato. Ma esiste anche un rimedio più versatile.

I PreparedStatement

JDBC viene incontro al problema di compatibilità precedentemente evidenziato attraverso una interfaccia denominata java.sql.PraparedStatement.

Oltre a questo vantaggio, attraverso tale interfaccia, è possibile sfruttare al meglio il meccanismo di caching messo a disposizione dalla maggior parte dei data base.

Molti DBMS mantengono in cache le istruzioni SQL precedentemente utilizzate e le mettono a disposizione qualora venga inviata al DB stesso un'istruzione analoga. Quindi utilizzando un comando già compilato e ottimizzato si ha un miglioramento generale delle performance.

Tale ragionamento è valido, però soltanto se le istruzioni pervenute sono esattamente identiche in forma e contenuto. Ovvero, se pervenissero in tempi diversi, le due istruzioni:

insert into Employee values ('Mario', 'Rossi', 'Analyst')
insert into Employee values ('Marco', 'Bianchi', 'Analyst')

il meccanismo di caching non potrebbe essere utilizzato, in quanto pur effettuando la medesima operazione (inserimento nella tabella Employee), la seconda istruzione contiene valori differenti e rappresenta un'istruzione SQL differente.

Il problema si risolverebbe facilmente passando al database un comando SQL che contenga una serie di variabili, in modo che il DB possa riutilizzare lo stesso comando ogni volta, senza preoccuparsi dei valori cui le variabili stesse fanno riferimento. I Prepared Statement mettono in pratica proprio tale strategia.

Creazione di un PreparedStatement

La creazione di un oggetto di tipo PreparedStatement è simile a quella utilizzata per gli oggetti di tipo Statement.

La differenza di rilievo consiste nel fatto che, con i PreparedStatement, è necessario indicare al database quale sia l'SQL che si intende eseguire; ovvero viene passato l'SQL nel metodo di creazione invece che nel metodo di esecuzione.

Ecco quali sono i metodi utilizzabili per creare un oggetto di tipo PreparedStatement:

  • prepareStatement (String sql): Crea un oggetto di tipo PreparedStatement relativo alla stringa SQL passata in input. Qualora venga restituito un resultset associato al Prepared Statement, questo sarà di tipo forward-only e non holdable.
  • prepareStatement(String sql, int resultSetType, int resultSetConcurrency): Come il precedente metodo con la differenza che il tipo di resultset e la concorrenza dello stesso resultset sono impostati in input. Anche in questo caso, il recordset sarà non holdable.
  • prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability): Come il precedente, ma con la capacità di assegnare anche l'holdability attraverso il parametro di input.

Per chi non avesse chiari i concetti di type, concurrency e holdability relativamente a un resultset, ne riportiamo una breve definizione:

  • Type: Definisce se un recordset sia o meno navigabile (scrollable) in avanti e indietro. Il type forward-only definisce un recordset non scrollable.
  • Concurrency: Definisce la capacità di aggiornare un resultset
  • Holdability: Normalmente quando si esegue un altro comando SQL con un prepared statement, gli eventuali resultset rimasti aperti vengono chiusi. Lo stesso avviene quando viene invocato il metodo commit(). Attraverso la feature denominata holdability (introdotta con JDBC 3.0) è possibile decidere se chiudere o meno un resultset in tali circostanze.

Il primo argomento passato nei metodi sopra enunciati è una stringa SQL. Essa può contenere delle variabili che rappresentano i dati che saranno impostati successivamente. Tali variabili sono denominate placeholders e sono rappresentate ciascuna da un punto interrogativo. Pertanto i due comandi SQL:

insert into Employee values ('Mario', 'Rossi', 'Analyst')
insert into Employee values ('Marco', 'Bianchi', 'Analyst')

saranno tradotti, attraverso l'utilizzo di PreparedStatement, in :

insert into Employee values (?, ?, ?)

Ognuno dei placeholder è referenziato attraverso l'indice che occupa all'interno del comando SQL.

Quando il comando SQL, presente nel Prepared Statement, viene inviato al database, quest'ultimo lo compila e rimane in attesa dei dati effettivi che consentano di eseguire il comando stesso. È proprio prima dell'esecuzione del comando che vengono sostituiti i placeholder con i dati: il driver invia i dati al database attraverso l'esecuzione del prepared statement e il DB imposta le variabili con i dati ricevuti per eseguire il comando SQL.

Come utilizzare i Prepared Statement

Abbiamo detto che i placeholder devono essere impostati dopo la creazione dell'oggetto di tipo PreparedStatement ma prima che il comando SQL possa essere eseguito. Vediamo, in pratica, come avvengono tali azioni.

I metodi, messi a disposizione dall'interfaccia PreparedStatement, hanno la seguente forma:

setTIPO()

dove TIPO rappresenta un tipo di dato in Java. Per esempio, il metodo per impostare una Stringa è il seguente:

void setString (int parameterIndex, String x)

Il primo argomento in input rappresenta l'indice del placeholder all'interno del comando SQL (il primo indice ha valore 1, il secondo 2 e così via). Il secondo argomento è il valore che andrà a rimpiazzare il placeholder referenziato:

Riprendiamo il comando visto in precedenza:

insert into Employee values (?, ?, ?)

I placeholder in esso contenuti saranno così impostati (nel caso di Mario Rossi):

String sql = "insert into Employee values (?, ?, ?)";
PreparedStatement prepStat = conn.prepareStatement(sql);
prepStat.setString(1, "Mario");
prepStat.setString(2, "Rossi");
prepStat.setString(3, "Analyst");
prepStat.executeUpdate();

È importante che vengano impostati tutti i parametri prima di eseguire l'istruzione executeUpdate(). In caso contrario, il driver scatenerà un'eccezione di tipo SQLException. Tale considerazione è importante quando si vogliono inserire dei valori di tipo NULL. Non bisognerà, infatti, evitare di scrivere l'istruzione che imposti il placeholder, come si potrebbe essere portati a pensare ma, al contrario, bisognerà avvalersi di uno dei seguenti due metodi:

  • void setNull(int parameterIndex, int sqlType)
  • void setNull(int parameterIndex, int sqlType, String typeName)

Il primo parametro indica l'indice del placeholder mentre il secondo rappresenta il tipo di dato presente sulla colonna nullable della tabella cui si fa riferimento. Questo parametro viene impostato attraverso l'utilizzo della classe java.sql.Types che contiene una serie di costanti corrispondenti a ogni tipo di dato presente in JDBC.

Per esempio se si vuole assegnare il valore NULL a una stringa andrà utilizzato il valore java.sql.Types.STRING.

Il terzo parametro, definito nel secondo metodo setNull(), viene utilizzato nei casi in cui si faccia uso di tipi di dati definiti dall'utente. In tal caso, si crea una classe Java associata a tale tipo e nel metodo setNull() si imposta il parametro sqlType con il valore java.sql.Types.OBJECT.

Il parametro typeName contiene la stringa che identifica la classe Java associata al tipo definito dall'utente.

Riutilizzare un Prepared Statement

Una volta che un placeholder è stato impostato con dei dati, esso rimane disponibile all'applicazione fin quando non si decida di modificarlo esplicitamente attraverso il codice. Questo consente di eseguire anche più volte del codice SQL attraverso il medesimo prepared statement senza reimpostare ogni volta i valori dei placeholder.

È anche possibile modificare una parte dei placeholder utilizzati e lasciare invariati gli altri. Per esempio, riallacciandoci all'ultimo esempio visto, si potrà scrivere:

prepStat.setString(1, "Marco");
prepStat.setString(2, "Bianchi");
prepStat.executeUpdate();

In tal caso vengono modificati i primi due placeholder lasciando inalterato il terzo ("Analyst") ed eseguito l'inserimento di un nuovo record sulla tabella Employee.

Ti consigliamo anche