Programmieren in Java: Aufbau


Kapitel 5: Datenbankanbindung


Inhaltsverzeichnis

Dieses Buch ist unter einer Creative Commons-Lizenz lizensiert.


5.1 Allgemeines

SQL und Zugriffsmechanismen

Das Abfragen von Daten in Datenbanken erfolgt seit langer Zeit mit Hilfe einer eigens definierten Sprache namens SQL. SQL ist standardisiert und hat sich auf sämtlichen Datenbanken durchgesetzt. Wenn Daten abgefragt oder auch neue Daten abgelegt werden sollen, werden SQL-Befehle formuliert und an die Datenbank geschickt. Auf die Abfragesprache SQL wird in diesem Kapitel nicht weiter eingegangen, sondern nur auf die Mechanismen in Java, die Verbindungen zwischen der Anwendung und der Datenbank ermöglichen, über die dann SQL-Befehle geschickt werden können.

Dieses Kapitel behandelt ausschließlich Java-Applications. Der Grund liegt darin, dass ein Datenbankzugriff voraussetzt, das auf dem gleichen Computer, auf dem das Java-Programm läuft, die Datenbank zur Verfügung steht. Da Java-Applets über das Internet in einen Browser geladen werden und dann im Browser ablaufen, müsste sichergestellt sein, dass jeder Anwender, der das Applet in seinen Browser lädt, auf seinem PC auch die Datenbank laufen hat, auf die das Applet zugreifen möchte. Selbst wenn diese Voraussetzung erfüllt wäre, müsste das Java-System so konfiguriert werden, damit tatsächlich das Java-Applet auf die Datenbank zugreifen kann. Denn die Sicherheitsbestimmungen von Java verhindern, dass das Applet einfach so aus seiner Sandbox heraus mit Datenbanken kommunizieren kann. Der Normalfall ist daher eher der, dass auf Datenbanken von Java-Applications aus zugegriffen wird.

Da der Zugriff auf Datenbanken in Java seit der Version 1.1 möglich ist, ist das Praxis-Beispiel in diesem Kapitel auch in Java 1.1 entwickelt.


5.2 Datenbank-Manager und -Treiber

Bindeglieder zwischen Anwendung und Datenbank

Java bietet verschiedene Möglichkeiten, um Anwendungen an Datenbanken zu koppeln. Die älteste, da zuerst in Java implementierte Technologie und heutiger De-facto-Standard zum Datenbankzugriff in Java nennt sich JDBC. JDBC steht für Java Database Connectivity und ist an ODBC angelehnt. ODBC wiederum steht für Open Database Connectivity und ist der Datenbankstandard in der Windows-Welt schlechthin. ODBC wurde von Microsoft entwickelt und hat sich nicht nur erfolgreich in der Windows-Welt durchgesetzt, sondern wurde auch auf Unix- und Linux-Systeme portiert. In Anlehnung an diesen erfolgreichen Standard hat Sun Microsystems ein ähnliches Konzept zum Datenbankzugriff in Java entwickelt.

Darstellung des Zusammenhangs von Anwendung, Datenbank-Manager, Datenbank-Treiber und Datenbank

JDBC schaltet zwischen Java-Anwendung und Datenbank zwei Bindeglieder, nämlich einen Datenbank-Manager und einen Datenbank-Treiber. Java-Anwendungen greifen also normalerweise nicht direkt auf eine Datenbank zu, sondern kommunizieren nur mit einem Datenbank-Manager. Alle Befehle, die an den Datenbank-Manager weitergegeben werden, werden vom Datenbank-Manager über den datenbankspezifischen Treiber an die Datenbank weitergeleitet. Diese mehrfache Verkettung von Bindegliedern hat den Sinn, Anwendungen unabhängig von konkreten Datenbanken zu entwickeln.

