6. [Cours]: Einführung in API JDBC
Stichworte: relationale Datenbanken, API JDBC, SQLException.
6.1. Support
![]() | ![]() | ![]() |
Der Ordner „[support / chap-06]“ enthält die Eclipse-Projekte dieses Kapitels.
6.2. Architecture
![]() |
Die Schicht JDBC (Java DataBase Connectivity) ist eine universelle Schnittstelle für den Zugriff auf Datenbanken. Sie bietet gegenüber der Schicht [DAO] stets dieselbe Schnittstelle. Wenn man von SGBD wechselt, muss lediglich der Treiber JDBC geändert werden. Die Schicht [DAO] bleibt unverändert.
6.3. Die Schritte beim Betrieb einer Datenbank
![]() |
In der oben dargestellten Architektur umfasst der Betrieb einer Datenbank durch das Konsolenprogramm folgende Schritte:
- Laden des Datenbanktreibers JDBC;
- Herstellen einer Verbindung zur Datenbank;
- Ausführung eines Befehls SQL an die Datenbank und Verarbeitung der Ergebnisse des Befehls SQL;
- Verbindung schließen;
Schritt 1 wird nur einmal ausgeführt. Die Schritte 2–4 werden wiederholt ausgeführt. Es ist zu beachten, dass keine Verbindung offen bleibt. Sie wird geschlossen, sobald sie nicht mehr benötigt wird.
6.3.1. Schritt 1 – Laden des Treibers JDBC in den Arbeitsspeicher
Der Code
// Treiber wird geladen JDBC
try {
Class.forName(nom de la classe du pilote JDBC);
} catch (ClassNotFoundException e1) {
// Ausnahme behandeln
}
Der Vorgang in Zeile 3 dient dazu, den Treiber JDBC aus der Datenbank in den Speicher zu laden. Dieser Vorgang muss nur einmal durchgeführt werden. Eine Wiederholung führt jedoch nicht zu einem Fehler. Die Treiberklasse JDBC wird im Classpath des Projekts gesucht. Daher muss im Eclipse-Projekt die Datei „[jar]“, die die Treiberklasse „JDBC“ enthält, in den Classpath des Projekts aufgenommen worden sein.
6.3.2. Schritt 2 – Herstellen einer Verbindung
Sobald der Treiber JDBC eingerichtet ist, wird er aufgefordert, eine Verbindung mit dem BD 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 {
// Verbindung herstellen
connexion = DriverManager.getConnection(url, user, passwd);
...
} catch (SQLException e1) {
// Ausnahme wird behandelt
...
} finally {
// Verbindung schließen
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e2) {
// Ausnahme behandeln
...
}
}
}
- Zeilen 3–7: Die Implementierungsklassen der Schnittstelle JDBC befinden sich alle im Paket [java.sql]. Außerdem lösen sie im Fehlerfall alle eine Ausnahme vom Typ [SQLException] aus (Zeile 19, 27). Diese Ausnahme leitet sich von der Klasse [Exception] ab und ist eine sogenannte kontrollierte Ausnahme: Man muss entweder ein try/catch-Block einfügen, um sie zu behandeln, oder alternativ darauf verzichten und durch Ergänzung der Methodensignatur um [throws SQLException] angeben, dass die Methode die Ausnahme weiterleitet;
- Zeile 17: [DriverManager.getConnection] ist eine statische Methode, die drei Parameter erwartet:
- [url]: das URL aus der Datenbank. Es handelt sich um eine Zeichenkette, die vom verwendeten BD abhängt. Für MySQL hat sie die Form [jdbc:mysql://localhost:3306/nom_de_la_bd];
- [user]: der Eigentümer der Verbindung;
- [passwd]: sein Passwort;
- Zeilen 24–30: Die Verbindung muss in der Klausel [finally] geschlossen werden, damit sie unabhängig davon, ob eine Ausnahme auftritt oder nicht, geschlossen wird.
6.3.3. Schritt 3 – Ausgabe der Befehle SQL und [SELECT]
Sobald eine Verbindung hergestellt ist, können Befehle vom Typ SQL gesendet werden. Die Handhabung von Leseaufträgen vom Typ [SELECT] unterscheidet sich von der bei Aktualisierungsvorgängen vom Typ [UPDATE, INSERT, DELETE] verwendeten Vorgehensweise. Wir beginnen mit den Befehlen SQL und [SELECT]:
Der Code
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Verbindung hergestellt
connexion = DriverManager.getConnection(url, user, passwd);
// Transaktionsstart
connexion.setAutoCommit(false);
// im schreibgeschützten Modus
connexion.setReadOnly(true);
// die Tabelle [PRODUITS] wird gelesen
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)));
}
// Transaktion festschreiben
connexion.commit();
} catch (SQLException e1) {
// Ausnahme wird behandelt
doCatchException(connexion,e1);
} finally {
// „finally“-Block wird verarbeitet
doFinally(rs, ps, connexion);
}
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
....
}
- Zeilen 8, 10: Eröffnung einer Transaktion (Zeile 8) im schreibgeschützten Modus (Zeile 10). Eine Transaktion ist eine Abfolge von Befehlen SQL, die entweder alle erfolgreich sind oder alle fehlschlagen. In einer Transaktion mit N Befehlen SQL werden also die vorherigen I Befehle storniert, wenn der Befehl I+1 fehlschlägt. Für einen Lesevorgang ist keine Transaktion erforderlich. Das Anlegen einer Transaktion im schreibgeschützten Modus kann jedoch bestimmten SGBD-Befehlen bestimmte Optimierungen ermöglichen;
- Zeile 12: Verwendung eines [PreparedStatement]. Ein [PreparedStatement] hat normalerweise Parameter, die mit dem Zeichen ? gekennzeichnet sind. Hier gibt es keine. Ein [PreparedStatement] ist ein Befehl, der vom SGBD vorbereitet wird. Diese Vorbereitung ist mit Aufwand verbunden und wird nur einmal durchgeführt. Anschließend wird dieser vorbereitete Befehl vom SGBD mit verschiedenen effektiven Parametern ausgeführt, die die formalen Parameter „?“ ersetzen. Es ist zu beachten, dass es vorzuziehen ist, die gewünschten Spalten namentlich anzugeben, anstatt die Notation * zu verwenden, um alle Spalten abzurufen. Durch die Angabe der Spaltennamen können deren Werte anschließend anhand ihrer Position in der Abfrage SELECT abgerufen werden;
- Zeile 13: Ausführung von [PreparedStatement]. Es wird ein Objekt vom Typ [ResultSet] abgerufen;
Ein Objekt vom Typ [ResultSet] stellt eine Tabelle dar, d. h. eine Sammlung von Zeilen und Spalten. Zu einem bestimmten Zeitpunkt hat man nur Zugriff auf eine Zeile der Tabelle, die als aktuelle Zeile bezeichnet wird. Bei der anfänglichen Erstellung des [ResultSet] gibt es keine aktuelle Zeile. Um sie zu erhalten, muss eine Operation vom Typ [ResultSet.next()] durchgeführt werden. Die Signatur der Methode next lautet wie folgt:
Diese Methode versucht, zur nächsten Zeile von [ResultSet] zu wechseln 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 mehr zurückgeholt werden.
Die Tabelle [ResultSet] enthält Spalten mit den Namen labelCol1, labelCol2, …, die in der ausgeführten Abfrage [SELECT] angegeben sind. 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 der Tabelle [ResultSet] mit dem Namen [myId] übernommen;
- die Spalte [NOM] wird in eine Spalte der Tabelle [ResultSet] mit dem Namen [myNom] übertragen;
- ...
In der obigen Darstellung werden die Bezeichner [myCol] als Spaltenbezeichnungen bezeichnet. Fehlen diese Bezeichnungen, hängen die Spaltennamen der Tabelle [ResultSet] von der Tabelle SGBD ab. Wenn die Tabelle [SELECT] auf eine einzige Tabelle angewendet wird, sind die Spaltenbezeichnungen standardmäßig die Namen der Spalten, die von der Tabelle SELECT abgefragt werden. Das Problem tritt auf, wenn der [SELECT] auf mehrere Tabellen angewendet wird und diese identische Spaltennamen enthalten, wie im folgenden Beispiel:
SELECT PRODUITS.NOM, CATEGORIES.NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
Angenommen, die Tabelle [PRODUITS] verfügt über einen Fremdschlüssel zur Tabelle [CATEGORIES], der durch die Beziehung [Produits] symbolisiert wird.CATEGORIE_ID --> [CATEGORIES].ID, und dass die Tabellen [PRODUITS] und [CATEGORIES] beide ein Feld [NOM] besitzen. In diesem Fall hängen die in der Tabelle [ResultSet] angegebenen Namen der Spalten [PRODUITS.NOM] und [CATEGORIES.NOM] von der Tabelle SGBD ab. Für die Portabilität zwischen SGBD müssen daher hier Spaltenbezeichnungen verwendet werden, und man schreibt:
SELECT PRODUITS.NOM as p_NOM, CATEGORIES.NOM as c_NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
Um die verschiedenen Felder der aktuellen Zeile von [ResultSet] auszuwerten, stehen folgende Methoden zur Verfügung:
um die Spalte mit dem Namen „labelColi“ der aktuellen Zeile abzurufen und somit die Spalte des [SELECT] mit dieser Bezeichnung. Type bezeichnet den Typ des Feldes coli. Man kann die folgenden Methoden von [getType] verwenden: getInt, getLong, getString, getDouble, getFloat, getDate, ... Anstelle des Spaltennamens kann auch deren Position in der ausgeführten Abfrage [SELECT] verwendet werden:
wobei i der Index der gewünschten Spalte ist (i >= 1).
- Zeilen 15–17: Abruf der in der Abfrage BD gelesenen Werte;
- Zeile 19: Die Transaktion wird bestätigt (man sagt auch „committed“). Dadurch wird sie beendet und die Ressourcen freigegeben, die die Transaktion SGBD für sie belegt hatte;
- Zeile 25: Die Ressourcen werden in der Transaktion [finally] freigegeben. Diese ruft die folgende Methode [doFinally] auf:
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// Abschluss von ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// Schließen [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
if (connexion != null) {
try {
// Verbindung schließen
connexion.close();
} catch (SQLException e3) {
// Ausnahme behandeln
}
}
}
- Zeilen 3–9: Schließung von [ResultSet];
- Zeilen 11–17: Schließen von [PreparedStatement];
- Zeilen 18–27: Schließen der Verbindung;
Die Schließungen in den Zeilen 3–17 scheinen insofern redundant zu sein, als die Verbindung in den Zeilen 18–25 geschlossen wird. Tatsächlich sind sie in bestimmten Fällen jedoch nicht redundant, 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 Methode [doCatchException] behandelt:
private static void doCatchException(Connection connexion, Throwable th) {
// Transaktion abbrechen
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// Ausnahme behandeln
}
}
- Zeilen 4–6: Die Transaktion wird abgebrochen. Dadurch wird sie beendet, und SGBD kann die für sie gebuchten Ressourcen freigeben;
6.3.4. Schritt 3 – Ausgabe der Befehle SQL und [INSERT, UPDATE, DELETE]
Die Befehle SQL und [INSERT, UPDATE, DELETE] sind Aktualisierungsvorgänge: Sie ändern die Datenbank, geben jedoch keine Zeilen zurück. Die einzige zurückgegebene Information ist die Anzahl der von der Aktualisierung betroffenen Zeilen.
Der Code
Connection connexion = null;
PreparedStatement ps = null;
try {
// Verbindung herstellen
connexion = DriverManager.getConnection(url, user, passwd);
// Transaktion starten
connexion.setAutoCommit(false);
// im Lese-/Schreibmodus
connexion.setReadOnly(false);
// Tabelle wird aktualisiert
ps = connexion.prepareStatement("UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?");
// Kategorie 1
ps.setInt(1, 10);
// Ausführung
int nbLignes=ps.executeUpdate();
// Transaktion festschreiben
connexion.commit();
} catch (SQLException e1) {
// Ausnahme wird behandelt
doCatchException(connexion, e1);
} finally {
// „finally“-Block wird verarbeitet
doFinally(null, ps, connexion);
}
}
- Zeile 9: Die Verbindung wird zum Lesen und Schreiben verwendet;
- Zeile 11: ein [PreparedStatement] mit einem Parameter (durch „?“ symbolisiert). Es können mehrere Parameter vorhanden sein. Sie sind ab 1 nummeriert;
- Zeile 13: Dem einzigen Parameter wird sein Wert zugewiesen. Der erste Parameter von [setType] ist die Position des Parameters im [PreparedStatement] (1, 2, ...) und der zweite der ihm zugewiesene Wert. Man kann die Methoden [setInt, setLong, setFloat, setDouble, setString, setDate, ...] verwenden;
- Zeile 15: Es wird die Methode [executeUpdate] verwendet und nicht [executeQuery], die für Aufträge vom Typ SELECT reserviert ist. Die Methode gibt die Anzahl der von der Operation betroffenen Zeilen zurück. Kann 0 sein.
- Zeile 17: Die Transaktion wird bestätigt;
6.3.5. Schritt 4 – Schließen der Verbindung
Eine Verbindung muss in einer Mehrbenutzerumgebung so schnell wie möglich geschlossen werden, da ein SGBD nur eine begrenzte Anzahl offener Verbindungen zulässt. In den vorangegangenen Beispielen wurde sie in der Klausel [finally] der Operationen SQL geschlossen, damit sie unabhängig davon, ob eine Ausnahme aufgetreten ist oder nicht, geschlossen wird.
6.4. Ein Beispielprojekt
6.4.1. Support
![]() |
Der Ordner [support / chap5] enthält die Eclipse-Projekte dieses Kapitels [1, 2]. Der Ordner [database] enthält das Skript SQL, mit dem die Beispieldatenbank MySQL aus diesem Kapitel ([1, 3]) erstellt werden kann.
6.4.2. Die verwendete Datenbank
Die folgenden Beispiele verwenden die folgende Datenbank „MySQL“:
![]() |
- [ID]: Primärschlüssel im Modus AUTO_INCREMENT (wenn kein Primärschlüssel angegeben wird, generiert SGBD diesen);
- [NOM]: Name eines Produkts – eindeutig;
- [CATEGORIE]: Nummer seiner Kategorie;
- [PRIX]: Preis;
- [DESCRIPTION]: eine Beschreibung des Produkts;
Wir erstellen sie mit dem Tool [WampServer] wie folgt: [1-9]:
![]() |
![]() |
![]() |
![]() |
6.4.3. Das Eclipse-Projekt
![]() |
Das Projekt ist ein Maven-Projekt, das durch die folgende Datei [pom.xml] 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 Treiber JDBC für SGBD und MySQL5;
- Zeilen 13–17: eine Bibliothek, die jSON (JavaScript Object Notation) verarbeiten kann (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 [Produit] sieht wie folgt aus:
package istia.st.jdbc;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Produit {
// Felder
private int id;
private String nom;
private int categorie;
private double prix;
private String description;
// Konstruktoren
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 und Setter
...
// in String
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
}
- Zeile 34: Wir verwenden die Bibliothek jSON, um die Zeichenkette jSON des Produkts anzuzeigen. Dies führt zu einer Anzeige, 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 Methode [toString] besteht darin, dass die Methode [toString] auch dann noch gültig ist, wenn der Klasse Felder hinzugefügt oder entfernt werden. Sind die Felder zudem selbst Objekte (Listen, Arrays, Dictionaries, Benutzerobjekte), können die Bibliotheken jSON diese wiederum in eine Zeichenkette jSON umwandeln;
6.4.5. Die Klasse [Static]
Die Klasse [Static] fasst in Methoden den in der Hauptklasse häufig verwendeten Code zusammen:
package istia.st.jdbc;
import java.util.ArrayList;
import java.util.List;
public class Static {
public static List<String> getErreursFromThrowable(Throwable th) {
// Die Liste der Fehlermeldungen der Ausnahme wird abgerufen
List<String> erreurs = new ArrayList<String>();
while (th != null) {
// Fehlermeldung des Throwables
erreurs.add(th.getMessage());
// Weiter zur Ursache des Throwables
th = th.getCause();
}
// Ergebnis
return erreurs;
}
public static void show(String title, List<String> messages){
// Titel
System.out.println(String.format("%s : ",title));
// Meldungen
for(String message : messages){
System.out.println(String.format("- %s",message));
}
}
}
- Zeilen 8–19: Ermöglicht den Abruf der Fehlerliste, die in einem Objekt vom Typ [Throwable] gekapselt ist, welches die übergeordnete Klasse der Klasse [Exception] ist;
- Zeilen 21–28: Zeigt eine Liste von Meldungen auf dem Bildschirm an;
Dieser Code könnte in der Hauptklasse stehen, da er hier ausschließlich von dieser verwendet wird. Wir betrachten hier jedoch einen allgemeineren Fall, in dem auch andere Klassen diesen Code benötigen würden.
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 {
// Konstanten
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) {
// Laden des Treibers JDBC von MySQL
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e1) {
doCatchException("Pilote JDBC introuvable", null, e1);
return;
}
// Die Tabelle [PRODUITS] wird geleert
delete();
// die Tabelle wird gefüllt
insert();
// die Tabelle wird gelesen
select();
// Aktualisierung
update();
// Anzeige
select();
// Einfügen von zwei identischen Elementen
// Das Einfügen muss fehlschlagen, und aufgrund der Transaktion wird keines der beiden Elemente eingefügt
insert2();
// Es wird geprüft
select();
// fertig
System.out.println("Travail terminé");
}
// Produktliste
private static void select() {
...
}
// Produkte löschen
public static void delete() {
...
}
// Produkte hinzufügen
public static void insert() {
...
}
// 2 Produkte hinzufügen
public static void insert2() {
...
}
// Aktualisierung bestimmter Produkte
public static void update() {
..
}
private static void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// Schließung ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// Schließen [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// Verbindung schließen
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// Fehlermeldungen werden angezeigt
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) {
// Fehlermeldungen werden angezeigt
Static.show(title, Static.getErreursFromThrowable(th));
// Transaktion abbrechen
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// Fehlermeldungen werden angezeigt
Static.show(title, Static.getErreursFromThrowable(e2));
}
}
}
6.4.7. Löschen des Inhalts der Produkttabelle
Die Methode [delete] löscht den Inhalt der Tabelle:
// Produkte löschen
public static void delete() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// Verbindung herstellen
connexion = DriverManager.getConnection(url, user, passwd);
// Transaktion starten
connexion.setAutoCommit(false);
// Tabelle [PRODUITS] leeren
ps = connexion.prepareStatement(delete);
ps.executeUpdate();
// Transaktion festschreiben
connexion.commit();
// Rückkehr in den Standardmodus
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// Ausnahme wird behandelt
doCatchException("Les erreurs suivantes se sont produites à la suppression du contenu de la table", connexion, e1);
} finally {
// „finally“-Block wird verarbeitet
doFinally(null, null, connexion);
}
}
In diesem Beispiel werden Transaktionen verwendet. Eine Transaktion ermöglicht es, Aufträge SQL zu bündeln, die entweder alle erfolgreich ausgeführt oder alle abgebrochen werden müssen. Es sind vier Vorgänge zu beachten:
- Start einer Transaktion: [connexion.setAutoCommit(false)];
- Erfolgreicher Abschluss einer Transaktion: [connexion.commit()]. In diesem Fall werden alle Vorgänge, die während der Transaktion unter der Nummer BD durchgeführt wurden, freigegeben;
- Abbruch einer Transaktion: [connexion.rollback()]. In diesem Fall werden alle während der Transaktion an der BD durchgeführten Vorgänge storniert;
- Rückkehr in den Modus [auto-commit], der der Standardmodus von API ist: JDBC: [connexion.setAutoCommit(true)]. In diesem Modus ist jeder Befehl SQL Gegenstand einer Transaktion. Wenn also zwei Einfügungen vorgenommen werden, von denen die zweite fehlschlägt:
- im Modus [AutoCommit=true] bleibt der erste Einfügevorgang erhalten (er wurde durch den ersten Befehl AutoCommit bestätigt);
- im Modus [AutoCommit=false] wird der erste Einfügevorgang rückgängig gemacht;
In unseren Beispielen brechen wir die Transaktion jedes Mal, wenn eine Ausnahme auftritt, in der Methode [doCatchException] ab:
private static void doCatchException(String title, Connection connexion, Throwable th) {
// Fehlermeldungen werden angezeigt
Static.show(title, Static.getErreursFromThrowable(th));
// Transaktion abbrechen
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// Fehlermeldungen werden angezeigt
Static.show("Erreur lors de l'annulation de la transaction", Static.getErreursFromThrowable(e2));
}
}
6.4.8. Erstellung des Inhalts der Produkttabelle
Die Methode [insert] erstellt den Inhalt der Tabelle:
// Produkte hinzufügen
public static void insert() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// Verbindung herstellen
connexion = DriverManager.getConnection(url, user, passwd);
// Transaktion starten
connexion.setAutoCommit(false);
// Tabelle wird gefüllt
ps = connexion.prepareStatement(insert);
for (int i = 0; i < 10; i++) {
// Vorbereitung
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));
// Ausführung
ps.executeUpdate();
}
// Transaktion festschreiben
connexion.commit();
// Zurück zum Standardmodus
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// Ausnahme wird behandelt
doCatchException("Les erreurs suivantes se sont produites à la création du contenu de la table", connexion, e1);
} finally {
// „finally“-Block wird verarbeitet
doFinally(null, null, connexion);
}
}
6.4.9. Anzeige des Inhalts der Produkttabelle
Die Methode [select] zeigt den Inhalt der Tabelle an:
// Produktliste
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Verbindung herstellen
connexion = DriverManager.getConnection(url, user, passwd);
// Transaktion starten
connexion.setAutoCommit(false);
// die Tabelle wird gelesen [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)));
}
// Transaktion festschreiben
connexion.commit();
// Rückkehr in den Standardmodus
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// Ausnahme wird behandelt
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// „finally“-Block wird verarbeitet
doFinally(null, null, connexion);
}
}
6.4.10. Aktualisierung des Tabelleninhalts
Die Methode [update] aktualisiert bestimmte Produkte:
// Aktualisierung bestimmter Produkte
public static void update() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// Verbindung herstellen
connexion = DriverManager.getConnection(url, user, passwd);
// Transaktionsbeginn
connexion.setAutoCommit(false);
// die Tabelle wird aktualisiert
ps = connexion.prepareStatement(update);
// Kategorie 1
ps.setInt(1, 1);
// Ausführung
ps.executeUpdate();
// Transaktion festschreiben
connexion.commit();
// Rückkehr zum Standardmodus
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// Ausnahme wird behandelt
doCatchException("Les erreurs suivantes se sont produites à la mise à jour du contenu de la table", connexion, e1);
} finally {
// „finally“-Block wird verarbeitet
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 möglich ist. Da es sich um eine Transaktion handelt, wird der erste Einfügevorgang rückgängig gemacht.
// Hinzufüge n von 2 Produkten mit denselben Primärschlüsseln
public static void insert2() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// Verbindung wird geöffnet
connexion = DriverManager.getConnection(url, user, passwd);
// Transaktion wird gestartet
connexion.setAutoCommit(false);
// 1 Zeile wird hinzugefügt
ps = connexion.prepareStatement(insert2);
// Ausführung
ps.executeUpdate();
// Es wird dieselbe Zeile ein zweites Mal hinzugefügt, also mit demselben Primärschlüssel
// Das Einfügen muss fehlschlagen, und aufgrund der Transaktion darf keines der beiden Elemente eingefügt werden
ps.executeUpdate();
// Transaktion festschreiben
connexion.commit();
// Rückkehr zum Standardmodus
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// die Ausnahme wird behandelt
doCatchException("Les erreurs suivantes se sont produites lors de l'ajout", connexion, e1);
} finally {
// „finally“-Block wird verarbeitet
doFinally(null, null, connexion);
}
}
6.4.12. Ergebnisse
Die Ausführung der Methode [main] liefert folgende Ergebnisse:
6.5. Verwendung einer Datenquelle vom Typ [DataSource]
Wir werden die vorherige Anwendung unter Verwendung einer Datenquelle vom Typ [javax.sql.DataSource] fortsetzen:

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 diese an den Pool zurückgegeben;
Letztendlich werden die Verbindungen nur einmal geöffnet, was die Leistung beim Zugriff auf die Datenbank 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 Datei „[pom.xml]“ 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>
<!-- Bibliothek 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] lautet wie folgt:
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 {
// Konstanten
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() {
// Datenquelle TomcatJdbc
DataSource dataSource = new DataSource();
// Zugriffskonfiguration JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// eine ursprünglich geöffnete Verbindung
dataSource.setInitialSize(1);
// Ergebnis
return dataSource;
}
}
- Zeilen 11–19: Die zuvor in [IntroJdbc01] definierten Konstanten wurden in [AppConfig] verschoben;
- Zeilen 31–34: Der Spring-Bean, der die Datenquelle definiert;
- Zeile 24: Erstellung der noch nicht konfigurierten Datenquelle;
- Zeilen 26–29: Die Informationen, die es der Datenquelle ermöglichen, eine Verbindung zur Datenbank herzustellen;
- Zeile 31: Erstellt einen Pool mit einer Verbindung. Mehr wird hier nicht benötigt. Es gibt nie 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 {
// Datenquelle
private static DataSource dataSource;
public static void main(String[] args) {
// Abruf des Spring-Kontexts
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// Abruf der Datenquelle
dataSource = ctx.getBean(DataSource.class);
// Die Tabelle [PRODUITS] wird geleert
delete();
...
// Fertig
ctx.close();
System.out.println("Travail terminé");
}
// Produktliste
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Verbindung herstellen
connexion = dataSource.getConnection();
// Transaktion starten
connexion.setAutoCommit(false);
...
} catch (SQLException e1) {
// Ausnahme wird behandelt
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// „finally“-Block wird verarbeitet
doFinally(null, null, connexion);
}
}
...
}
- Zeile 14: die Datenquelle. Es ist zu beachten, dass es sich um den Typ [javax.sql.DataSource] handelt, bei dem es sich um eine Schnittstelle handelt;
- Zeile 18: Instanziierung der Spring-Objekte;
- Zeile 20: Abruf einer Referenz auf die Datenquelle. Es ist zu beachten, dass die tatsächlich verwendete Klasse zu keinem Zeitpunkt genannt wird. Hier lässt also nichts darauf schließen, dass eine Implementierung [TomcatJdbc] verwendet wird;
- Zeile 36: Abruf einer offenen Verbindung;
- Der restliche Code ist identisch mit dem der Klasse [IntroJdbc01];
6.6. Conclusion
Weitere Informationen zur Datenbankverwaltung finden Sie im Dokument [Exploiter une base relationnelle avec l'écosystème Spring].












