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.
private String hostprivate String databaseprivate String userprivate String passwordprivate Connection dbConnectiondbConnection verwaltet die Verbindung zur Datenbank.
private Statement sqlStatementsqlStatement führt übergebene SQL-Anweisungen aus.
private ResultSet resultSetresultSet 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)public boolean openConnection()dbConnection zu.
true, wenn der Vorgang erfolgreich war, andernfalls false.
public void closeConnection()public ResultSet executeQuery(String pSql)ResultSet zurück.
public boolean executeUpdate(String pSql)true, wenn der Vorgang erfolgreich war, andernfalls false.
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
DBZugriffUm zu verstehen, was der Konstruktor im Einzelnen tut, untersuchen wir seinen Quellcode:
//...
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());
}
}
//...
}
DBZugriff (Ausschnitt)
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ösungLösung
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();
}
}
}
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.
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());
}
}
}
//...
}
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 Artikelnummer, Verkaufspreis, Lagerbestand und Artikelbezeichnung auf der Konsole aus.
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
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.
|
TestDBZugriff2 (Quellcode)public double getDouble(int columnIndex)double-Wert zurück. Die erste Spalte hat den columnIndex 1.
public double getDouble(String columnLabel)double-Wert zurück.
public double getInt(int columnIndex)int-Wert zurück. Die erste Spalte hat den columnIndex 1.
public double getInt(String columnLabel)int-Wert zurück.
public double getString(int columnIndex)String-Wert zurück. Die erste Spalte hat den columnIndex 1.
public double getString(String columnLabel)String-Wert zurück.
public boolean next()true, wenn der Cursor auf den nächsten Datensatz gesetzt wurde und false, wenn es im Abfrageergebnis keine weiteren Datensätze mehr gibt
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.
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;
}
//...
}
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 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 Artikelnummer, Verkaufspreis, Lagerbestand und Artikelbezeichnung auf der Konsole ausgeben.
Lösung
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();
}
}
}
}
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 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
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();
}
}
}
}
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.
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;
}
//...
}
DBZugriff (Ausschnitt)