Anwendungen werden programmtechnisch nur direkt an den Datenbank-Manager gekoppelt. Der Datenbank-Manager ist fest in die Java-Klassenhierarchie integriert. Bevor die Anwendung mit einer Datenbank arbeiten kann, sagt sie dem Datenbank-Manager, mit welcher Datenbank sie genau arbeiten möchte. Der Datenbank-Manager kümmert sich dann darum, einen geeigneten Treiber zu verwenden. Dieses Bindeglied zwischen Datenbank-Manager und Datenbank selber wird normalerweise vom Hersteller der Datenbank entwickelt. So kann jeder Software-Hersteller, der Datenbanken entwickelt, ein passendes Verbindungsstück anbieten, über das ein JDBC-Datenbank-Manager mit der Datenbank kommunizieren kann. Die Schnittstellen zwischen dem Datenbank-Manager und dem Treiber sind hierbei standardisiert und exakt vorgegeben. Dieses flexible System ermöglicht nun die Entwicklung von Anwendungen, die ganz allgemein Datenbanken unterstützen, ohne dass die Anwendung eine konkrete Datenbank eines Herstellers voraussetzt.

Es besteht die Möglichkeit, die JDBC-Welt mit der ODBC-Welt zu vernetzen. Da ODBC ein viel älterer Standard als JDBC ist, existieren möglicherweise für eine Datenbank mehr ODBC-Treiber, die eventuell auch ausgereifter und leistungsfähiger als JDBC-Treiber sind. Über eine JDBC-ODBC-Bridge wird der JDBC-Datenbank-Manager mit dem ODBC-Datenbank-Manager gekoppelt. Der ODBC-Datenbank-Manager verwendet dann einen ODBC-Treiber, um mit einer bestimmten Datenbank zu kommunizieren.

Dieses Hintereinanderschalten von JDBC- und ODBC-Datenbank-Manager wird von Sun Microsystems nicht empfohlen. Zum einen erfordert diese Konstellation, dass auf dem Computer-System ODBC vorhanden und konfiguriert ist - ein echter JDBC-Treiber würde auch ohne ODBC direkt mit einer Datenbank kommunzieren können - zum anderen ist die JDBC-ODBC-Bridge kein Bestandteil der offiziellen Klassenhierarchie in Java. Die JDBC-ODBC-Bridge ist zwar von Sun Microsystems den Entwicklungswerkzeugen im Software Development Kit in Form der Klasse sun.jdbc.odbc.JdbcOdbcDriver beigelegt worden. Das heißt aber nicht, dass Java-Klassenhierarchien anderer Hersteller ebenfalls die Klasse sun.jdbc.odbc.JdbcOdbcDriver anbieten. Microsoft bietet zum Beispiel diese Klasse in seinem Software Development Kit for Java nicht an. Stattdessen hat man eine eigene JDBC-ODBC-Bridge entwickelt und diese in der Klasse com.ms.jdbc.odbc.JdbcOdbcDriver implementiert.

Im nachfolgenden Beispiel wird dennoch die JDBC-ODBC-Bridge angewandt. Zum einen ist ODBC ein fester Bestandteil der Windows-Betriebssysteme und automatisch vorhanden. Zum anderen bietet Microsoft für die Datenbank Access keinen eigenen JDBC-Treiber an. Es gibt zwar von Dritt-Herstellern JDBC-Treiber für Access, diese müssten jedoch erst von Ihnen heruntergeladen und installiert werden. Die Anwendung der JDBC-ODBC-Bridge ist daher für folgendes Beispiel einfacher.

Wollen Sie das Beispiel unter einem anderen Betriebssystem als Windows ausführen, so müssen Sie eine neue Datenbank erstellen, die unter Ihrem Betriebssystem lauffähig ist, und den Treibernamen im Code des Beispiels ändern. Eine Übersicht über existierende JDBC-Treiber und Links zum Download finden Sie auf der Homepage der JDBC-Technologie bei Sun Microsystems.


5.3 Datenbankzugriff per JDBC

Der De-facto-Standard in Java

