home  |  suche  |  kontakt/johner  |  institut 
studierende  |  tech-docs  |  mindmailer 

Einführung

JDBC steht für Java Database Connectivity und stellt eine Zugriffschicht für Datenbanken zur Verfügung, die unabhängig von einer speziellen Datenbank, einem speziellen Produkt, ist. Damit ist es beispielsweise möglich, während der Entwicklung und dem produktiven Betrieb einer Anwendung verschiedene Datenbanken zu nutzen, ohne Änderungen am Quellcode vornehmen zu müssen.

Genau genommen muss dieser Zugriff nicht einmal auf eine Datenbank erfolgen, es existieren auch JDBC-konforme Treiber, welche die Persistierung der Daten über XML-Dateien realisiert. Ein Beispielprodukt findet sich bei http://www.sunopsis.com/.

Datenbanktreiber

Die Schnittstelle zur Datenbank (Datenbankmanagementsystem DBMS) wird über JDBC standardisiert. Hingegen obliegt es dem Hersteller der Datenbank, diese Schnittstelle zu implementieren. Diese Vermittlungsschicht bezeichnet man als Treiber und unterscheidet abhängig von der technischen Umsetzung vier verschiedene Typen.

Treiber des Typs 1

Dieser Treiber vermittelt nicht zwischen der Java-Anwendung und der Datenbank, sondern zwischen der Anwendung und einem ODBC-Treiber, welcher die eigentliche Kommunikation mit der Datenbank durchführt. Dieser Treibertyp hat den Vorteil, dass es viele Werkzeuge gibt, die ebenfalls auf ODBC aufsetzen, beispielsweise um Datenbankinhalte zu lesen, abzufragen oder auszuwerten. Als Nachteil bringt diese zusätzliche Abstraktionsschicht Performanzeinbußen und die Abhängigkeit zum Windows Betriebssystem mit sich. Damit eignet sich dieser Treibertyp mit der JDBC-ODBC-Bridge besonders für die Entwicklung.

Treiber der Typen 2 und 3

Diese beiden Treiberklassen verzichten auf die ODBC-Schicht und bestehen aus dem eigentlichen JDBC-Treiber und einer von ihm gekapselten proprietären Schicht.

Beim Typ 2 befindet sich diese Schicht in Form eine Datenbankklienten auf dem Computer, auf dem die Anwendung läuft (und damit nicht notwendigerweise auf dem Datenbankserver).

Bei den nicht so gebräuchlichen Treibern vom Typ 3 existiert - ähnlich wie bei ODBC - eine DBMS unabhängige Schicht zwischen JDBC Treiber und dem DBMS, diese Schicht kann sich jedoch auf einem dritten Computersystem, einer Middleware befinden.

Treiber des Typs 4

Diese Treiber werden meist vom Hersteller des DBMS angeboten und sind vollständig in Java implementiert. Damit ist eine Plattformunabhängigkeit gewährleistet, welche jedoch - bedingt durch diesen ausschließlichen Einsatz von Java - mit Performance-Einbußen einhergeht.

Die nachfolgende Abbildung stammt von www.jeckle.de

Das Package java.sql

Die Java-API besteht im Wesentlichen aus Interfaces, nämlich Connection, Statement bzw. PreparedStatement und ResultSet.

  • Connection bildet die Kommunikationsverbindung mit der Datenbank. Üblicherweise baut man nur eine Connection, oder einen Connectionpool auf
  • Statement realisiert eine Datenbankaktion. Die Unterklasse PreparedStatement unterscheidet sich dahingehend, dass sie durch parametrisierte Abfragen ressourcenschonender arbeiten kann.
  • ResultSet repräsentiert die vom DBMS auf eine Anfrage hin zurückgegebene Ergebnismenge. Über diese Ergebnismenge kann man iterieren und die atomaren Informationsstücke extrahieren, welche einem Datenbankfeld entsprechen. Es ist auch möglich, die Ergebnismenge direkt im ResultSet zu modifizieren und in das DBMS zurückzuschreiben.

Nachfolgende Grafik entstammt ebenfalls www.jeckle.de.

Arbeiten mit JDBC

Zu erst muss der Treiber geladen und dann eine Datenbankverbindung erstellt werden.

 

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection connection = DriverManager.getConnection(
"jdbc:odbc:<name_datenbank>", "<user>", "<password>");

 

Würde man (um Engpässe bei vielen Datenbankverbindungen zu vermieden) ein Connectionpool nutzen wollen, wäre obiger Quellcode zu erweitern durch

 

PooledConnection pc = 
new PooledConnection(connection);
Connection conToWorkWith =
pc.getConnection();

 

Als nächstes braucht man ein Statement, mit dem wir exemplarisch eine Tabelle anlegen, mit Daten füllen und abfragen wollen:

 

Statement statement = connection.createStatement();

statement.execute(
"CREATE TABLE students (name VARCHAR(64), id INTEGER);");

