6. [Corso]: Introduzione all'API JDBC
Parole chiave: database relazionali, API JDBC, SQLException.
6.1. Supporto
![]() | ![]() | ![]() |
La cartella [support / chap-06] contiene i progetti Eclipse relativi a questo capitolo.
6.2. Architettura
![]() |
Il livello JDBC (Java Database Connectivity) è un'interfaccia universale di accesso al database. Presenta sempre la stessa interfaccia al livello [DAO]. Se si cambia il DBMS, è sufficiente cambiare il driver JDBC. Il livello [DAO] rimane invariato.
6.3. Passaggi per l'utilizzo di un database
![]() |
Nell'architettura sopra descritta, la gestione di un database tramite il programma console prevede i seguenti passaggi:
- caricare il driver JDBC del database;
- aprire una connessione al database;
- eseguire un'istruzione SQL sul database ed elaborare i risultati dell'istruzione SQL;
- chiudere la connessione;
Il passaggio 1 viene eseguito una sola volta. I passaggi da 2 a 4 vengono eseguiti ripetutamente. Si noti che le connessioni non vengono lasciate aperte; vengono chiuse non appena non sono più necessarie.
6.3.1. Passaggio 1 - Caricamento del driver JDBC in memoria
// driver loading JDBC
try {
Class.forName(nom de la classe du pilote JDBC);
} catch (ClassNotFoundException e1) {
// handle the exception
}
Lo scopo dell'operazione alla riga 3 è caricare in memoria il driver JDBC del database. Questa operazione deve essere eseguita una sola volta. Tuttavia, ripeterla non causa alcun errore. La classe del driver JDBC viene cercata nel classpath del progetto. Pertanto, nel progetto Eclipse, il file [jar] contenente la classe del driver JDBC deve essere stato incluso nel classpath del progetto.
6.3.2. Passaggio 2 - Apertura di una connessione
Una volta che il driver JDBC è a posto, gli diamo l'istruzione di aprire una connessione al database:
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 {
// opening connection
connexion = DriverManager.getConnection(url, user, passwd);
...
} catch (SQLException e1) {
// we handle the exception
...
} finally {
// close connection
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e2) {
// handle the exception
...
}
}
}
- Righe 3–7: Le classi che implementano l'interfaccia JDBC si trovano tutte nel pacchetto [java.sql]. Inoltre, in caso di errore, generano tutte una [SQLException] (righe 19, 27). Questa eccezione deriva dalla classe [Exception] ed è una cosiddetta eccezione controllata: è necessario utilizzare un blocco try/catch per gestirla o, in alternativa, scegliere di non gestirla e indicare che il metodo consente la propagazione dell'eccezione aggiungendo [throws SQLException] alla firma del metodo;
- Riga 17: [DriverManager.getConnection] è un metodo statico che accetta tre parametri:
- [url]: l'URL del database. Si tratta di una stringa che dipende dal database utilizzato. Per MySQL, ha il formato [jdbc:mysql://localhost:3306/db_name];
- [user]: il proprietario della connessione;
- [passwd]: la password dell'utente;
- righe 24–30: la connessione deve essere chiusa nella clausola [finally] in modo che venga chiusa indipendentemente dal fatto che si verifichi o meno un'eccezione.
6.3.3. Passaggio 3 - Esecuzione delle istruzioni SQL [SELECT]
Una volta stabilita la connessione, è possibile eseguire i comandi SQL. Il modo in cui vengono gestiti i comandi di lettura [SELECT] differisce da quello utilizzato per le operazioni di aggiornamento [UPDATE, INSERT, DELETE]. Inizieremo con i comandi SQL [SELECT]:
Il codice
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// opening connection
connexion = DriverManager.getConnection(url, user, passwd);
// start of transaction
connexion.setAutoCommit(false);
// in read-only mode
connexion.setReadOnly(true);
// table [PRODUITS] is read
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)));
}
// commit transaction
connexion.commit();
} catch (SQLException e1) {
// we handle the exception
doCatchException(connexion,e1);
} finally {
// we treat the 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 istruzioni SQL che o vanno tutte a buon fine o falliscono tutte. Pertanto, in una transazione contenente N istruzioni SQL, se la (I+1)-esima istruzione fallisce, le I istruzioni precedenti verranno annullate. Per un'operazione di lettura, una transazione non è necessaria. Tuttavia, la creazione di una transazione in sola lettura può consentire ad alcuni DBMS di eseguire determinate ottimizzazioni;
- Riga 12: uso di un [PreparedStatement]. Un [PreparedStatement] ha normalmente dei parametri indicati dal carattere ?. Qui, invece, non è così. Un [PreparedStatement] è un'istruzione preparata dal DBMS. Questa preparazione ha un costo e viene eseguita una sola volta. L'istruzione preparata viene quindi eseguita dal DBMS con i parametri effettivi che sostituiscono i parametri formali ?. Si noti che è preferibile specificare le colonne desiderate piuttosto che utilizzare la notazione * per recuperare tutte le colonne. Specificando i nomi delle colonne, i loro valori possono quindi essere recuperati in base alla loro posizione nell'istruzione SELECT;
- Riga 13: esecuzione del [PreparedStatement]. Viene recuperato un oggetto [ResultSet];
Un oggetto [ResultSet] rappresenta una tabella, ovvero un insieme di righe e colonne. In un dato momento, abbiamo accesso a una sola riga della tabella, chiamata riga corrente. Quando il [ResultSet] viene creato inizialmente, non c'è alcuna riga corrente. Dobbiamo 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 può essere recuperata.
La tabella [ResultSet] contiene colonne denominate labelCol1, labelCol2, ... come specificato 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 [NAME] andrà in una colonna del [ResultSet] denominata [myName];
- ...
Nell'esempio sopra riportato, gli identificatori [myCol] sono denominati etichette di colonna. Senza queste etichette, i nomi delle colonne del [ResultSet] dipendono dal DBMS. Quando il [SELECT] opera su una singola tabella, le etichette di colonna saranno per impostazione predefinita i nomi delle colonne richieste dal SELECT. Il problema sorge quando il [SELECT] opera su più tabelle e tali tabelle contengono 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 [PRODUCTS] abbia una chiave esterna verso la tabella [CATEGORIES], rappresentata dalla relazione [PRODUCTS].CATEGORY_ID --> [CATEGORIES].ID, e che entrambe le tabelle [PRODUCTS] e [CATEGORIES] abbiano un campo [NAME]. In questo caso, i nomi assegnati nel [ResultSet] alle colonne [PRODUCTS.NAME] e [CATEGORIES.NAME] dipendono dal DBMS. Per garantire la portabilità tra i DBMS, è quindi necessario utilizzare qui le etichette delle colonne, e scriveremo:
SELECT PRODUITS.NOM as p_NOM, CATEGORIES.NOM as c_NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
Per accedere ai vari campi della riga corrente nel [ResultSet], sono disponibili i seguenti metodi:
per recuperare la colonna denominata "labelColi" dalla riga corrente, ovvero la colonna nell'istruzione [SELECT] con quell'etichetta. Type si riferisce al tipo di dati del campo "labelColi". È possibile utilizzare i seguenti metodi [getType]: getInt, getLong, getString, getDouble, getFloat, getDate, ... Invece di 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 dal database;
- riga 19: la transazione viene convalidata (operazione nota anche come commit). Ciò la conclude e libera le risorse che il DBMS aveva allocato per essa;
- riga 25: le risorse vengono rilasciate nel blocco [finally]. Questo richiama il seguente metodo [doFinally]:
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// closure ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// closure [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
if (connexion != null) {
try {
// close connection
connexion.close();
} catch (SQLException e3) {
// handle the exception
}
}
}
- righe 3-9: chiudere il [ResultSet];
- righe 11–17: chiudere il [PreparedStatement];
- righe 18–27: chiudere la connessione;
Le chiusure nelle righe 3–17 sembrano ridondanti poiché la connessione viene chiusa nelle righe 18–25. In realtà, in alcuni casi non sono ridondanti 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) {
// cancel transaction
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// handle the exception
}
}
- righe 4–6: la transazione viene annullata. Questo la termina e il DBMS può liberare le risorse ad essa assegnate;
6.3.4. Fase 3 - Emissione di istruzioni SQL [INSERT, UPDATE, DELETE]
Le istruzioni SQL [INSERT, UPDATE, DELETE] sono operazioni di aggiornamento: modificano il database ma non restituiscono alcuna riga. L'unica informazione restituita è il numero di righe interessate dall'operazione di aggiornamento.
Il codice
Connection connexion = null;
PreparedStatement ps = null;
try {
// ouverture connexion
connexion = DriverManager.getConnection(url, user, passwd);
// début transaction
connexion.setAutoCommit(false);
// en mode lecture / écriture
connexion.setReadOnly(false);
// on met à jour la table
ps = connexion.prepareStatement("UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?");
// catégorie 1
ps.setInt(1, 10);
// exécution
int nbLignes=ps.executeUpdate();
// commit transaction
connexion.commit();
} catch (SQLException e1) {
// on traite l'exception
doCatchException(connexion, e1);
} finally {
// on traite le finally
doFinally(null, ps, connexion);
}
}
- riga 9: la connessione viene utilizzata per la lettura e la scrittura;
- riga 11: un [PreparedStatement] con 1 parametro (rappresentato da ?). I parametri possono essere più di uno. Sono numerati a partire da 1;
- riga 13: il suo valore viene assegnato al singolo parametro. Il primo parametro di [setType] è la posizione del parametro nel [PreparedStatement] (1, 2, ...) e il secondo è il valore ad esso assegnato. È possibile utilizzare i metodi [setInt, setLong, setFloat, setDouble, setString, setDate, ...];
- riga 15: viene utilizzato il metodo [executeUpdate], non [executeQuery], che è riservato alle istruzioni SELECT. Il metodo restituisce il numero di righe interessate dall'operazione. Può essere 0.
- riga 17: la transazione viene confermata;
6.3.5. Fase 4 - Chiusura della connessione
In un ambiente multiutente, una connessione deve essere chiusa il più rapidamente possibile perché un DBMS accetta un numero limitato di connessioni aperte. Negli esempi precedenti, è stata 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 relativi a questo capitolo [1, 2]. La cartella [database] contiene lo script SQL per la creazione del database MySQL di esempio relativo a questo capitolo [1, 3].
6.4.2. Il database utilizzato
Gli esempi seguenti utilizzano il seguente database MySQL:
![]() |
- [ID]: chiave primaria in modalità AUTO_INCREMENT (se non viene specificata alcuna chiave primaria, il DBMS la genera);
- [NAME]: nome del prodotto — univoco;
- [CATEGORY]: numero di categoria;
- [PRICE]: il suo prezzo;
- [DESCRIPTION]: una descrizione del prodotto;
Lo creeremo utilizzando lo strumento [WampServer] come segue [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 per il DBMS MySQL5;
- righe 13–17: una libreria in grado di gestire JSON (JavaScript Object Notation) (vedi Sezione 22.6). La useremo per visualizzare i prodotti dal database in formato JSON;
6.4.4. La classe Product
La classe [Product] è la seguente:
package istia.st.jdbc;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Produit {
// fields
private int id;
private String nom;
private int categorie;
private double prix;
private String description;
// manufacturers
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;
}
// getters and setters
...
// to String
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
}
- Riga 34: Utilizziamo la libreria JSON per visualizzare la stringa JSON del prodotto. Questo produce un output simile al 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 riportato è che, se alla classe vengono aggiunti o rimossi dei campi, il suo metodo [toString] rimane valido. Inoltre, se i campi stessi sono oggetti (liste, array, dizionari, oggetti utente), le librerie JSON possono a loro volta convertirli in stringhe JSON;
6.4.5. La classe [Static]
La classe [Static] raggruppa i metodi contenenti 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) {
// retrieve the list of exception error msgs
List<String> erreurs = new ArrayList<String>();
while (th != null) {
// throwable error message
erreurs.add(th.getMessage());
// we move on to the cause of throwable
th = th.getCause();
}
// result
return erreurs;
}
public static void show(String title, List<String> messages){
// title
System.out.println(String.format("%s : ",title));
// messages
for(String message : messages){
System.out.println(String.format("- %s",message));
}
}
}
- righe 8–19: restituisce un elenco di errori racchiusi in un oggetto di tipo [Throwable], che è la superclasse della classe [Exception];
- righe 21-28: visualizza un elenco di messaggi sullo schermo;
Questo codice potrebbe trovarsi nella classe principale poiché è l'unica a utilizzarlo in questo contesto. Tuttavia, stiamo considerando uno scenario 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 {
// constants
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) {
// loading the JDBC driver from MySQL
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e1) {
doCatchException("Pilote JDBC introuvable", null, e1);
return;
}
// empty table [PRODUITS]
delete();
// fill it
insert();
// we read it
select();
// update
update();
// display
select();
// insertion of two identical elements
// insertion must fail and neither element is inserted because of the transaction
insert2();
// we check
select();
// finish
System.out.println("Travail terminé");
}
// product list
private static void select() {
...
}
// product deletion
public static void delete() {
...
}
// add products
public static void insert() {
...
}
// add 2 products
public static void insert2() {
...
}
// product updates
public static void update() {
..
}
private static void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// closure ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// closure [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// close connection
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// display error msg
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) {
// display error msg
Static.show(title, Static.getErreursFromThrowable(th));
// cancel transaction
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// display error msg
Static.show(title, Static.getErreursFromThrowable(e2));
}
}
}
6.4.7. Eliminazione del contenuto della tabella dei prodotti
Il metodo [delete] elimina il contenuto della tabella:
// product deletion
public static void delete() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// opening connection
connexion = DriverManager.getConnection(url, user, passwd);
// start of transaction
connexion.setAutoCommit(false);
// empty table [PRODUITS]
ps = connexion.prepareStatement(delete);
ps.executeUpdate();
// commit transaction
connexion.commit();
// return to default mode
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// we handle the exception
doCatchException("Les erreurs suivantes se sont produites à la suppression du contenu de la table", connexion, e1);
} finally {
// we treat the finally
doFinally(null, null, connexion);
}
}
Questo esempio utilizza le transazioni. Una transazione consente di raggruppare istruzioni SQL che devono essere tutte eseguite con successo o tutte annullate. Ci sono quattro operazioni da tenere presenti:
- avvio di una transazione: [connection.setAutoCommit(false)];
- conclusione di una transazione con esito positivo: [connection.commit()]. In questo caso, tutte le operazioni eseguite sul database durante la transazione vengono confermate;
- conclusione di una transazione con errore: [connection.rollback()]. In questo caso, tutte le operazioni eseguite sul database durante la transazione vengono annullate;
- Ritorno alla modalità [auto-commit], che è la modalità predefinita per l'API JDBC: [connection.setAutoCommit(true)]. In questa modalità, ogni istruzione SQL fa parte di una transazione. Pertanto, se si eseguono due inserimenti e il secondo fallisce:
- in modalità [AutoCommit=true], il primo inserimento rimane (è stato confermato 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) {
// display error msg
Static.show(title, Static.getErreursFromThrowable(th));
// cancel transaction
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// display error msg
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:
// add products
public static void insert() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// opening connection
connexion = DriverManager.getConnection(url, user, passwd);
// start of transaction
connexion.setAutoCommit(false);
// fill the table
ps = connexion.prepareStatement(insert);
for (int i = 0; i < 10; i++) {
// preparation
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));
// execution
ps.executeUpdate();
}
// commit transaction
connexion.commit();
// return to default mode
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// we handle the exception
doCatchException("Les erreurs suivantes se sont produites à la création du contenu de la table", connexion, e1);
} finally {
// we treat the finally
doFinally(null, null, connexion);
}
}
6.4.9. Visualizzazione del contenuto della tabella dei prodotti
Il metodo [select] visualizza il contenuto della tabella:
// product list
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// opening connection
connexion = DriverManager.getConnection(url, user, passwd);
// start of transaction
connexion.setAutoCommit(false);
// table [PRODUITS] is read
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)));
}
// commit transaction
connexion.commit();
// return to default mode
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// we handle the exception
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// we treat the finally
doFinally(null, null, connexion);
}
}
6.4.10. Aggiornamento del contenuto della tabella
Il metodo [update] aggiorna determinati prodotti:
// product updates
public static void update() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// opening connection
connexion = DriverManager.getConnection(url, user, passwd);
// start of transaction
connexion.setAutoCommit(false);
// table is updated
ps = connexion.prepareStatement(update);
// category 1
ps.setInt(1, 1);
// execution
ps.executeUpdate();
// commit transaction
connexion.commit();
// return to default mode
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// we handle the exception
doCatchException("Les erreurs suivantes se sont produites à la mise à jour du contenu de la table", connexion, e1);
} finally {
// we treat the 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 è consentita. Poiché ci troviamo in una transazione, il primo inserimento verrà annullato.
// add 2 pro ducts with the same primary keys
public static void insert2() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// opening connection
connexion = DriverManager.getConnection(url, user, passwd);
// start of transaction
connexion.setAutoCommit(false);
// add 1 line
ps = connexion.prepareStatement(insert2);
// execution
ps.executeUpdate();
// we add the same line a 2nd time, with the same primary key
// the insertion must fail and neither of the two elements must be inserted because of the transaction
ps.executeUpdate();
// commit transaction
connexion.commit();
// return to default mode
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// we handle the exception
doCatchException("Les erreurs suivantes se sont produites lors de l'ajout", connexion, e1);
} finally {
// we treat the finally
doFinally(null, null, connexion);
}
}
6.4.12. Risultati
I risultati dell'esecuzione del metodo [main] sono i seguenti:
6.5. Utilizzo di una fonte dati [DataSource]
Rivedremo l'applicazione precedente utilizzando una fonte dati [javax.sql.DataSource]:

Useremo 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. L'origine dati sarà 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>
<!-- library 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 l'origine dati;
La classe di configurazione 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 {
// constantes
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() {
// source de données TomcatJdbc
DataSource dataSource = new DataSource();
// configuration accès JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// une connexion ouverte initialement
dataSource.setInitialSize(1);
// résultat
return dataSource;
}
}
- righe 11–19: le costanti precedentemente definite in [IntroJdbc01] sono state spostate in [AppConfig];
- righe 31–34: il bean Spring che definisce l'origine dati;
- riga 24: creazione della fonte dati, che non è ancora 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 {
// data source
private static DataSource dataSource;
public static void main(String[] args) {
// spring context retrieval
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// data source recovery
dataSource = ctx.getBean(DataSource.class);
// empty table [PRODUITS]
delete();
...
// finish
ctx.close();
System.out.println("Travail terminé");
}
// product list
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// opening connection
connexion = dataSource.getConnection();
// start of transaction
connexion.setAutoCommit(false);
...
} catch (SQLException e1) {
// we handle the exception
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// we treat the finally
doFinally(null, null, connexion);
}
}
...
}
- riga 14: l'origine 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 la classe effettivamente utilizzata non viene mai menzionata. Pertanto, in questo caso, nulla suggerisce che venga utilizzata un'implementazione [TomcatJdbc];
- riga 36: ottenimento di una connessione aperta;
- il resto del codice è identico a quello della classe [IntroJdbc01];
6.6. Conclusione
Ulteriori informazioni sulla gestione dei database sono disponibili nel documento [Utilizzo di un database relazionale con l'ecosistema Spring].