Nachdem JDBC die am häufigsten angewandte Methode in Java ist, um auf Datenbanken zuzugreifen, wird dieses Verfahren umfangreich in diesem Kapitel vorgestellt. Die Vorgehensweise ist bei JDBC folgende: Das Java-Programm greift auf den Datenbank-Manager zu und fordert einen Verbindungsaufbau zu einer bestimmten Datenbank an. Dazu werden im Allgemeinen Angaben wie Art des Zugriffsmechanismus, Name der Datenbank und möglicherweise Benutzername und Passwort für den Datenbankzugriff benötigt. Der Datenbank-Manager meldet der Java-Anwendung, wenn die Verbindung zur gewünschten Datenbank erfolgreich aufgebaut wurde. Steht die Verbindung, erstellt die Java-Anwendung einen SQL-Befehl. Dieser SQL-Befehl wird dann über die Verbindung an die Datenbank geschickt. Wenn es sich um einen Abfrage-Befehl handelt, wird das Ergebnis der Abfrage zurückgegeben.

Das folgende Beispiel zeigt das Ergebnis einer Datenbankabfrage in einem Fenster an. Es werden nachfolgend lediglich die JDBC-relevanten Anweisungen erläutert, da auf die Entwicklung von Fenster-Oberflächen mit AWT in einem eigenen Kapitel umfangreich eingegangen wurde. Um das Beispiel auf Ihrem PC nachzuvollziehen, müssen Sie Ihre ODBC-Konfiguration um einen sogenannten DSN-Eintrag erweitern. ODBC wird auf Windows-Systemen über die Systemsteuerung konfiguriert. Fügen Sie ODBC eine neue Datenquelle hinzu, der Sie den Namen Highscore geben und die Sie mit der Access-Datenbank verknüpfen, die vom Beispiel verwendet wird und in der ZIP-Datei mit dem Quellcode enthalten ist.

Um das Beispiel auf Ihrem Computer auszuführen, ist keine installierte Version von Microsoft Access notwendig. Sie benötigen lediglich einen Access-Treiber, der in der ODBC-Steuerung aufgelistet wird, wenn er vorhanden ist.

import java.awt.*; 
import java.awt.event.*; 
import java.sql.*; 

public class MyApplication extends Frame implements WindowListener 
{ 
  static MyApplication myapp; 

  public static void main(String args[]) 
  { 
    try 
    { 
      Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); 
      /* Class.forName("com.ms.jdbc.odbc.JdbcOdbcDriver"); */ 

      myapp = new MyApplication(); 
      myapp.setLayout(new GridLayout(1, 2)); 
      List list1 = new List(4); 
      List list2 = new List(4); 
      myapp.add(list1); 
      myapp.add(list2); 
      myapp.setSize(400, 120); 
      myapp.addWindowListener(myapp); 
      myapp.setVisible(true); 

      String url = "jdbc:odbc:Highscore"; 
      Connection con = DriverManager.getConnection(url); 

      Statement stmt = con.createStatement(); 

      String sql = "SELECT Firstname, Lastname FROM employees"; 
      ResultSet rst = stmt.executeQuery(sql); 

      while (rst.next()) 
      { 
        list1.add(rst.getString("Firstname")); 
        list2.add(rst.getString("Lastname")); 
      } 

      rst.close(); 
      stmt.close(); 
      con.close(); 
    } 
    catch (Exception ex) 
    { 
      System.out.println(ex.toString()); 
      System.exit(1); 
    } 
  } 

  public void windowClosing(WindowEvent ev) 
  { 
    setVisible(false); 
    dispose(); 
    System.exit(1); 
  } 

  public void windowActivated(WindowEvent ev) { } 
  public void windowClosed(WindowEvent ev) { } 
  public void windowDeactivated(WindowEvent ev) { } 
  public void windowDeiconified(WindowEvent ev) { } 
  public void windowIconified(WindowEvent ev) { } 
  public void windowOpened(WindowEvent ev) { } 
} 