statement.execute(
"INSERT INTO students values ('Gerd', 1);");

ResultSet result = statement.executeQuery("
SELECT * FROM students WHERE id = 1;");

statement.close().

 

Aus diesem ResultSet kann nun der Name des Studenten extrahiert werden:

 

while (result.next()) {
String name = result.getString("name"); //Argument ist Spaltenname
}

 

TODO: Update erklären, einmal konventionell, einmal direkt auf ResultSet

Die Klasse DatabaseMetaData gestattet Informationen über die Datenbank, beispielsweise das Datenbanklayout zu erfragen (z.B. Name und Aufbau der Tabellen). Dies kann genutzt werden, um alle Tabellen einer Datenbank zu löschen, ohne dass deren Namen bekannt sein müssen.

 

DatabaseMetaData metas = connection.getMetaData();
ResultSet rs = metas.getTables(null, null, "", null);
while (rs.next() {
String tableName = rs.getString("TABLE_NAME");
statement.execute("DROP TABLE " + tableName);
}

 

Vergleichbar erlaubt die Klasse ResultSetMetadata die Struktur einer Ergebnismenge zu untersuchen.

Beispielcode

package jdbc;

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

public class DatabaseClient {

  public static void main(String[] argsthrows SQLException,
      ClassNotFoundException {
    // Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" );
    // Connection connection =
    // DriverManager.getConnection("jdbc:odbc:imuk1dpm", "", "");
    Class.forName("com.mysql.jdbc.Driver");
    Connection connection = DriverManager.getConnection(
        "jdbc:mysql:///krankenhaus""root""");
    Statement statement = connection.createStatement();

    // Vorhandene Tabelle löschen
    // statement.execute("DROP TABLE Auto");

    // Tabelle anlegen
    statement
        .execute("CREATE TABLE Auto (Seriennr INTEGER, Typ VARCHAR(32), Kaufdatum DATE, Kosten NUMERIC);");

    // Primärschlüssel einfügen
    statement.execute("ALTER TABLE Auto ADD PRIMARY KEY (Seriennr);");

    // Werte hinzufügen
    statement
        .execute("INSERT INTO Auto (Seriennr, Typ, Kaufdatum, Kosten) VALUES (12, 'Audi', '2005-10-09', 1332.20);");
    statement
        .execute("INSERT INTO Auto (Seriennr, Typ, Kaufdatum, Kosten) VALUES (13, 'VW', '2005-11-12', 15232.99);");
    statement
        .execute("INSERT INTO Auto (Seriennr, Typ, Kaufdatum, Kosten) VALUES (14, 'VW', '2003-1-1', 15232.99);");

    // Werte abfragen
    ResultSet rs = statement
        .executeQuery("SELECT * FROM Auto WHERE Typ='VW'");
    printResults(rs);

    statement.close();
    connection.close();
    System.out.println("Fertig!");
  }

  private static void printResults(ResultSet rsthrows SQLException {
    ResultSetMetaData metaData = rs.getMetaData();
    int numberColumns = metaData.getColumnCount();
    String separator = "============";
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < numberColumns; i++) {
      System.out.print(metaData.getColumnName(i + 1"\t");
      sb.append(separator);
    }
    System.out.println("\n" + sb.toString());
    while (rs.next()) {
      for (int i = 0; i < numberColumns; i++) {
        System.out.print(rs.getString(i + 1"\t");
      }
      System.out.println("\n");
    }
  }

}

Transaktionen

Datenbankzugriffe werden in sogenannte Transaktion zerlegt, die dafür Sorge tragen, dass parallele Zugriffe auf die Datenbank möglich sind, ohne dass die Datenbank inkonsistent wird.

JDBC unterscheidet fünf verschiedene Transaktionslevels:

     

  1. TRANSACTION_NONE TODO Beschreibungen einfügen.
  2. TRANSACTION_READ_UNCOMMITTED
  3. TRANSACTION_READ_COMMITTED
  4. TRANSACTION_REPEATABLE_READ
  5. TRANSACTION_SERIALIZABLE

Das Transaktionslevel ist auf der Connection zu definieren:

 

connection.setTransactionIsolation
(Connection.TRANSACTION_READ_COMMITTED);

 

Falls man die Transaktionsgrenzen selbst festlegen will, muss man autocommit abschalten.

 

connection.setAutoCommit(false);

 

Das Transaktionsende wird dann mit

 

connection.rollback();

 

bzw.

 

connection.commit();

 

bestimmt. Möchte man nur bis zu einem freiwählbaren Punkt zurückkehren, können Savepoints definiert werden. Beispiel:

 

statement.executeUpdate(STATEMENT_1);
Savepoint sp1 = connection.setSavepoint("Savepoint1");
statement.executeUpdate(STATEMENT_2);
statement.rollback(sp1);

 

In diesem Beispiel würde das erste Update-Statement erhalten bleiben, während die Änderungen durch das zweite verworfen würden.