19.1 Anwendungsunabhängige Datenbankzugriffsklasse

Für den Zugriff auf eine MySQL-Datenbank erstellen wir eine Klasse namens DBZugriff. Die Klasse ist in der Datenzugriffsschicht angesiedelt und unabhängig von der Fachkonzeptschicht. Sie kann daher unverändert in jeder Java-Anwendung eingesetzt werden, die auf eine MySQL-Datenbank zugreifen soll.

img/Abb_19_1_UML_DBZugriff.svg
datenzugriff.DBZugriff
private String host
IP-Adresse des Rechners, auf dem die Datenbank läuft.
private String database
Name der Datenbank.
private String user
Benutzername, unter dem der Datenbankzugriff erfolgt.
private String password
Benutzerpasswort
private Connection dbConnection
Das Objekt dbConnection verwaltet die Verbindung zur Datenbank.
private Statement sqlStatement
Das Objekt sqlStatement führt übergebene SQL-Anweisungen aus.
private ResultSet resultSet
Das Objekt resultSet ermöglicht den Zugriff auf das Abfrageergebnis. Das Attribut wird ausschließlich dazu genutzt, der Methode closeConnection() zu ermöglichen auch die von resultSet belegten Ressourcen explizit freizugeben.
public DBZugriff(pHost: String, pDatabase: String,
                 pUser: String, pPassword: String)

Weist die übergebenen Verbindungsdaten den entsprechenden Attributen zu und registriert den JDBC-Treiber beim JDBC-Treibermanager der Java-Laufzeitumgebung.
public boolean openConnection()
Baut eine auf und weist diese dem Attribut dbConnection zu.
Rückgabe: true, wenn der Vorgang erfolgreich war, andernfalls false.
public void closeConnection()
Schließt die Datenbankverbindung und gibt die belegten Ressourcen frei.
public ResultSet executeQuery(String pSql)
Führt die übergebene SQL-Anweisung (Abfrage) aus und gibt das Abfrageergebnis in Form eines Objekts vom Typ1 ResultSet zurück.
public boolean executeUpdate(String pSql)
Führt die übergebene SQL-Anweisung (Datenmanipulation) aus.
Rückgabe: true, wenn der Vorgang erfolgreich war, andernfalls false.
Abb. 19-1: Die Klasse DBZugriff (UML)

19.1.1 Verbindung zur Datenbank aufbauen und beenden

In einem ersten Test werden wir eine Verbindung zu einer MySQL-Datenbank aufbauen. Wenn dies erfolgreich war, beenden wir die Verbindung anschließend wieder. Auf der Konsole erhalten wir dabei entsprechende Mitteilungen, welche Schritte erfolgreich abgeschlossen worden sind beziehungsweise welcher Schritt fehlgeschlagen ist.

Hierfür müssen wir ein Objekt der Klasse DBZugriff erzeugen. Das Objekt soll uns eine Verbindung zu einem MySQL-DBMS ermöglichen, das in unserem Test auf dem gleichen Rechner läuft wie unsere Java-Testanwendung (localhost). Innerhalb des MySQL-DBMS soll mit Hilfe des Benutzers root ein Zugriff auf die Datenbank artikelverwaltung erfolgen. Ein Passwort ist für diesen Benutzer nicht hinterlegt.2

JAVA
new DBZugriff("localhost", "artikelverwaltung", "root", "")
Abb. 19-2: Erzeugung eines Objekts der Klasse DBZugriff

Um zu verstehen, was der Konstruktor im Einzelnen tut, untersuchen wir seinen Quellcode:

JAVA
package datenzugriff;

//...

public class DBZugriff {
    
    private String host;
    private String database;
    private String user;
    private String password;
    //...