In obiger Beispiel-Anwendung wird auf eine Microsoft-Access-Datenbank per JDBC-ODBC-Bridge zugegriffen. Da es sich um eine Java-Application handelt und um kein Applet, lautet die zu implementierende Start-Methode main(). Mit dieser Methode beginnt die Java-Application zu laufen. Beachten Sie, dass main() eine statische Methode ist. Sie können daher in main() nicht mit dem Schlüsselwort this arbeiten, da this sich jeweils auf das aktuelle Objekt bezieht, main() aber beim Programmstart für kein Objekt aufgerufen wird.

Bevor auf eine Datenbank zugegriffen werden kann, muss der datenbankspezifische Treiber geladen werden - das Bindeglied zwischen Datenbank-Manager und Datenbank. Man ruft hierzu die Methode forName() der Klasse java.lang.Class auf und übergibt ihr den Namen der Klasse, die den Datenbank-Treiber implementiert. In diesem Fall wird als Treiber die JDBC-ODBC-Bridge von Sun Microsystems verwendet. Für die Java-Anwendung und speziell für den JDBC-Datenbank-Manager spielt es keine Rolle, dass der verwendete Treiber nicht direkt eine Verbindung zur Datenbank, sondern nur zum ODBC-Datenbank-Manager aufbaut. Dies spielt sich alles im Hintergrund ab und läuft unabhängig von Java. Wenn Sie das Beispiel-Programm mit den Microsoft-Entwicklungswerkzeugen ausführen wollen, ändern Sie den Code und ersetzen Sie die Angabe sun.jdbc.odbc.JdbcOdbcDriver durch com.ms.jdbc.odbc.JdbcOdbcDriver.

Nachdem der benötigte Treiber geladen wurde, wird auf den Datenbank-Manager zugegriffen. Der Datenbank-Manager ist in der Klasse java.sql.DriverManager implementiert. Er besitzt eine statische Methode namens getConnection(), die überladen ist und mit verschiedenen Parameterlisten angeboten wird. Der einfachste Methoden-Aufruf von getConnection() erwartet lediglich einen Parameter, und zwar eine URL. Die Bezeichnung URL meint in diesem Fall keine WWW-Adresse, sondern eine genaue Angabe, zu welcher Datenbank denn nun eine Verbindung aufgebaut werden soll. Bisher haben wir lediglich einen Treiber zum Zugriff auf Access-Datenbanken geladen. Auf welche Access-Datenbank genau zugegriffen werden soll, wird nun in der URL dem Datenbank-Manager mitgeteilt.

Das genaue Aussehen der URL kann je nach Datenbank und notwendigen Parametern ziemlich unterschiedlich sein. Die URL besitzt jedoch drei Grundbausteine: Der Angabe jdbc folgt ein Subprotokoll und ein Subname. Alle drei Bestandteile werden durch Doppelpunkte getrennt - also jdbc:subprotocol:subname. Für Datenbankzugriffe per JDBC-ODBC-Bridge muss als Subprotokoll odbc und als Subname ein DSN angegeben werden. Die Angabe für JDBC-ODBC-Verbindungen lautet also jdbc:odbc:dsn. Über URLs wird ganz klar beschrieben, zu welcher Datenbank und über welchen Mechanismus die Verbindung aufgebaut werden soll. Sehen Sie in der Dokumentation Ihrer Datenbank und in der des JDBC-Treibers nach, wie die URL genau aufgebaut sein muss, wenn Sie auf eine andere Art und Weise als per JDBC-ODBC-Bridge auf eine Datenbank zugreifen wollen.

Sollte es irgendein Problem beim Verbindungsaufbau zur Datenbank geben, wirft die Methode getConnection() eine Exception vom Typ java.sql.SQLException. Ansonsten wird ein Objekt vom Typ java.sql.Connection zurückgegeben, das die Verbindung zur Datenbank charakterisiert und über das im Nachfolgenden SQL-Anweisungen gesendet und Abfrageergebnisse empfangen werden können.

