JDBC stellt ein Standard-API zur Verfügung, mit dessen Hilfe Objekte und Methoden definiert werden können, die es dem Programmierer ermöglichen, in der Applikation den Zugriff auf die darunter liegende Datenbank zu realisieren. Der Zugriff erfolgt in der folgenden Art und Weise (siehe auch Abb. 10-8): Laden eines geeigneten JDBC-Treibers. Herstellung einer Datenbankanbindung über den entsprechenden JDBC-Treiber des verwendeten DBMS. Erstellung eines Anweisungsobjekts (sog. Statement Object) und Weitergabe der auszuführenden Anweisung über das Anweisungsobjekt an das darunter liegende DBMS. Rückgabe der Ergebnisse in Form von Ergebnisdatensätzen. Schließen der Verbindung zur Datenbank. Eine Java-Applikation läuft hierbei typischerweise auf einem Client-Rechner, der eine Verbindung zu einer (oder mehreren) Datenbank(en) aufbaut, die sich auf einem Remote-Server befindet(en). Die Verwaltung der Datenbankverbindungen, die in Form von Java-Objekten erzeugt werden, wird vom JDBC-Treiber-Manager übernommen. Dieser kann mehrere Verbindungen zu unterschiedlichen Datenbanken gleichzeitig verwalten und ermöglicht somit auch den Zugriff auf verteilte Datenbanken. Abb. 10.8: Allgemeiner Ablauf einer JDBC-Anwendung Bevor der allgemeine Ablauf einer Datenbankanfrage über JDBC erläutert wird, muss darauf aufmerksam gemacht werden, dass eine JDBC-Anwendung alle für die Ausführung von JDBC notwendigen Klassen importieren muss. Diese Klassen sind Teil des Packages java.sql, das Bestandteil des JDKs seit der Version 1.1 ist. Laden eines JDBC-Treibers Zur Ausführung von JDBC-Anweisungen muss ein Datenbanktreiber geladen werden, der Anweisungen in eine Form umsetzt, die vom speziellen Datenbanksystem verstanden werden. Ein derartiger Treiber kann, wie in Kapitel 10.2 erläutert, einer der insgesamt vier verschiedenen Typen sein. Hierbei kann es sich entweder um eine JDBC-ODBC-Bridge handeln, die eine ODBC-Datenbank ansprechen kann, oder um einen in native Code implementierten Treiber für ein spezielles DBMS (bspw. DB2-JDBC-Treiber oder Oracle-JDBC-Treiber). Der Treiber wird mit dem in Java verfügbaren Lademechanismus für Klassen (Class Loader) mit Hilfe der folgenden Methode geladen: class.forName("meinTreiber"); Somit lautet der Code für das Laden des standardmäßig verwendeten JDBC-ODBC-Bridge-Treibers: Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Das Laden eines Treibers mit Hilfe der Anweisung class.forName() hat den Vorteil, dass eine Instanz des Treibers dynamisch erzeugt und automatisch beim TreiberManager-Objekt registriert wird (siehe auch Abb. 10-9). Abb. 10.9: Dynamisches Laden eines JDBC-Treibers Verbindung zu Datenbanken Bei der Erstellung einer Verbindung zu einer Datenbank wird die bereits erwähnte Komponente java.sql.DriverManager eingesetzt. Hierbei muss zunächst eine Verbindung zur Datenbank aufgebaut werden, bevor Anfragen an eine Datenquelle gestellt und verarbeitet werden können. Eine Datenbankanbindung kann auf die folgenden zwei Arten erstellt werden: Standardmäßig durch die Methode GetConnection() der Klasse java.sql.DriverManager. Explizit durch die Methode Connect() des Interfaces java.sql.Driver. Der Konstruktor der Methode java.sql.getConnection() erwartet bis zu drei Parameter: String db_url Die URL der Datenbank, zu der die Verbindung aufgenommen werden soll. String userID Anmeldung des Benutzers in der Datenbank. String passwd Das entsprechende Passwort des Benutzers für die Datenbankanmeldung. Die beiden letzten Parameter (userID und passwd) sind optional und können gegebenenfalls auch in Form einer Liste übergeben werden. Die Syntax für das Öffnen einer Verbindung lautet somit: Connection mein_con = DriverManager.getConnection(db_url, userID, passwd); Die Struktur der angegebenen URL (db_url) ist von der Implementierung des Treibers abhängig. Im Fall der JDBC-ODBC-Bridge hat sie die Form: jdbc:odbc:datenQuelleName[;attributName= attributWert]* Hierbei ist datenQuelleName der Name der Datenquelle. Die Datenquelle wird durch ein Semikolon von einer beliebigen Anzahl von Attributen getrennt. Ein Beispiel für die oben angegebene Syntax ist das folgende Code-Segment: jdbc:odbc:mein_jdbc_test // oder bei Verwendung von zwei Attributen jdbc:odbc:mein_jdbc_test;CacheSize=20;ExtensionCase=LOWE R Datenquellen werden in der Regel durch Benutzernamen und Passwort vor Missbrauch geschützt. Diese werden der Datenbank in Form der Parameter userID und passwd übermittelt. Die Klasse java.sql.DriverManager durchsucht bei diesem Aufruf eine Liste der registrierten Treiber und versucht, eine Verbindung zur Datenbank durch den impliziten Aufruf der Klasse Driver.Connect aufzubauen. Hierbei wird der erste passende Treiber verwendet und die Treibersuche damit abgeschlossen (siehe Abb. 10-10). Die zweite Möglichkeit, eine Verbindung zu einer JDBC-Datenbank zu erstellen, ist die Verwendung der Methode java.sql.Driver.Connect, mit der der gewünschte Treibers explizit aufgerufen wird. Dieses Vorgehen ist dann nützlich, wenn ein bestimmter Treiber eingesetzt werden soll. Abb. 10.10: Aufbau einer JDBC-Verbindung zu einem DBMS Generierung und Ausführung von Anweisungen Die Methode createStatement() der Klasse java.sql.Connection erzeugt ein JDBC-Anweisungsobjekt (java.sql.Statement). Ein Statement-Objekt kann hierbei nicht wie sonst üblich mit Hilfe des Kommandos new erzeugt werden, sondern wird beim entsprechenden Connection-Objekt angefordert. Diese Eigenschaft setzt voraus, dass eine Datenbankverbindung bereits besteht. Die Syntax hierfür lautet: Statement mein_stmnt = mein_con.createStatement(); Mit einem Statement-Objekt werden die Daten einer Datenbank verändert und gespeichert. Es existieren drei verschiedene Statement-Typen mit verschiedenen Aufgaben, um eine SQL-Anfrage an die verbundene Datenbank zu senden. Abhängig von der Anfrageart wird die Methode createStatement() der Klasse java.sql.Statement, die Methode prepareStatement() der Klasse java.sql.PreparedStatement oder die Methode prepareCall() der Klasse java.sql.CallableStatement aufgerufen, wodurch eine entsprechende Instanz entsteht. Statement-Objekte Ein Statement wird zur Datenmanipulation und zum Erzeugen von Ergebnissen benutzt. Eine Anfrage kann durch die Methoden executeQuery(), executeUpdate(), executeBatch() oder execute() ausgeführt werden. Die jeweilige Methode bezieht sich immer auf ein bestehendes Statement und erwartet als Eingabeparameter einen String für das auszuführende Statement. Der Rückgabewert der Ausführung der Methode executeQuery() ist vom Typ ResultSet (Ergebnistabelle bei Abfragen oder Auswertungen). Der Rückgabewert ist bei der Ausführung der Methoden executeUpdate() oder executeBatch() vom Typ int (z. B. Anzahl der geänderten Datensätze bei Update-Anweisungen). Die Methode execute() liefert den Datentyp boolean (true oder false) zurück. Die Syntax einer Abfrage lautet hier wie folgt: ResultSet mein_result = mein_stmt.executeQuery("SELECT * FROM Spieler"); Die Methode executeUpdate() wird für Veränderungen von Tabellen verwendet. Die SQL-Anfragen INSERT, UPDATE und DELETE verändern Spalten und Zeilen einer Tabelle. Der Rückgabewert entspricht dabei der Zahl veränderter Zeilen. CREATE TABLE und DROP TABLE liefern demnach als Rückgabewert immer den Wert 0. int update_result = my_stmt.executeUpdate("CREATE TABLE Tabelle_2"); Die Methode execute() wird verwendet, wenn mehrere Resultate zurückgeliefert werden sollen. PreparedStatement-Objekte Die Klasse PreparedStatement ist abgeleitet von der Klasse java.sql.Statement. Allerdings werden hier die oben aufgeführten Methoden executeQuery(), executeUpdate() und execute() verändert. Sie erhalten keinen Parameter, da die SQL-Anfrage bereits mit dem Konstruktor übergeben wird. Ein PreparedStatement-Objekt wird einmal erzeugt und übersetzt und anschließend zur Laufzeit mit aktuellen Parametern versehen. Dieses Vorgehen weist gegenüber einem einfachen Statement-Objekt eine besondere Effizienz auf. Ein PreparedStatement-Objekt wird mit der folgenden Syntax erzeugt: PreparedStatement mein_prstmnt = Connection.prepareStatement("UPDATE Spieler SET Gespielt=? WHERE Name=?"); Mit der dargestellten Syntax wird eine Anfrage vorbereitet, deren Werte Gespielt und Name zur Laufzeit gesetzt werden. Das Fragezeichen fungiert als Platzhalter. Werte in einem PreparedStatement-Objekt werden mit den Methoden setXXX gesetzt. Dadurch ist es möglich, eine SQL-Anfrage mit verschiedenen Parametern zu stellen, ohne bei jedem Aufruf eine Instanz der Klasse Statement erstellen zu müssen. Bevor die Anfrage gesendet wird, müssen die Parameter gesetzt werden. Die Methoden setInt(), setShort() und setString() setzen bspw. die Parameter. Hierbei muss die entsprechende Konvertierungssyntax beachtet werden: PreparedStatement.setxxx(position, derZuSetzendeWert); Der erste Parameter der Methode setxxx gibt die Ordinalposition des Parameters in der SQL-Anfrage an. Der zweite Parameter entspricht dem Wert, den die Variable annehmen soll. Die nachfolgenden Beispiele demonstrieren dieses Vorgehen. Das erste Beispiel verwendet die Methode executeUpdate zur Bearbeitung einer Instanz der Klasse Statement. Das zweite Beispiel nutzt ein vorbereitetes Statement-Objekt (PreparedStatement). // Benutzen der executeUpdate-Methode zur Bearbeitung // einer Instanz der Statement-Klasse. // Eine Verbindung wird etabliert Statement mein_stmnt = mein_con.createStatement(); // Definition eines String-Objekts, um // die Tabelle zu aktualisieren String updateString = "UPDATE Spieler SET Gespielt=5 WHERE Name=ElSaddik"; // Tabelle aktualisieren mein_stmnt.executeUpdate(updateString); // Benutzen von PreparedStatement-Objekten: // Eine Verbindung zur Tabelle wird hergestellt. Dazu wird // ein PreparedStatement-Objekt definiert. Bei diesem Objekt // werden zwei Variablen durch das Benutzen eines // Fragezeichens "?" definiert PreparedStatement mein_prstmnt = mein_con.prepareStatement("UPDATE Spieler SET Gespielt=? WHERE Nachname=?"); // Setzen der jeweiligen Variablen mit den entsprechenden // setxxx-Methoden mein_prstmnt.setInt(1, 5); mein_prstmnt.setString(2, ElSaddik); // Tabelle aktualisieren mein_prstmnt.executeUpdate(); CallableStatement-Objekte Die Klasse java.sql.CallableStatement stellt eine Erweiterung der Klasse PreparedStatement dar. Die Parameter eines CallableStatement-Objektes stellen nicht nur Eingabewerte, sondern auch Ausgabewerte dar. Die Ergebnisse werden dann dementsprechend mit Methoden der Form getxxx ermittelt. Die Platzhalter (Fragezeichen), die die Ausgabewerte enthalten, müssen initialisiert werden. Der Typ des Rückgabewertes wird mit Hilfe der folgenden Syntax angegeben: CallableStatement.registerOutParameter(position, java.sql.Types.DER_TYP); Der erste Parameter der Registrierungsmethode gibt die Ordinalposition des Parameters in der SQL-Anfrage an. Der zweite Parameter entspricht dem Datentyp der Variablen (z. B. TINYINT oder DECIMAL). Mit Hilfe dieser Klasse können sog. Stored Procedures aufgerufen werden, sofern das Datenbanksystem derartige Prozeduren zur Verfügung stellt. Anfragemakros (Stored Procedures) stellen eine gekapselte Menge von SQL-Anweisungen dar, die zu einem bestimmten Zeitpunkt aufgerufen werden können. Zur Veranschaulichung wird das folgende Beispiel verwendet: // Registriere den ersten Parameter als TINYINT-Wert CallableStatement.registerOutParameter(1, java.sql.Types.TINYINT); // Die Anfrage soll ausgefuehrt werden, wobei der Wert des // ersten Parameters 1 ist. Ein SQL-TINYINT wird in Java auf int // oder byte abgebildet. CallableStatement.executeQuery(); byte x=CallableStatement.getByte(1); Verarbeitung von Ergebnissen Das Ergebnis einer Abfrage, das durch den Aufruf der Methode executeQuery() ermittelt wird, wird in Form einer Ergebnistabelle vom Typ java.sql.ResultSet zurückgeliefert. Dieses Interface bietet Methoden der Form getxx, mit denen die zurückgelieferten Ergebnisse ausgewertet werden können. In Abhängigkeit des Ergebnistyps muss die entsprechende getxxx-Methode gewählt werden. Der Zugriff auf Spaltenwerte erfolgt mit einer der folgenden Methoden: String mein_result = ResultSet.getString("SpaltenName"); String mein_result = ResultSet.getString("SpaltenNummer"); Der Aufruf der erste Methode erlaubt den Zugriff auf Spaltenwerte durch das Eingeben des Spaltenamens, wobei beim Aufruf der zweiten Methode die Spaltennummer eingegeben werden kann. In der Regel liefern Anfragen eine Vielzahl von Ergebnissen. Mit der Methode next() kann der nächste Ergebniseintrag einer Spalte bearbeitet werden. ResultSet.next; Mit der folgenden SQL-Abfrage werden sowohl ein numerischer Wert (Anzahl der gespielten Runden) sowie ein String-Objekt, das den Nachnamen des Spielers darstellt, zurückgeliefert. SELECT Gespielt, Nachname FROM Spieler; Der entsprechende Java-Code verwendet die Methoden getInt() und getString(), um die Resultate zu bearbeiten: int anzahlSpiele = mein_result.getInt("Gespielt"); String name = mein_result.getString("Name"); Ist der Aufbau der zugrunde liegenden Datenbanktabelle nicht bekannt (bspw. bei der Anweisung "SELECT * FROM Tabelle_1"), so muss erst mittels der Methode Connection.GetMetaData die Struktur einer Tabelle für eine bestehende Verbindung ausgelesen und mit den entsprechenden Methoden ausgewertet werden. Eine ausführliche Beschreibung hierzu ist Teil des Anwendungsbeispiels (siehe Kapitel 10.8). Schließen einer Verbindung Das Schließen der Datenbankverbindung erfordert explizit die Abmeldung von der Datenbank. Dafür wird die entsprechende Methode close() der Klasse java.sql.Connection aufgerufen. Diese Methode wird für eine bestehende Verbindung ohne Parameter verwendet. Die Syntax für das Schließen einer Datenbankverbindung lautet: mein_con.close();