6. [Kurs]: Einführung in die JDBC-API
Stichworte: relationale Datenbanken, JDBC-API, SQLException.
6.1. Support
![]() | ![]() | ![]() |
Der Ordner [support / chap-06] enthält die Eclipse-Projekte für dieses Kapitel.
6.2. Architektur
![]() |
Die JDBC-Schicht (Java Database Connectivity) ist eine universelle Schnittstelle für den Datenbankzugriff. Sie stellt der [DAO]-Schicht stets dieselbe Schnittstelle zur Verfügung. Wenn Sie das DBMS wechseln, müssen Sie lediglich den JDBC-Treiber austauschen. Die [DAO]-Schicht bleibt unverändert.
6.3. Schritte zur Bedienung einer Datenbank
![]() |
In der oben dargestellten Architektur umfasst die Bedienung einer Datenbank über das Konsolenprogramm die folgenden Schritte:
- Laden des JDBC-Treibers der Datenbank;
- Öffnen einer Verbindung zur Datenbank;
- Ausführen einer SQL-Anweisung in der Datenbank und Verarbeiten der Ergebnisse der SQL-Anweisung;
- Schließen der Verbindung;
Schritt 1 wird nur einmal ausgeführt. Die Schritte 2–4 werden wiederholt ausgeführt. Beachten Sie, dass Verbindungen nicht offen bleiben; sie werden geschlossen, sobald sie nicht mehr benötigt werden.
6.3.1. Schritt 1 – Laden des JDBC-Treibers in den Arbeitsspeicher
// driver loading JDBC
try {
Class.forName(nom de la classe du pilote JDBC);
} catch (ClassNotFoundException e1) {
// handle the exception
}
Der Zweck des Vorgangs in Zeile 3 besteht darin, den JDBC-Treiber der Datenbank in den Arbeitsspeicher zu laden. Dieser Vorgang muss nur einmal durchgeführt werden. Eine Wiederholung führt jedoch nicht zu einem Fehler. Die JDBC-Treiberklasse wird im Klassenpfad des Projekts gesucht. Daher muss im Eclipse-Projekt die [jar]-Datei, die die JDBC-Treiberklasse enthält, in den Klassenpfad des Projekts aufgenommen worden sein.
6.3.2. Schritt 2 – Eine Verbindung herstellen
Sobald der JDBC-Treiber eingerichtet ist, weisen wir ihn an, eine Verbindung zur Datenbank herzustellen:
Der Code
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
...
}
}
}
- Zeilen 3–7: Die Klassen, die die JDBC-Schnittstelle implementieren, befinden sich alle im Paket [java.sql]. Außerdem lösen sie im Fehlerfall alle eine [SQLException] aus (Zeilen 19, 27). Diese Ausnahme leitet sich von der Klasse [Exception] ab und ist eine sogenannte geprüfte Ausnahme: Sie müssen einen try/catch-Block verwenden, um sie zu behandeln, oder alternativ entscheiden, sie nicht zu behandeln, und durch Hinzufügen von [throws SQLException] zur Methodensignatur angeben, dass die Methode die Weitergabe der Ausnahme zulässt;
- Zeile 17: [DriverManager.getConnection] ist eine statische Methode, die drei Parameter benötigt:
- [url]: die Datenbank-URL. Dies ist eine Zeichenkette, die von der verwendeten Datenbank abhängt. Bei MySQL hat sie das Format [jdbc:mysql://localhost:3306/db_name];
- [user]: der Eigentümer der Verbindung;
- [passwd]: das Passwort des Benutzers;
- Zeilen 24–30: Die Verbindung muss in der [finally]-Klausel geschlossen werden, damit sie unabhängig davon geschlossen wird, ob eine Ausnahme auftritt oder nicht.
6.3.3. Schritt 3 – Ausführen von SQL-[SELECT]-Anweisungen
Sobald eine Verbindung hergestellt ist, können SQL-Befehle ausgeführt werden. Die Art und Weise, wie Leseanweisungen [SELECT] behandelt werden, unterscheidet sich von der für Aktualisierungsoperationen [UPDATE, INSERT, DELETE]. Wir beginnen mit [SELECT]-SQL-Befehlen:
Der Code
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) {
....
}
- Zeilen 8, 10: Öffnen einer Transaktion (Zeile 8) im schreibgeschützten Modus (Zeile 10). Eine Transaktion ist eine Abfolge von SQL-Anweisungen, die entweder alle erfolgreich sind oder alle fehlschlagen. Wenn also in einer Transaktion mit N SQL-Anweisungen die (I+1)-te Anweisung fehlschlägt, werden die vorangegangenen I Anweisungen zurückgesetzt. Für einen Lesevorgang ist keine Transaktion erforderlich. Das Erstellen einer schreibgeschützten Transaktion kann es bestimmten DBMS jedoch ermöglichen, bestimmte Optimierungen durchzuführen;
- Zeile 12: Verwendung eines [PreparedStatement]. Ein [PreparedStatement] enthält normalerweise Parameter, die durch das Zeichen ? gekennzeichnet sind. Hier ist dies nicht der Fall. Ein [PreparedStatement] ist eine vom DBMS vorbereitete Anweisung. Diese Vorbereitung ist mit Aufwand verbunden und wird nur einmal durchgeführt. Diese vorbereitete Anweisung wird dann vom DBMS mit tatsächlichen Parametern ausgeführt, die die formalen Parameter ? ersetzen. Beachten Sie, dass es vorzuziehen ist, die gewünschten Spalten anzugeben, anstatt die *-Notation zu verwenden, um alle Spalten abzurufen. Durch die Angabe der Spaltennamen können deren Werte dann basierend auf ihrer Position in der SELECT-Anweisung abgerufen werden;
- Zeile 13: Ausführung des [PreparedStatement]. Ein [ResultSet]-Objekt wird abgerufen;
Ein [ResultSet]-Objekt repräsentiert eine Tabelle, d. h. eine Menge von Zeilen und Spalten. Zu jedem Zeitpunkt haben wir nur Zugriff auf eine Zeile der Tabelle, die als aktuelle Zeile bezeichnet wird. Wenn das [ResultSet] anfänglich erstellt wird, gibt es keine aktuelle Zeile. Wir müssen eine [ResultSet.next()]-Operation ausführen, um sie zu erhalten. Die Signatur der next-Methode lautet wie folgt:
Diese Methode versucht, zur nächsten Zeile des [ResultSet] zu springen, und gibt bei Erfolg „true“ zurück, andernfalls „false“. Bei Erfolg wird die nächste Zeile zur neuen aktuellen Zeile. Die vorherige Zeile geht verloren und kann nicht wiederhergestellt werden.
Die Tabelle [ResultSet] enthält Spalten mit den Namen labelCol1, labelCol2, …, wie in der ausgeführten [SELECT]-Abfrage angegeben. Mit der Abfrage:
SELECT ID as myId, NOM as myNom, CATEGORIE as myCategorie, PRIX as myPrix, DESCRIPTION as myDescription FROM PRODUITS
- wird die Spalte [ID] in eine Spalte im [ResultSet] mit dem Namen [myId] übernommen;
- die Spalte [NAME] wird in eine Spalte im [ResultSet] mit dem Namen [myName] übernommen;
- ...
Im obigen Beispiel werden die Bezeichner [myCol] als Spaltenbezeichnungen bezeichnet. Ohne diese Bezeichnungen hängen die Namen der [ResultSet]-Spalten vom DBMS ab. Wenn die [SELECT]-Anweisung auf eine einzelne Tabelle angewendet wird, entsprechen die Spaltenbezeichnungen standardmäßig den Namen der von der SELECT-Anweisung angeforderten Spalten. Das Problem tritt auf, wenn die [SELECT]-Anweisung auf mehrere Tabellen angewendet wird und diese Tabellen identische Spaltennamen enthalten, wie im folgenden Beispiel:
SELECT PRODUITS.NOM, CATEGORIES.NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
Angenommen, die Tabelle [PRODUCTS] verfügt über einen Fremdschlüssel zur Tabelle [CATEGORIES], dargestellt durch die Beziehung [PRODUCTS].CATEGORY_ID --> [CATEGORIES].ID, und sowohl die Tabelle [PRODUCTS] als auch die Tabelle [CATEGORIES] verfügen über ein Feld [NAME]. In diesem Fall hängen die Namen, die im [ResultSet] den Spalten [PRODUCTS.NAME] und [CATEGORIES.NAME] zugewiesen werden, vom DBMS ab. Um die Portabilität zwischen verschiedenen DBMS zu gewährleisten, müssen hier daher Spaltenbezeichnungen verwendet werden, und wir schreiben:
SELECT PRODUITS.NOM as p_NOM, CATEGORIES.NOM as c_NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
Um auf die verschiedenen Felder der aktuellen Zeile im [ResultSet] zuzugreifen, stehen folgende Methoden zur Verfügung:
um die Spalte mit dem Namen „labelColi“ aus der aktuellen Zeile abzurufen, d. h. die Spalte in der [SELECT]-Anweisung mit dieser Bezeichnung. Type bezieht sich auf den Datentyp des Feldes „labelColi“. Die folgenden [getType]-Methoden können verwendet werden: getInt, getLong, getString, getDouble, getFloat, getDate, ... Anstelle des Spaltennamens können Sie dessen Position in der ausgeführten [SELECT]-Abfrage verwenden:
wobei i der Index der gewünschten Spalte ist (i>=1).
- Zeilen 15–17: Abruf der aus der Datenbank gelesenen Werte;
- Zeile 19: Die Transaktion wird validiert (auch als „committed“ bezeichnet). Dadurch wird sie beendet und die Ressourcen, die das DBMS dafür zugewiesen hatte, werden freigegeben;
- Zeile 25: Die Ressourcen werden im [finally]-Block freigegeben. Dies ruft die folgende [doFinally]-Methode auf:
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
}
}
}
- Zeilen 3–9: Schließen des [ResultSet];
- Zeilen 11–17: Schließen des [PreparedStatement];
- Zeilen 18–27: Schließen der Verbindung;
Die Schließungen in den Zeilen 3–17 scheinen überflüssig, da die Verbindung in den Zeilen 18–25 geschlossen wird. Tatsächlich sind sie in manchen Fällen jedoch nicht überflüssig, und es wird empfohlen, sie beizubehalten [http://stackoverflow.com/questions/4507440/must-jdbc-resultsets-and-statements-be-closed-separately-although-the-connection].
- Zeile 22: Die Ausnahme wird von der folgenden [doCatchException]-Methode behandelt:
private static void doCatchException(Connection connexion, Throwable th) {
// cancel transaction
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// handle the exception
}
}
- Zeilen 4–6: Die Transaktion wird zurückgesetzt. Dadurch wird sie beendet, und das DBMS kann die ihr zugewiesenen Ressourcen freigeben;
6.3.4. Schritt 3 – Ausführen von SQL-Anweisungen [INSERT, UPDATE, DELETE]
SQL-Anweisungen [INSERT, UPDATE, DELETE] sind Aktualisierungsoperationen: Sie ändern die Datenbank, geben jedoch keine Zeilen zurück. Die einzige zurückgegebene Information ist die Anzahl der von der Aktualisierungsoperation betroffenen Zeilen.
Der Code
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);
}
}
- Zeile 9: Die Verbindung wird zum Lesen und Schreiben verwendet;
- Zeile 11: ein [PreparedStatement] mit 1 Parameter (dargestellt durch ?). Es können mehrere Parameter vorhanden sein. Sie sind beginnend mit 1 nummeriert;
- Zeile 13: Sein Wert wird dem einzelnen Parameter zugewiesen. Der erste Parameter von [setType] ist die Position des Parameters im [PreparedStatement] (1, 2, ...) und der zweite ist der ihm zugewiesene Wert. Sie können die Methoden [setInt, setLong, setFloat, setDouble, setString, setDate, ...] verwenden;
- Zeile 15: Es wird die Methode [executeUpdate] verwendet, nicht [executeQuery], die für SELECT-Anweisungen reserviert ist. Die Methode gibt die Anzahl der von der Operation betroffenen Zeilen zurück. Kann 0 sein.
- Zeile 17: Die Transaktion wird festgeschrieben;
6.3.5. Schritt 4 – Schließen der Verbindung
Eine Verbindung muss in einer Mehrbenutzerumgebung so schnell wie möglich geschlossen werden, da ein DBMS nur eine begrenzte Anzahl offener Verbindungen akzeptiert. In den vorherigen Beispielen wurde sie in der [finally]-Klausel der SQL-Operationen geschlossen, damit sie unabhängig davon geschlossen wird, ob eine Ausnahme aufgetreten ist oder nicht.
6.4. Ein Beispielprojekt
6.4.1. Support
![]() |
Der Ordner [support / chap5] enthält die Eclipse-Projekte für dieses Kapitel [1, 2]. Der Ordner [database] enthält das SQL-Skript zum Anlegen der Beispiel-MySQL-Datenbank für dieses Kapitel [1, 3].
6.4.2. Die verwendete Datenbank
Die folgenden Beispiele verwenden die folgende MySQL-Datenbank:
![]() |
- [ID]: Primärschlüssel im AUTO_INCREMENT-Modus (wenn kein Primärschlüssel angegeben ist, generiert das DBMS ihn);
- [NAME]: Produktname – eindeutig;
- [CATEGORY]: Kategorienummer;
- [PRICE]: Preis;
- [DESCRIPTION]: eine Beschreibung des Produkts;
Wir erstellen diese mit dem Tool [WampServer] wie folgt [1-9]:
![]() |
![]() |
![]() |
![]() |
6.4.3. Das Eclipse-Projekt
![]() |
Das Projekt ist ein Maven-Projekt, das durch die folgende [pom.xml]-Datei definiert ist:
<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>
- Zeilen 8–12: der JDBC-Treiber für das DBMS MySQL5;
- Zeilen 13–17: eine Bibliothek zur Verarbeitung von JSON (JavaScript Object Notation) (siehe Abschnitt 22.6). Wir werden sie verwenden, um die Produkte aus der Datenbank im JSON-Format anzuzeigen;
6.4.4. Die Produktklasse
Die Klasse [Product] sieht wie folgt aus:
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;
}
}
}
- Zeile 34: Wir verwenden die JSON-Bibliothek, um die JSON-Zeichenkette des Produkts anzuzeigen. Dies erzeugt eine Ausgabe, die in etwa wie folgt aussieht:
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"}
Der Vorteil der oben genannten [toString]-Methode besteht darin, dass sie auch dann gültig bleibt, wenn Felder zur Klasse hinzugefügt oder aus ihr entfernt werden. Wenn es sich bei den Feldern selbst um Objekte handelt (Listen, Arrays, Wörterbücher, Benutzerobjekte), können JSON-Bibliotheken diese wiederum in JSON-Zeichenfolgen umwandeln;
6.4.5. Die Klasse [Static]
Die Klasse [Static] fasst Methoden zusammen, die häufig in der Hauptklasse verwendeten Code enthalten:
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));
}
}
}
- Zeilen 8–19: Gibt eine Liste von Fehlern zurück, die in einem Objekt vom Typ [Throwable] gekapselt sind, der die Oberklasse der Klasse [Exception] darstellt;
- Zeilen 21–28: Zeigt eine Liste von Meldungen auf dem Bildschirm an;
Dieser Code könnte in der Hauptklasse stehen, da er hier nur dort verwendet wird. Wir betrachten jedoch ein umfassenderes Szenario, in dem andere Klassen diesen Code möglicherweise benötigen.
6.4.6. Das Grundgerüst der Hauptklasse
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. Löschen des Inhalts der Produkttabelle
Die Methode [delete] löscht den Inhalt der Tabelle:
// 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);
}
}
In diesem Beispiel werden Transaktionen verwendet. Eine Transaktion ermöglicht es Ihnen, SQL-Anweisungen zu gruppieren, die entweder alle erfolgreich sein müssen oder alle zurückgesetzt werden. Es gibt vier Operationen, die Sie beachten sollten:
- Starten einer Transaktion: [connection.setAutoCommit(false)];
- Erfolgreiches Beenden einer Transaktion: [connection.commit()]. In diesem Fall werden alle während der Transaktion an der Datenbank durchgeführten Operationen festgeschrieben;
- Beenden einer Transaktion mit Fehler: [connection.rollback()]. In diesem Fall werden alle während der Transaktion an der Datenbank durchgeführten Operationen zurückgesetzt;
- Zurück zum [Auto-Commit]-Modus, dem Standardmodus für die JDBC-API: [connection.setAutoCommit(true)]. In diesem Modus ist jede SQL-Anweisung Teil einer Transaktion. Wenn Sie also zwei Einfügungen durchführen und die zweite fehlschlägt:
- im Modus [AutoCommit=true] bleibt der erste Einfügevorgang erhalten (er wurde durch den ersten AutoCommit bestätigt);
- im Modus [AutoCommit=false] wird der erste Einfügevorgang rückgängig gemacht;
In unseren Beispielen rollen wir die Transaktion in der Methode [doCatchException] zurück, sobald eine Ausnahme auftritt:
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. Erstellen des Inhalts der Produkttabelle
Die Methode [insert] erstellt den Tabelleninhalt:
// 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. Anzeigen des Inhalts der Tabelle „products“
Die Methode [select] zeigt den Tabelleninhalt an:
// 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. Aktualisierung des Tabelleninhalts
Die Methode [update] aktualisiert bestimmte Produkte:
// 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. Rolle der Transaktion
Die Methode [insert2] fügt zwei Produkte mit demselben Primärschlüssel in die Tabelle ein, was nicht zulässig ist. Da wir uns in einer Transaktion befinden, wird der erste Einfügevorgang rückgängig gemacht.
// 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. Ergebnisse
Die Ergebnisse der Ausführung der Methode [main] lauten wie folgt:
6.5. Verwendung einer [DataSource]-Datenquelle
Wir werden die vorherige Anwendung unter Verwendung einer [javax.sql.DataSource]-Datenquelle erneut betrachten:

Wir verwenden eine Datenquelle, die durch die Klasse [org.apache.tomcat.jdbc.pool.DataSource] implementiert wird. Diese Klasse nutzt einen Verbindungspool, d. h. eine Reihe offener Verbindungen:
- Wenn der Pool instanziiert wird, wird eine bestimmte Anzahl von Verbindungen zur Datenbank geöffnet. Diese Anzahl ist konfigurierbar;
- Wenn der Java-Code eine Verbindung öffnet, wird diese vom Pool bereitgestellt;
- Wenn der Java-Code eine Verbindung schließt, wird sie an den Pool zurückgegeben;
Letztendlich werden Verbindungen nur einmal geöffnet, was die Leistung beim Datenbankzugriff verbessert. Die Datenquelle wird in einer Spring-Konfigurationsklasse definiert
6.5.1. Das Eclipse-Projekt
![]() |
Das Projekt ist ein Maven-Projekt, das durch die folgende [pom.xml]-Datei definiert ist:
<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>
- Zeilen 21–25: Abhängigkeit von Spring;
- Zeilen 27–31: Abhängigkeit von der Bibliothek, die die Datenquelle bereitstellt;
Die Spring-Konfigurationsklasse [AppConfig] sieht wie folgt aus:
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;
}
}
- Zeilen 11–19: Die zuvor in [IntroJdbc01] definierten Konstanten wurden nach [AppConfig] verschoben;
- Zeilen 31–34: die Spring-Bean, die die Datenquelle definiert;
- Zeile 24: Erstellung der Datenquelle, die noch nicht konfiguriert ist;
- Zeilen 26–29: die Informationen, die es der Datenquelle ermöglichen, eine Verbindung zur Datenbank herzustellen;
- Zeile 31: Erstellt einen Pool mit 1 Verbindung. Mehr brauchen wir hier nicht. Es gibt niemals mehrere gleichzeitige Verbindungen;
6.5.2. Die Hauptklasse
Die Hauptklasse [IntroJdbc02] sieht wie folgt aus:
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);
}
}
...
}
- Zeile 14: die Datenquelle. Beachten Sie, dass es sich um den Typ [javax.sql.DataSource] handelt, der eine Schnittstelle ist;
- Zeile 18: Instanziierung von Spring-Objekten;
- Zeile 20: Abrufen einer Referenz auf die Datenquelle. Beachten Sie, dass die tatsächlich verwendete Klasse nie erwähnt wird. Daher gibt es hier keinen Hinweis darauf, dass eine [TomcatJdbc]-Implementierung verwendet wird;
- Zeile 36: Abrufen einer offenen Verbindung;
- Der Rest des Codes ist identisch mit dem der Klasse [IntroJdbc01];
6.6. Fazit
Weitere Informationen zur Datenbankverwaltung finden Sie im Dokument [Arbeiten mit einer relationalen Datenbank unter Verwendung des Spring-Ökosystems].