    public DBZugriff(String pHost, String pDatabase, String pUser, String pPassword) {
        final String JDBC_DRIVER = "com.mysql.jdbc.Driver"; //JDBC-Treiberklasse, die die Kommunikation mit MySQL-DBMS ermöglicht.
        
        host = pHost;
        database = pDatabase;
        user = pUser;
        password = pPassword;
        
        try{
            Class.forName(JDBC_DRIVER); //Beim Laden der JDBC-Treiberklasse, registriert diese sich beim JDBC-Treibermanager.
            System.out.println("JDBC-Treiber beim JDBC-Treibermanager registriert...");
        }
        catch(ClassNotFoundException e){
            System.out.println("JDBC-Treiber nicht gefunden: " + e.toString());
        }
    }

    //...
}
Abb. 19-3: Quellcode der Klasse DBZugriff (Ausschnitt)
Aufgabe

Aufgabe 19-1: Test – Verbindung zur Datenbank aufbauen und beenden

Erstellen Sie in Eclipse ein neues Java-Projekt namens 19-1_Artikelverwaltung und binden Sie den von Oracle für MySQL bereitgestellten JDBC-Datenbanktreiber ein.

JDBC (Java Database Connectivity) bezeichnet die von JAVA in den Paketen java.sql und javax.sql bereitgestellten Schnittstellen für den Zugriff auf relationale Datenbankmanagementsysteme (RDBMS). Mit ihrer Hilfe können Java-Anwendungen unabhängig vom verwendeten RDBMS entwickelt werden. In einem Java-Projekt muss lediglich ein vom Hersteller des verwendeten RDBMS bereitgestellter JDBC-Datenbanktreiber eingebunden werden.

Erzeugen Sie ein geeignetes Objekt der Klasse DBZugriff (vgl. Abbildung 19-2). Bauen Sie mit seiner Hilfe eine Verbindung zur Datenbank auf. Wenn dieser Vorgang erfolgreich war, soll die Verbindung anschließend wieder beendet werden. Die hierfür erforderlichen Methoden finden Sie in Abbildung 19-1.

Stellen Sie sicher, dass das MySQL-DBMS auf Ihrem Rechner gestartet ist, bevor Sie den Test ausführen.

Lösung
Lösung
JAVA
package test;

import datenzugriff.DBZugriff;

public class TestDBZugriff1 {

    public static void main(String[] args) {
        DBZugriff dbZugriff = new DBZugriff("localhost", "artikelverwaltung", "root", "");

        //Test: Öffnen und Schließen einer DB-Verbindung
        if(dbZugriff.openConnection()) {
            dbZugriff.closeConnection();
        }
    }
}
Abb. 19-4: Klasse TestDBZugriff1 (Quellcode)

In Abbildung 19-5 können wir die Funktionsweise der beiden Methoden openConnection() und closeConnection() anhand eines Quellcode-Ausschnitts der Klasse DBZugriff nachvollziehen.

JAVA
package datenzugriff;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class DBZugriff {

    private String host;
    private String database;
    private String user;
    private String password;
    private Connection dbConnection//Verwaltet die Verbindung zur Datenbank.
    private Statement sqlStatement;  //Führt übergebene SQL-Anweisungen aus.
    private ResultSet resultSet;     //Ermöglicht den Zugriff auf das Abfrageergebnis.

    //...

    //Datenbank-Verbindung öffnen
    public boolean openConnection() {
        String url = "jdbc:mysql://" + host + "/" + database; //JDBC-URL ("Wo finde ich die Datenbank?")
        boolean connected = false;
        
        try{
            dbConnection = DriverManager.getConnection(url, user, password); //Beim DriverManager ein Connection-Objekt (DB-Verbindung) anfordern
            connected = true;
            System.out.println("Verbindung hergestellt...");
        }
        catch(SQLException e){
            System.out.println("Datenbankverbindung fehlgeschlagen: " + e.toString());
        }
        
        return connected;
    }
        
    //Datenbank-Verbindung schließen und belegte Ressourcen freigeben
    public void closeConnection() {
    
        if(resultSet!=null) {
            try{
                resultSet.close(); //Gibt die belegten Ressourcen frei.
            }
            catch(SQLException e){
                System.out.println("Die durch das resultSet belegten Ressourcen"
                                 + "konnten nicht freigegeben werden: \n" + e.toString());
            }
        }
        
        if(sqlStatement!=null) {
            try{
                sqlStatement.close(); //Gibt die belegten Ressourcen frei.
            }
            catch(SQLException e){
                System.out.println("Die durch das sqlStatement belegten Ressourcen"
                                 + "konnten nicht freigegeben werden: \n" + e.toString());
            }
        }
        
        if(dbConnection!=null) {
            try{
                dbConnection.close(); //Gibt die belegten Ressourcen frei.
                System.out.println("Verbindung getrennt...");
            }
            catch(SQLException e){
                System.out.println("Die durch das dbConnection belegten Ressourcen"
                                 + "konnten nicht freigegeben werden: \n" + e.toString());
            }
        }
    }

    //...
}
Abb. 19-5: Quellcode der Klasse DBZugriff (Ausschnitt)