Um eine SQL-Anweisung über die bestehende Verbindung an die Datenbank zu senden, wird mit der Methode createStatement() ein Objekt vom Typ java.sql.Statement erzeugt. Für dieses Objekt wird die Methode executeQuery() aufgerufen, wenn eine SQL-Abfrage mit SELECT ausgeführt werden soll, und executeUpdate(), wenn eine SQL-Anweisung mit INSERT, UPDATE oder DELETE ausgeführt werden soll. Während executeQuery() ein Objekt vom Typ java.sql.ResultSet zurückgibt, das Zugriff auf das Abfrage-Ergebnis ermöglicht, wird von executeUpdate() ein int-Wert für die Anzahl der veränderten Zeilen für den ausgeführten SQL-Befehl zurückgegeben. Sollte die SQL-Anweisung fehlschlagen, wirft sowohl executeQuery() als auch executeUpdate() eine Exception vom Typ java.sql.SQLException.

Im vorliegenden Beispiel-Programm wird eine Tabelle employees durchsucht. Es wird nach Vor- und Nachname aller Angestellten gefragt, die in der Tabelle gespeichert sind. Das Ergebnis dieser SQL-Abfrage steht im Objekt rst zur Verfügung. Es handelt sich hierbei um eine Tabelle, die zeilenweise alle gefundenen Ergebnisse enthält, und deren Spalten mit den in der SQL-Abfrage angegebenen Spaltennamen übereinstimmen. Die Klasse java.sql.ResultStatement, auf der das Objekt rst basiert, bietet zahlreiche Methoden an, um nun auf die gefundenen Daten in der Ergebnistabelle zuzugreifen.

Um die gefundenen Daten auszugeben, wird in einer while-Schleife wiederholt auf das Objekt rst zugegriffen. Der Aufruf von next() setzt den Cursor jeweils in die nächste Zeile der Ergebnistabelle, in der dann mit der Methode getString() das gefundene Ergebnis aus der Spalte Firstname und Lastname geholt wird. Beachten Sie, dass Sie getString() nur anwenden, wenn die entsprechende Spalte tatsächlich ein Ergebnis vom Typ einer Zeichenkette enthält. Die Klasse java.sql.ResultSet bietet zahlreiche andere Methoden wie getBoolean(), getByte(), getDate(), getInt(), getFloat() und so weiter, die im SQL-Standard definierte Datentypen in Datentypen aus Java konvertieren. Sie müssen darauf achten, dass Sie die jeweils korrekte Methode verwenden. Als Parameter übergeben Sie einfach den Namen der Spalte, die Sie auslesen wollen. Sollte der Aufruf einer dieser Methoden zu einem Fehler führen, wird im Fehlerfall wie so oft bei JDBC-Operationen eine Exception vom Typ java.sql.SQLException geworfen.

Bevor Sie beginnen, auf Daten in der Ergebnistabelle zuzugreifen, müssen Sie unbedingt genau einmal die Methode next() für das Objekt rst aufrufen. Der Tabellencursor zeigt von Anfang an nicht auf die erste gefundene Zeile, sondern muss erst mit einem Aufruf von next() auf die erste Zeile in der Ergebnistabelle gesetzt werden. next() liefert jeweils einen Wert vom Typ boolean zurück, an dem Sie erkennen können, ob eine weitere Zeile in der Ergebnistabelle existiert oder nicht. Am Ende schließen Sie mit close() die Ergebnistabelle, wenn Sie alle Daten ausgelesen haben und Sie sie nicht mehr benötigen. Außerdem rufen Sie close() für stmt und con auf, um das SQL-Anweisungsobjekt vom Typ java.sql.Statement und die Verbindung zur Datenbank zu schließen.


5.4 SQL Object Language Bindings

Kompakt, aber statisch

Um die Anbindung von Java an Datenbanken zu verbessern, arbeiten mehrere Unternehmen gemeinsam an einem neuen Standard: Mit SQL/OLB soll es in Zukunft schneller und einfacher werden, Daten in Java aus Datenbanken zu lesen und in Datenbanken zu schreiben. Anstatt sich mit mehreren Objekten wie Datenbank-Manager, Verbindung, SQL-Statement und Ergebnistabelle rumzuschlagen, sollen sogenannte SQL/OLB Klauseln Java-Variablen direkt mit SQL-Anweisungen verknüpfen. Die Variablen werden je nach Kontext als Eingangswerte für SQL-Anweisungen verwendet oder als Speicherort für Daten aus einer Abfrage der Datenbank. Die SQL/OLB Klauseln werden automatisch an eine Datenbank geschickt, deren Name lediglich zu Beginn des Programms angegeben werden muss, ohne dass entsprechende Anweisungen zum Senden der SQL-Befehle programmiert werden müssen.

