6. [Cours]: Introduzione a API JDBC
Parole chiave: database relazionali, API JDBC, SQLException.
6.1. Support
![]() | ![]() | ![]() |
La cartella [support / chap-06] contiene i progetti Eclipse di questo capitolo.
6.2. Architecture
![]() |
Il livello JDBC (Java DataBase Connectivity) è un'interfaccia universale di accesso ai database. Presenta sempre la stessa interfaccia verso il livello [DAO]. Se si cambia SGBD, è sufficiente cambiare il driver JDBC. Il livello [DAO] rimane invariato.
6.3. Le fasi di gestione di un database
![]() |
Nell’architettura sopra descritta, l’esecuzione di un database da parte del programma da console prevede le seguenti fasi:
- caricamento del driver JDBC del database;
- apertura di una connessione con il database;
- invio di un comando SQL al database ed elaborazione dei risultati del comando SQL;
- chiusura della connessione;
Il passaggio 1 viene eseguito una sola volta. I passaggi 2-4 vengono ripetuti. Si noti che non si lascia una connessione aperta. La si chiude non appena non è più necessaria.
6.3.1. fase 1 - caricamento in memoria del driver JDBC
Il codice
// caricamento del driver JDBC
try {
Class.forName(nom de la classe du pilote JDBC);
} catch (ClassNotFoundException e1) {
// gestione dell'eccezione
}
L'operazione della riga 3 ha lo scopo di caricare in memoria il driver JDBC dal database. Questa operazione deve essere eseguita una sola volta. Ripeterla, tuttavia, non causa alcun errore. La classe del driver JDBC viene cercata nel Classpath del progetto. È quindi necessario che nel progetto Eclipse il file [jar], contenente la classe del driver JDBC, sia stato incluso nel Classpath del progetto.
6.3.2. Fase 2 - Apertura di una connessione
Una volta installato il driver JDBC, gli viene richiesto di aprire una connessione con il BD:
Il codice
package spring.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class IntroJdbc01 {
...
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// apertura della connessione
connexion = DriverManager.getConnection(url, user, passwd);
...
} catch (SQLException e1) {
// si gestisce l'eccezione
...
} finally {
// chiusura della connessione
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e2) {
// gestire l'eccezione
...
}
}
}
- righe 3-7: le classi di implementazione dell'interfaccia JDBC si trovano tutte nel pacchetto [java.sql]. Inoltre, in caso di errore, tutte generano un'eccezione di tipo [SQLException] (righe 19, 27). Questa eccezione deriva dalla classe [Exception] ed è un'eccezione cosiddetta «controllata»: è necessario inserire un try/catch per gestirla oppure, in alternativa, non gestirla e indicare che il metodo lascia passare l'eccezione completando la firma del metodo con [throws SQLException];
- riga 17, [DriverManager.getConnection] è un metodo statico che richiede tre parametri:
- [url]: il URL del database. Si tratta di una stringa di caratteri che dipende dal BD utilizzato. Per MySQL, ha la forma [jdbc:mysql://localhost:3306/nom_de_la_bd];
- [user]: il proprietario della connessione;
- [passwd]: la sua password;
- righe 24-30: la connessione deve essere chiusa nella clausola [finally] affinché venga chiusa indipendentemente dal verificarsi o meno di un'eccezione.
6.3.3. Fase 3 - Emissione dei comandi SQL e [SELECT]
Una volta ottenuta la connessione, è possibile inviare comandi SQL. La gestione dei comandi di lettura [SELECT] differisce da quella utilizzata per le operazioni di aggiornamento [UPDATE, INSERT, DELETE]. Iniziamo con i comandi SQL e [SELECT]:
Il codice
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// apertura sessione
connexion = DriverManager.getConnection(url, user, passwd);
// inizio transazione
connexion.setAutoCommit(false);
// in modalità sola lettura
connexion.setReadOnly(true);
// si legge la tabella [PRODUITS]
ps = connexion.prepareStatement("SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS");
rs = ps.executeQuery();
System.out.println("Liste des produits : ");
while (rs.next()) {
System.out.println(new Produit(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getDouble(4), rs.getString(5)));
}
// conferma transazione
connexion.commit();
} catch (SQLException e1) {
// gestione dell'eccezione
doCatchException(connexion,e1);
} finally {
// si gestisce il finally
doFinally(rs, ps, connexion);
}
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
....
}
- righe 8, 10: apertura di una transazione (riga 8) in modalità di sola lettura (riga 10). Una transazione è una sequenza di comandi SQL che o vanno tutti a buon fine o falliscono tutti. Pertanto, in una transazione composta da N comandi SQL, se il comando I+1 fallisce, i precedenti I comandi verranno annullati. Per un'operazione di lettura, non è necessaria una transazione. Tuttavia, la creazione di una transazione in sola lettura può consentire ad alcuni SGBD di effettuare determinate ottimizzazioni;
- riga 12: utilizzo di un [PreparedStatement]. Un [PreparedStatement] presenta normalmente parametri indicati dal carattere ?. In questo caso non ne ha. Un [PreparedStatement] è un comando preparato dal SGBD. Questa preparazione ha un costo e viene eseguita una sola volta. Successivamente, questo comando preparato viene eseguito dal SGBD con diversi parametri effettivi che andranno a sostituire i parametri formali «?». Da notare che è preferibile specificare le colonne desiderate piuttosto che utilizzare il simbolo * per ottenere tutte le colonne. Specificando il nome delle colonne è poi possibile ottenere i loro valori in base alla loro posizione nella query SELECT;
- riga 13: esecuzione di [PreparedStatement]. Si recupera un oggetto di tipo [ResultSet];
Un oggetto di tipo [ResultSet] rappresenta una tabella, ovvero un insieme di righe e colonne. In un dato momento, si ha accesso solo a una riga della tabella, denominata riga corrente. Al momento della creazione iniziale del [ResultSet], non esiste una riga corrente. È necessario eseguire un’operazione [ResultSet.next()] per ottenerla. La firma del metodo next è la seguente:
Questo metodo tenta di passare alla riga successiva del [ResultSet] e restituisce true in caso di esito positivo, false in caso contrario. In caso di esito positivo, la riga successiva diventa la nuova riga corrente. La riga precedente viene persa e non sarà possibile tornare indietro per recuperarla.
La tabella di [ResultSet] presenta colonne denominate labelCol1, labelCol2, ... specificate nella query [SELECT] eseguita. Con la query:
SELECT ID as myId, NOM as myNom, CATEGORIE as myCategorie, PRIX as myPrix, DESCRIPTION as myDescription FROM PRODUITS
- la colonna [ID] verrà inserita in una colonna del [ResultSet] denominata [myId];
- la colonna [NOM] verrà trasferita in una colonna del [ResultSet] denominata [myNom];
- ...
Nell’esempio sopra riportato, gli identificatori [myCol] sono denominati etichette di colonna. In assenza di tali etichette, i nomi delle colonne del [ResultSet] dipendono dal SGBD. Quando il [SELECT] opera su una singola tabella, le etichette delle colonne saranno per impostazione predefinita i nomi delle colonne richieste dal SELECT. Il problema si presenta quando il [SELECT] opera su più tabelle e in queste sono presenti nomi di colonne identici, come nell’esempio seguente:
SELECT PRODUITS.NOM, CATEGORIES.NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
supponendo che la tabella [PRODUITS] abbia una chiave esterna verso la tabella [CATEGORIES], rappresentata dalla relazione [Produits].CATEGORIE_ID --> [CATEGORIES].ID, e che le tabelle [PRODUITS] e [CATEGORIES] abbiano entrambe un campo [NOM]. In questo caso, i nomi assegnati nel [ResultSet] alle colonne [PRODUITS.NOM] e [CATEGORIES.NOM] dipendono dal SGBD. Per garantire la portabilità tra i file SGBD, è quindi necessario utilizzare qui le etichette delle colonne e si scriverà:
SELECT PRODUITS.NOM as p_NOM, CATEGORIES.NOM as c_NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
Per utilizzare i diversi campi della riga corrente di [ResultSet], sono disponibili i seguenti metodi:
per ottenere la colonna denominata «labelColi» della riga corrente e quindi la colonna del [SELECT] con tale etichetta. Type indica il tipo del campo coli. È possibile utilizzare i seguenti metodi [getType]: getInt, getLong, getString, getDouble, getFloat, getDate, ... Anziché utilizzare il nome della colonna, è possibile utilizzare la sua posizione nella query [SELECT] eseguita:
dove i è l’indice della colonna desiderata (i>=1).
- righe 15-17: recupero dei valori letti nella BD;
- riga 19: la transazione viene convalidata (si dice anche «committata»). Ciò la conclude e libera le risorse che la transazione SGBD aveva mobilitato per essa;
- riga 25: le risorse vengono liberate nella transazione [finally]. Quest’ultima richiama il metodo [doFinally] seguente:
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// chiusura ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// chiusura [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
if (connexion != null) {
try {
// chiudere la connessione
connexion.close();
} catch (SQLException e3) {
// gestire l'eccezione
}
}
}
- righe 3-9: chiusura del [ResultSet];
- righe 11-17: chiusura di [PreparedStatement];
- righe 18-27: chiusura della connessione;
Le chiusure delle righe 3-17 sembrano ridondanti, dato che la connessione viene chiusa alle righe 18-25. In realtà, in alcuni casi non lo sono ed è consigliabile lasciarle [http://stackoverflow.com/questions/4507440/must-jdbc-resultsets-and-statements-be-closed-separately-although-the-connection].
- riga 22: l'eccezione viene gestita dal seguente metodo [doCatchException]:
private static void doCatchException(Connection connexion, Throwable th) {
// annullamento della transazione
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// gestire l'eccezione
}
}
- righe 4-6: la transazione viene annullata. Ciò la conclude e il metodo SGBD potrà liberare le risorse impiegate per essa;
6.3.4. fase 3 - emissione degli ordini SQL e [INSERT, UPDATE, DELETE]
Gli ordini SQL e [INSERT, UPDATE, DELETE] sono operazioni di aggiornamento: modificano il database ma non restituiscono alcuna riga. L’unica informazione fornita è il numero di righe interessate dall’operazione di aggiornamento.
Il codice
Connection connexion = null;
PreparedStatement ps = null;
try {
// apertura connessione
connexion = DriverManager.getConnection(url, user, passwd);
// inizio transazione
connexion.setAutoCommit(false);
// in modalità lettura/scrittura
connexion.setReadOnly(false);
// si aggiorna la tabella
ps = connexion.prepareStatement("UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?");
// categoria 1
ps.setInt(1, 10);
// esecuzione
int nbLignes=ps.executeUpdate();
// conferma transazione
connexion.commit();
} catch (SQLException e1) {
// si gestisce l'eccezione
doCatchException(connexion, e1);
} finally {
// si gestisce il finally
doFinally(null, ps, connexion);
}
}
- riga 9: la connessione viene utilizzata in lettura e scrittura;
- riga 11: un [PreparedStatement] con 1 parametro (simboleggiato da ?). È possibile avere più parametri. Sono numerati a partire da 1;
- riga 13: si assegna il valore all’unico parametro. Il primo parametro di [setType] è la posizione del parametro nel [PreparedStatement] (1, 2, ...) e il secondo è il valore ad esso attribuito. È possibile utilizzare i metodi [setInt, setLong, setFloat, setDouble, setString, setDate, ...];
- riga 15: si utilizza il metodo [executeUpdate] e non [executeQuery], riservato agli ordini SELECT. Il metodo restituisce il numero di righe interessate dall’operazione. Può essere 0.
- riga 17: la transazione viene convalidata;
6.3.5. fase 4 - chiusura della connessione
In un contesto multiutente, una connessione deve essere chiusa il più rapidamente possibile, poiché un SGBD accetta un numero limitato di connessioni aperte. Negli esempi precedenti, veniva chiusa nella clausola [finally] delle operazioni SQL in modo che venisse chiusa indipendentemente dal verificarsi o meno di un'eccezione.
6.4. Un progetto di esempio
6.4.1. Supporto
![]() |
La cartella [support / chap5] contiene i progetti Eclipse di questo capitolo [1, 2]. La cartella [database] contiene lo script SQL che consente di creare il database di esempio MySQL di questo capitolo [1, 3].
6.4.2. Il database utilizzato
Gli esempi che seguono utilizzano il seguente database MySQL:
![]() |
- [ID]: chiave primaria in modalità AUTO_INCREMENT (se non viene specificata una chiave primaria, SGBD la genera);
- [NOM]: nome di un prodotto - univoco;
- [CATEGORIE]: numero della categoria;
- [PRIX]: il suo prezzo;
- [DESCRIPTION]: una descrizione del prodotto;
Lo creeremo con lo strumento [WampServer] nel modo seguente [1-9]:
![]() |
![]() |
![]() |
![]() |
6.4.3. Il progetto Eclipse
![]() |
Il progetto è un progetto Maven definito dal seguente file [pom.xml]:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.jdbc</groupId>
<artifactId>intro-jdbc-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.1</version>
</dependency>
</dependencies>
</project>
- righe 8-12: il driver JDBC di SGBD MySQL5;
- righe 13-17: una libreria in grado di gestire il jSON (Javascript Object Notation) (cfr. paragrafo 22.6). La useremo per visualizzare in formato jSON i prodotti presenti nel database;
6.4.4. La classe dei prodotti
La classe [Produit] è la seguente:
package istia.st.jdbc;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Produit {
// campi
private int id;
private String nom;
private int categorie;
private double prix;
private String description;
// costruttori
public Produit() {
}
public Produit(int id, String nom, int categorie, double prix, String description) {
this.id = id;
this.nom = nom;
this.categorie = categorie;
this.prix = prix;
this.description = description;
}
// getter e setter
...
// in String
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
}
- riga 34: si utilizza la libreria jSON per visualizzare la stringa jSON del prodotto. Il risultato è una visualizzazione simile alla seguente:
Liste des produits :
{"id":1,"nom":"NOM1","categorie":1,"prix":100.0,"description":"DESC1"}
{"id":2,"nom":"NOM2","categorie":1,"prix":101.0,"description":"DESC2"}
Il vantaggio del metodo [toString] sopra indicato è che, se si aggiungono o si rimuovono campi dalla classe, il suo metodo [toString] rimane sempre valido. Inoltre, se i campi sono essi stessi oggetti (liste, array, dizionari, oggetti utente), le librerie jSON sono in grado di trasformarli a loro volta in stringhe jSON;
6.4.5. La classe [Static]
La classe [Static] raggruppa in metodi il codice utilizzato frequentemente nella classe principale:
package istia.st.jdbc;
import java.util.ArrayList;
import java.util.List;
public class Static {
public static List<String> getErreursFromThrowable(Throwable th) {
// si recupera l'elenco dei messaggi di errore dell'eccezione
List<String> erreurs = new ArrayList<String>();
while (th != null) {
// messaggio di errore del throwable
erreurs.add(th.getMessage());
// si passa alla causa del throwable
th = th.getCause();
}
// risultato
return erreurs;
}
public static void show(String title, List<String> messages){
// titolo
System.out.println(String.format("%s : ",title));
// messaggi
for(String message : messages){
System.out.println(String.format("- %s",message));
}
}
}
- righe 8-19: consente di ottenere l'elenco degli errori incapsulati in un oggetto di tipo [Throwable], che è la classe madre della classe [Exception];
- righe 21-28: visualizza sullo schermo un elenco di messaggi;
Questo codice potrebbe trovarsi nella classe principale, poiché in questo caso è l'unica a utilizzarlo. Ci troviamo qui in un contesto più ampio, in cui altre classi potrebbero aver bisogno di questo codice.
6.4.6. Lo scheletro della classe principale
package istia.st.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class IntroJdbc01 {
// costanti
final static String url = "jdbc:mysql://localhost:3306/dbIntroJdbc";
final static String user = "root";
final static String passwd = "";
final static String insert = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (?, ?, ?, ?, ?)";
final static String delete = "DELETE FROM PRODUITS";
final static String select = "SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS";
final static String update = "UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?";
final static String insert2 = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (100,'X',1,1,'x')";
public static void main(String[] args) {
// caricamento del driver JDBC da MySQL
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e1) {
doCatchException("Pilote JDBC introuvable", null, e1);
return;
}
// si svuota la tabella [PRODUITS]
delete();
// la si riempie
insert();
// si legge
select();
// aggiornamento
update();
// visualizzazione
select();
// inserimento di due elementi identici
// l'inserimento non va a buon fine e nessuno dei due elementi viene inserito a causa della transazione
insert2();
// si verifica
select();
// fine
System.out.println("Travail terminé");
}
// elenco dei prodotti
private static void select() {
...
}
// eliminazione prodotti
public static void delete() {
...
}
// aggiunta prodotti
public static void insert() {
...
}
// aggiunta di 2 prodotti
public static void insert2() {
...
}
// aggiornamento di alcuni prodotti
public static void update() {
..
}
private static void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// chiusura ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// chiusura [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// chiudi la connessione
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// vengono visualizzati i messaggi di errore
Static.show("Les erreurs suivantes se sont produites lors de la fermeture de la connexion",
Static.getErreursFromThrowable(e3));
}
}
}
private static void doCatchException(String title, Connection connexion, Throwable th) {
// vengono visualizzati i messaggi di errore
Static.show(title, Static.getErreursFromThrowable(th));
// annullamento della transazione
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// visualizza i messaggi di errore
Static.show(title, Static.getErreursFromThrowable(e2));
}
}
}
6.4.7. Cancellazione del contenuto della tabella dei prodotti
Il metodo [delete] elimina il contenuto della tabella:
// eliminazione prodotti
public static void delete() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// apertura connessione
connexion = DriverManager.getConnection(url, user, passwd);
// inizio transazione
connexion.setAutoCommit(false);
// svuotamento della tabella [PRODUITS]
ps = connexion.prepareStatement(delete);
ps.executeUpdate();
// conferma transazione
connexion.commit();
// ritorno alla modalità predefinita
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// gestione dell'eccezione
doCatchException("Les erreurs suivantes se sont produites à la suppression du contenu de la table", connexion, e1);
} finally {
// si gestisce il finally
doFinally(null, null, connexion);
}
}
Questo esempio utilizza le transazioni. Una transazione consente di raggruppare gli ordini SQL che devono essere tutti eseguiti con successo o tutti annullati. È necessario conoscere quattro operazioni:
- avvio di una transazione: [connexion.setAutoCommit(false)];
- conclusione di una transazione con esito positivo: [connexion.commit()]. In questo caso, tutte le operazioni effettuate su BD durante la transazione vengono convalidate;
- conclusione di una transazione con esito negativo: [connexion.rollback()]. In questo caso, tutte le operazioni effettuate su BD durante la transazione vengono annullate;
- ritorno alla modalità [auto-commit], che è la modalità predefinita di API JDBC: [connexion.setAutoCommit(true)]. In questa modalità, ogni ordine SQL è oggetto di una transazione. Pertanto, se si effettuano due inserimenti e il secondo fallisce:
- in modalità [AutoCommit=true], il primo inserimento rimane (è stato convalidato dal primo AutoCommit);
- in modalità [AutoCommit=false], il primo inserimento viene annullato;
Nei nostri esempi, ogni volta che si verifica un'eccezione, annulliamo la transazione nel metodo [doCatchException]:
private static void doCatchException(String title, Connection connexion, Throwable th) {
// visualizzazione dei messaggi di errore
Static.show(title, Static.getErreursFromThrowable(th));
// annullamento della transazione
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// vengono visualizzati i messaggi di errore
Static.show("Erreur lors de l'annulation de la transaction", Static.getErreursFromThrowable(e2));
}
}
6.4.8. Creazione del contenuto della tabella dei prodotti
Il metodo [insert] crea il contenuto della tabella:
// aggiunta prodotti
public static void insert() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// apertura della connessione
connexion = DriverManager.getConnection(url, user, passwd);
// inizio transazione
connexion.setAutoCommit(false);
// compilazione della tabella
ps = connexion.prepareStatement(insert);
for (int i = 0; i < 10; i++) {
// preparazione
int n = i + 1;
ps.setInt(1, n);
ps.setString(2, String.format("NOM%s", n));
ps.setInt(3, n / 5 + 1);
ps.setDouble(4, 100 * (1 + (double) i / 100));
ps.setString(5, String.format("DESC%s", n));
// esecuzione
ps.executeUpdate();
}
// conferma transazione
connexion.commit();
// ritorno alla modalità predefinita
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// gestione dell'eccezione
doCatchException("Les erreurs suivantes se sont produites à la création du contenu de la table", connexion, e1);
} finally {
// gestione del blocco finally
doFinally(null, null, connexion);
}
}
6.4.9. Visualizzazione del contenuto della tabella dei prodotti
Il metodo [select] visualizza il contenuto della tabella:
// elenco dei prodotti
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// apertura connessione
connexion = DriverManager.getConnection(url, user, passwd);
// inizio transazione
connexion.setAutoCommit(false);
// si legge la tabella [PRODUITS]
ps = connexion.prepareStatement(select);
rs = ps.executeQuery();
System.out.println("Liste des produits : ");
while (rs.next()) {
System.out.println(new Produit(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getDouble(4), rs.getString(5)));
}
// conferma transazione
connexion.commit();
// ritorno alla modalità predefinita
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// gestione dell'eccezione
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// si gestisce il finally
doFinally(null, null, connexion);
}
}
6.4.10. Aggiornamento del contenuto della tabella
Il metodo [update] aggiorna alcuni prodotti:
// aggiornamento di alcuni prodotti
public static void update() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// apertura della connessione
connexion = DriverManager.getConnection(url, user, passwd);
// inizio transazione
connexion.setAutoCommit(false);
// aggiornamento della tabella
ps = connexion.prepareStatement(update);
// categoria 1
ps.setInt(1, 1);
// esecuzione
ps.executeUpdate();
// conferma transazione
connexion.commit();
// ritorno alla modalità predefinita
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// si gestisce l'eccezione
doCatchException("Les erreurs suivantes se sont produites à la mise à jour du contenu de la table", connexion, e1);
} finally {
// elaborazione del finally
doFinally(null, null, connexion);
}
}
6.4.11. Ruolo della transazione
Il metodo [insert2] inserisce nella tabella due prodotti con la stessa chiave primaria, cosa che non è possibile. Poiché ci si trova all'interno di una transazione, il primo inserimento verrà annullato.
// aggiunta di 2 prodotti con le stesse chiavi primarie
public static void insert2() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// apertura della connessione
connexion = DriverManager.getConnection(url, user, passwd);
// inizio transazione
connexion.setAutoCommit(false);
// si aggiunge 1 riga
ps = connexion.prepareStatement(insert2);
// esecuzione
ps.executeUpdate();
// si aggiunge la stessa riga una seconda volta, quindi con la stessa chiave primaria
// l'inserimento deve fallire e nessuno dei due elementi deve essere inserito a causa della transazione
ps.executeUpdate();
// commit della transazione
connexion.commit();
// ritorno alla modalità predefinita
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// si gestisce l'eccezione
doCatchException("Les erreurs suivantes se sont produites lors de l'ajout", connexion, e1);
} finally {
// si gestisce il finally
doFinally(null, null, connexion);
}
}
6.4.12. Risultati
L'esecuzione del metodo [main] produce i seguenti risultati:
6.5. Utilizzo di una fonte dati di tipo [DataSource]
Riprendiamo l'applicazione precedente utilizzando una fonte dati di tipo [javax.sql.DataSource]:

Utilizzeremo una fonte dati implementata dalla classe [org.apache.tomcat.jdbc.pool.DataSource]. Questa classe utilizza un pool di connessioni, ovvero un insieme di connessioni aperte:
- quando il pool viene istanziato, viene aperto un certo numero di connessioni al database. Questo numero è configurabile;
- quando il codice Java apre una connessione, questa viene fornita dal pool;
- quando il codice Java chiude una connessione, questa viene restituita al pool;
In definitiva, le connessioni vengono aperte una sola volta, il che migliora le prestazioni di accesso al database. La fonte di dati verrà definita in una classe di configurazione Spring
6.5.1. Il progetto Eclipse
![]() |
Il progetto è un progetto Maven definito dal seguente file [pom.xml]:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.jdbc</groupId>
<artifactId>intro-jdbc-02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<!-- libreria jSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.1</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<!-- Tomcat JDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>8.0.20</version>
</dependency>
</dependencies>
</project>
- righe 21-25: dipendenza da Spring;
- righe 27-31: dipendenza dalla libreria che fornisce la fonte dei dati;
La classe di configurazione di Spring [AppConfig] è la seguente:
package istia.st.jdbc;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
// costanti
final static String URL = "jdbc:mysql://localhost:3306/dbIntroJdbc";
final static String USER = "root";
final static String PASSWD = "";
final static String INSERT = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (?, ?, ?, ?, ?)";
final static String DELETE = "DELETE FROM PRODUITS";
final static String SELECT = "SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS";
final static String UPDATE = "UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?";
final static String INSERT2 = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (100,'X',1,1,'x')";
final static String DRIVER_CLASSNAME = "com.mysql.jdbc.Driver";
@Bean
public DataSource dataSource() {
// fonte dati TomcatJdbc
DataSource dataSource = new DataSource();
// configurazione accesso JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// una connessione inizialmente aperta
dataSource.setInitialSize(1);
// risultato
return dataSource;
}
}
- righe 11-19: le costanti precedentemente definite in [IntroJdbc01] sono state spostate in [AppConfig];
- righe 31-34: il bean Spring che definisce la fonte dati;
- riga 24: creazione della fonte dati, ancora non configurata;
- righe 26-29: le informazioni che consentono alla fonte dati di connettersi al database;
- riga 31: crea un pool di 1 connessione. Qui non ne servono di più. Non ci sono mai più connessioni simultanee;
6.5.2. La classe principale
La classe principale [IntroJdbc02] è la seguente:
package istia.st.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IntroJdbc02 {
// fonte dati
private static DataSource dataSource;
public static void main(String[] args) {
// recupero del contesto Spring
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// recupero della fonte dati
dataSource = ctx.getBean(DataSource.class);
// si svuota la tabella [PRODUITS]
delete();
...
// fine
ctx.close();
System.out.println("Travail terminé");
}
// elenco dei prodotti
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// apertura connessione
connexion = dataSource.getConnection();
// inizio transazione
connexion.setAutoCommit(false);
...
} catch (SQLException e1) {
// gestione dell'eccezione
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// elaborazione del finally
doFinally(null, null, connexion);
}
}
...
}
- riga 14: la fonte dati. Si noti che è di tipo [javax.sql.DataSource], che è un'interfaccia;
- riga 18: istanziazione degli oggetti Spring;
- riga 20: ottenimento di un riferimento alla fonte dati. Si noti che in nessun momento viene citata la classe effettivamente utilizzata. Pertanto, in questo caso, nulla lascia supporre che si stia utilizzando un'implementazione [TomcatJdbc];
- riga 36: ottenimento di una connessione aperta;
- il resto del codice è identico a quello della classe [IntroJdbc01];
6.6. Conclusion
Ulteriori informazioni sulla gestione dei database sono disponibili nel documento [Exploiter une base relationnelle avec l'écosystème Spring].