19.1.2 Abfrageergebnis mit einem Datensatz

In einem zweiten Test werden wir dem DBMS eine Abfrage übergeben, die zu einem Abfrageergebnis mit einem Datensatz führt. Anschließend lesen wir aus dem Datensatz die Werte einiger Spalten aus und geben sie auf der Konsole aus.

Beispiel

Wir fragen aus der Beispieldatenbank die Daten des Artikels mit der Artikelnummer 11076 ab. Anschließend geben wir davon Artikel­nummer, Verkaufs­preis, Lager­bestand und Artikel­bezeichnung auf der Konsole aus.

JAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package test;

import datenzugriff.DBZugriff;
import java.sql.ResultSet;
import java.sql.SQLException;

public class TestDBZugriff2 {

    //Test: Daten des 1. Datensatzes des Abfrageergebnisses (resultSet) werden auf der Konsole ausgegeben
    public static void main(String[] args) {
        DBZugriff dbZugriff = new DBZugriff("localhost", "artikelverwaltung", "root", "");
        ResultSet resultSet;

        if(dbZugriff.openConnection()) {
            resultSet = dbZugriff.executeQuery("SELECT * FROM artikel WHERE ArtNr='11076';");

            try {
                if(resultSet!=null && resultSet.next()) {
                    System.out.println( resultSet.getString("ArtNr") + "\t"
                            + resultSet.getDouble("VkPreis") + "\t"
                            + resultSet.getInt("LBestand") + "\t"
                            + resultSet.getString("ArtBez") );
                }
            }
            catch (SQLException e) {
                System.out.println("Fehler beim DB-Zugriff!" + e.toString());
            }
            finally { //Falls eine Exception geworfen wird, die keine SQLException ist, werden Anweisungen, die auf das try-catch-Konstrukt folgen, nicht mehr ausgeführt. Anweisungen im finally-Block werden jedoch auch in diesem Fall ausgeführt.
                dbZugriff.closeConnection();
            }
        }
    }
}

Erläuterungen