Möglich machen diese scheinbare Zauberei zusätzliche Werkzeuge. Ein sogenannter SQL/OLB-Übersetzer liest vor der Kompilierung die SQL/OLB Klauseln und wandelt sie in gültigen Java-Code um. Der automatisch erstellte Java-Code greift auf eine SQL/OLB-Bibliothek zu, die letztendlich die Verbindung zur Datenbank herstellt. Dies wird wie gewohnt per JDBC erfolgen.

SQL/OLB ist eine sehr neue Technik. Sie besteht aus drei Teilen, von denen der erste inzwischen offizieller ANSI-Standard ist. Die anderen beiden Teile befinden sich derzeit in der Entwicklung. Wenn der Standard komplett verabschiedet sein wird, wird es möglich sein, Datenbankzugriffe ähnlich wie im folgenden Code-Ausschnitt zu programmieren.

oracle.sqlj.runtime.Oracle.connect("jdbc:oracle:oci"); 

String firstname; 
#sql { SELECT Firstname INTO :firstname FROM employees WHERE Lastname = 'Schäling' }; 

System.out.println(firstname); 

Der Vorteil von SQL/OLB ist die enorm kompakte Schreibweise. Der Software-Entwickler kann mit wenigen Code-Zeilen Datenbankzugriffe programmieren, da der SQL/OLB-Übersetzer ihm viel Arbeit abnimmt und die SQL/OLB Klauseln in gültigen Java-Code übersetzt. Der Nachteil ist, dass mit #sql eingeleitete SQL/OLB Klauseln statisch sind und fest im Code verankert werden. Es ist nicht möglich, solche Klauseln dynamisch während der Laufzeit des Programms zu erstellen, so wie es mit SQL-Anweisungen, die als Zeichenkette vorliegen, möglich ist. Dennoch kann man davon ausgehen, dass SQL/OLB ein erfolgreicher Standard werden wird. Die Unterstützung von SQL/OLB, das auch als SQLj bekannt ist, ist jedenfalls riesig - von Sun Microsystems über Oracle bis IBM ist alles dabei, was bei Java und Datenbanken Rang und Namen hat.


5.5 Aufgaben

Übung macht den Meister

Sie können die Lösungen zu allen Aufgaben in diesem Buch als ZIP-Datei erwerben.

  1. Erweitern Sie das Beispiel aus Abschnitt 5.3, „Datenbankzugriff per JDBC“ dahingehend, dass bei einem Mausklick auf die in den Listen angezeigten Einträge der entsprechende Datensatz sowohl aus der Datenbank gelöscht als auch aus den Listen entfernt wird. Es spielt hierbei keine Rolle, ob der Anwender auf den Vor- oder Nachnamen eines Angestellten klickt: Der komplette Datensatz soll aus der Datenbank gelöscht werden, und Vor- und Nachname des Angestellten sollen aus der Fensteroberfläche verschwinden.

    Um Mausklicks auf Einträge in den Listen zu registrieren, verwenden Sie das Interface java.awt.event.ItemListener. Sie müssen hierzu die Klasse MyApplication um eine Methode itemStateChanged() erweitern. Über den Parameter, der automatisch vom Java-System an diese Methode übergeben wird, finden Sie heraus, auf welche der beiden Listen der Anwender geklickt hat. Bauen Sie dann erneut eine Verbindung zur Datenbank auf und senden Sie einen SQL-Befehl zum Löschen des ausgewählten Datensatzes. Vergessen Sie nicht, den Datensatz des Angestellten außerdem aus den Listen zu entfernen, wenn die Lösch-Operation erfolgreich war.