Zeile 12 Ein Objekt vom Typ1 ResultSet ermöglicht den Zugriff auf ein Abfrageergebnis.
Zeile 14 Ist die Methode openConnection() erfolgreich, gibt sie den Wert true zurück und die Abfrage kann durchgeführt werden. Scheitert der Verbindungsaufbau, gibt sie den Wert false zurück und unser Testprogramm endet. Da in letzterem Fall dem Attribut dbConnection des Objekts dbZugriff kein Connection-Objekt zugewiesen wird, erübrigt sich auch ein Aufruf der Methode closeConnection().
Zeile 15 Die executeQuery-Methode führt die übergebene SQL-Anweisung aus. Ist der Vorgang erfolgreich, gibt sie ein Objekt vom Typ ResultSet zurück, andernfalls null. Das Testprogramm wird in jedem Fall fortgeführt.
Zeile 17 Alle Methoden des Objekts resultSet, die in diesem Beispiel aufgerufen werden, lösen eine SQLException aus, falls bei ihrer Ausführung etwas schief geht. Die entsprechenden Aufrufe erfolgen daher innerhalb eines try-Blocks. Eine mögliche SQLException wird durch den catch-Block (Zeile 25) aufgefangen.
Zeile 18 Zunächst wird geprüft, ob resultSet auf ein ResultSet-Objekt verweist. Ist dies der Fall wird für das Objekt die Methode next() ausgeführt.
Die Methode next() versucht den Datensatzzeiger (Cursor) auf den nächsten Datensatz des Abfrageergebnisses zu setzen. Unmittelbar nach einer Abfrage steht der Cursor zunächst vor dem ersten Datensatz. Die Methode gibt den Wert true zurück, wenn der Cursor auf den nächsten Datensatz gesetzt wurde, und den Wert false, wenn es keine weiteren Datensätze mehr gibt.
Scheiterte die Ausführung der Abfrage in Zeile 15, hat resultSet den Wert null. In diesem Fall führt der Versuch die Methode next() auszuführen zu einer Ausnahme, die in Zeile 25 aufgefangen wird. Im finally-Block (Zeile 28) wird sichergestellt, dass alle belegten Ressourcen freigegeben werden. Anschließend endet das Testprogramm.
Zeile 19 Die getString-Methode liest im Abfrageergebnis aus dem Datensatz, auf den der Datensatzzeiger aktuell zeigt, den Wert der Spalte ArtNr aus und gibt ihn als String zurück.
Zeile 20 Die getDouble-Methode liest im Abfrageergebnis aus dem Datensatz, auf den der Datensatzzeiger aktuell zeigt, den Wert der Spalte VkPreis aus und gibt ihn als double-Wert zurück.
Zeile 21 Die getInt-Methode liest im Abfrageergebnis aus dem Datensatz, auf den der Datensatzzeiger aktuell zeigt, den Wert der Spalte LBestand aus und gibt ihn als int-Wert zurück.
Zeile 28 Indem wir die closeConnection()-Methode innerhalb des finally-Blocks aufrufen, stellen wir sicher, dass sie in jedem Fall ausgeführt wird. Alternativ wäre es denkbar die Anweisung nach dem try-catch-Konstrukt aufzurufen. In diesem Fall muss jedoch sichergestellt werden, dass diese Anweisung ausgeführt wird. Das bedeutet alle denkbaren Exceptions müssen abgefangen werden, auch solche die keine Behandlung erzwingen. Das hat zur Folge, dass auch keine Ausnahmen an den Aufrufer weitergereicht werden dürfen. Um diese Einschränkungen zu vermeiden, empfiehlt sich die Verwendung von finally.
Abb. 19-6: Klasse TestDBZugriff2 (Quellcode)
img/Abb_19_7_UML_ResultSet.svg
java.sql.ResultSet
public double getDouble(int columnIndex)
Liefert den in der aktuellen Zeile, in der angegebenen Spalte (Spaltenindex) stehenden Wert als double-Wert zurück. Die erste Spalte hat den columnIndex 1.
public double getDouble(String columnLabel)
Liefert den in der aktuellen Zeile, in der angegebenen Spalte (Spaltenüberschrift) stehenden Wert als double-Wert zurück.
public double getInt(int columnIndex)
Liefert den in der aktuellen Zeile, in der angegebenen Spalte (Spaltenindex) stehenden Wert als int-Wert zurück. Die erste Spalte hat den columnIndex 1.
public double getInt(String columnLabel)
Liefert den in der aktuellen Zeile, in der angegebenen Spalte (Spaltenüberschrift) stehenden Wert als int-Wert zurück.
public double getString(int columnIndex)
Liefert den in der aktuellen Zeile, in der angegebenen Spalte (Spaltenindex) stehenden Wert als String-Wert zurück. Die erste Spalte hat den columnIndex 1.
public double getString(String columnLabel)
Liefert den in der aktuellen Zeile, in der angegebenen Spalte (Spaltenüberschrift) stehenden Wert als String-Wert zurück.
public boolean next()
Versucht den Datensatzzeiger (Cursor) im Abfrageergebnis auf den nächsten Datensatz zu setzen. Unmittelbar nach einer Abfrage steht der Cursor zunächst vor dem ersten Datensatz.
Rückgabe true, wenn der Cursor auf den nächsten Datensatz gesetzt wurde und false, wenn es im Abfrageergebnis keine weiteren Datensätze mehr gibt
Abb. 19-7: Die Klasse ResultSet (UML)

In Abbildung 19-8 können wir die Funktionsweise der Methode executeQuery(pSql: String): ResultSet anhand eines Quellcode-Ausschnitts der Klasse DBZugriff nachvollziehen.

JAVA
package datenzugriff;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class DBZugriff {

    //...
    private Connection dbConnection //Verwaltet die Verbindung zur Datenbank.
    private Statement sqlStatement  //Führt übergebene SQL-Anweisungen aus.
    private ResultSet resultSet     //Ermöglicht den Zugriff auf das Abfrageergebnis.

    //...

    //SQL-Anweisung (Abfrage) ausführen und Rückgabe des entsprechenden Abfrageergebnisses
    public ResultSet executeQuery(String pSql) {
        ResultSet resultSet = null;
        
        try {
            sqlStatement = dbConnection.createStatement();   //Statement-Objekt bereitstellen
            resultSet = sqlStatement.executeQuery(pSql);     //Statement-Objekt führt die Abfrage aus und gibt ein entsprechendes ResultSet zurück.
            this.resultSet = resultSet;                      //Um das ResultSet-Objekt über closeConnection() schließen zu können, wird dem Attribut resultSet ein entsprechender Verweis auf das Objekt zugewiesen.
            System.out.println("Datenbank-Abfrage durchgeführt...");
        }
        catch(SQLException e){
            System.out.println("Datenbank-Abfrage fehlgeschlagen: " + e.toString());
            System.out.println("SQL-Anweisung: " + pSql);
        }
        
        return resultSet;
    }

    //...
}
Abb. 19-8: Quellcode der Klasse DBZugriff (Ausschnitt)

19.1.3 Abfrageergebnis mit mehreren Datensätzen

In einem dritten Test werden wir dem DBMS eine Abfrage übergeben, die zu einem Abfrageergebnis mit mehreren Datensätzen führt. Anschließend lesen wir aus allen Datensätzen des Abfrageergebnisses die Werte einiger Spalten aus und geben sie auf der Konsole aus.

Aufgabe

Aufgabe 19-2: Test – Abfrageergebnis auf der Konsole ausgeben

Erstellen Sie nach dem Vorbild der Klasse TestDBZugriff2 die Klasse TestDBZugriff3. Diese soll aus der Beispieldatenbank die Daten aller Artikel abfragen und jeweils Artikel­nummer, Verkaufs­preis, Lager­bestand und Artikel­bezeichnung auf der Konsole ausgeben.

Lösung
Lösung
JAVA
package test;

import datenzugriff.DBZugriff;
import java.sql.ResultSet;
import java.sql.SQLException;

public class TestDBZugriff3 {

    //Test: Daten aller Datensätze des Abfrageergebnisses (resultSet) werden auf der Konsole ausgegeben
    public static void main(String[] args) {
        DBZugriff dbZugriff = new DBZugriff("localhost", "artikelverwaltung", "root", "");
        ResultSet resultSet;

        if(dbZugriff.openConnection()) {
            resultSet = dbZugriff.executeQuery("SELECT * FROM artikel;");

            try {
                if(resultSet!=null) {
                    while(resultSet.next()) {
                        System.out.println( resultSet.getString("ArtNr") + "\t"
                                + resultSet.getDouble("VkPreis") + "\t"
                                + resultSet.getInt("LBestand") + "\t"
                                + resultSet.getString("ArtBez") );
                    }
                }
            }
            catch (SQLException e) {
                System.out.println("Fehler beim DB-Zugriff!" + e.toString());
            }
            finally {
                dbZugriff.closeConnection();
            }
        }
    }
}
Abb. 19-9: Klasse TestDBZugriff3 (Quellcode)

19.1.4 Einfügen eines neuen Datensatzes

In einem vierten Test werden wir dem DBMS eine SQL-Anweisung übergeben, die die Daten eines neuen Artikels in die entsprechende Tabelle schreibt. Zu Kontrollzwecken lesen wird anschließend den neuen Datensatz aus der Datenbank und geben die Werte einiger Spalten auf der Konsole aus.

Aufgabe

Aufgabe 19-3: Test – Einfügen eines neuen Datensatzes

Erstellen Sie die Klasse TestDBZugriff4. Diese soll einen neuen Artikel (Artikelnummer 12346, Bezeichnung Testartikel, Preis 9.99, Lagerbestand 100) in der Beispieldatenbank anlegen. Anschließend soll sie diese Daten aus der Datenbank abfragen und auf der Konsole ausgeben.

Lösung
Lösung
JAVA
package test;

import java.sql.ResultSet;
import java.sql.SQLException;
import datenzugriff.DBZugriff;

public class TestDBZugriff4 {

    //Test: Die Daten eines neu erstellten Artikels werrden in der Datenbank gespeichert und anschließend abgefragt
    public static void main(String[] args) {
        DBZugriff dbZugriff = new DBZugriff("localhost", "artikelverwaltung", "root", "");
        String sql;
        ResultSet resultSet;

        if(dbZugriff.openConnection()) {
            //Datensatz einfügen
            sql = "INSERT INTO artikel(ArtNr, ArtBez, VkPreis, LBestand)"
                    + "VALUES('12346', 'Test', 9.99, 100);";
            if(dbZugriff.executeUpdate(sql)){
                System.out.println("Artikel gespeichert!");
            }
            else {
                System.out.println("Der Artikel konnte nicht gespeichert werden!");
            }
            
            
            //Datensatz abfragen
            resultSet = dbZugriff.executeQuery("SELECT * FROM artikel WHERE ArtNr='12346';");
            try {
                if(resultSet!=null && resultSet.next()) {
                    System.out.println( resultSet.getString("ArtNr") + "\t"
                            + resultSet.getDouble("VkPreis") + "\t"
                            + resultSet.getInt("LBestand") + "\t"
                            + resultSet.getString("ArtBez") );
                }
            }
            catch (SQLException e) {
                System.out.println("Fehler beim DB-Zugriff!" + e.toString());
            }
            finally {
                dbZugriff.closeConnection();
            }
        }
    }
}
Abb. 19-10: Klasse TestDBZugriff4 (Quellcode)

Das Ändern beziehungsweise Löschen eines Artikels funktioniert analog. Es muss lediglich die INSERT-Anweisung durch eine UPDATE beziehungsweise DELETE-Anweisung ersetzt werden.

JAVA
package datenzugriff;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class DBZugriff {

    //...
    private Connection dbConnection //Verwaltet die Verbindung zur Datenbank.
    private Statement sqlStatement  //Führt übergebene SQL-Anweisungen aus.

    //...

    //SQL-Anweisung (Datenmanipulation) ausführen (z. B. INSERT INTO, UPDATE der DELETE)
    public boolean executeUpdate(String pSql) {
        boolean executed = false;
        
        try{
            sqlStatement = dbConnection.createStatement();   //Statement-Objekt bereitstellen
            sqlStatement.executeUpdate(pSql);                //Statement-Objekt führt die Anfügeabfrage aus.
            executed = true;
            System.out.println("Datenbank-Aktualisierung durchgeführt...");
        }
        catch(SQLException e){
            System.out.println("Datenbank-Aktualisierung fehlgeschlagen: " + e.toString());
            System.out.println("SQL-Anweisung: " + pSql);
        }
        
        return executed;
    }

    //...
}
Abb. 19-11: Quellcode der Klasse DBZugriff (Ausschnitt)