Java Standard: JDBC
Rechtliches
[Bearbeiten]Java™ und JDBC™ sind Trademarks der Firma Sun Microsystems. Auch andere verwendete Soft- oder Hardwarenamen können, ohne besonders gekennzeichnet zu sein, eingetragene Markenbezeichnungen sein und sollen auch als solche betrachtet werden.
Einleitung
[Bearbeiten]JDBC ist die Abkürzung für Java Database Connectivity. Die JDBC-API ermöglicht den Zugriff auf RDBMS (relationale Datenbankmanagementsysteme) mittels Java und SQL.
Die JDBC-API residiert in folgenden Packages:
java.sql
javax.sql
(erweitert JDBC um Server-Side-Möglichkeiten)
Datenbanktreiber
[Bearbeiten]RDBMS gibt es fast so viele wie Sandkörner am Strand. Jedes weist seine Besonderheiten auf und unterstützt verschiedene SQL-Versionen mit herstellerspezifischen Einschränkungen oder Erweiterungen.
Um nun die Möglichkeit zu schaffen diese Datenbanksysteme mittels eines einheitlichen Interfaces, nämlich der JDBC-API, ansprechen zu können, bedarf es noch mindestens einer Zwischenschicht. Diese Zwischenschicht oder zumindest einen Teil davon stellen die JDBC-Datenbanktreiber dar.
Typen von Datenbanktreibern
[Bearbeiten]- Typ 1: JDBC-ODBC-Bridge
- Typ 2: Setzt auf einen herstellerspezifischen Treiber auf
- Typ 3: JDBC-Netzwerktreiber
- Typ 4: Nativer JDBC-Treiber
Wie diese Treibertypen genau funktionieren soll hier nicht weiter interessieren.
Prinzipiell kann aber folgendes gesagt werden: Je höher die Typnummer, desto besser und effizienter die Datenbankanbindung. Ein Typ 4-Treiber ist, falls für das jeweilige Datenbanksystem vorhanden, den anderen Treibertypen normalerweise vorzuziehen.
Ein JDBC-ODBC-Bridge-Treiber (= Typ 1-Treiber) ist in den aktuellen Java-Releases enthalten. Sun Microsystems weist aber ausdrücklich darauf hin, dass dieser nur für experimentelle Zwecke verwendet werden soll oder wenn keine anderen Treiber für die Datenbank verfügbar sind.
Sun Microsystems listet unter [1] viele verfügbare JDBC-Datenbanktreiber.
Einen Datenbanktreiber laden
[Bearbeiten]Alle nachfolgenden Beispiele beziehen sich auf das RDBMS PostgreSQL 8.0 und den JDBC-Treiber "8.0-312 JDBC3". Dieser Typ 4-Treiber ist unter [2] als Datei "postgresql-8.0-312.jdbc3.jar" verfügbar. Diese JAR-Datei muss natürlich für ein funktionsfähiges Programm in den CLASSPATH eingebunden werden.
try
{
Class.forName("org.postgresql.Driver");
}
catch(ClassNotFoundException e)
{
// ...
}
Der Klassenname ist treiberabhängig. Suns JDBC-ODBC-Bridge würde mittels sun.jdbc.odbc.JdbcOdbcDriver
angesprochen. In der Regel finden Sie diesen in der Dokumentation zum entsprechenden Treiber des Datenbankherstellers.
Der Treibermanager
[Bearbeiten]Bei fehlerfreier Abarbeitung des obigen Codefragmentes ist der Treiber nun beim Treibermanager (Klasse DriverManager
) angemeldet.
Folgendes Programmstück schreibt die Aktivitäten des Treibermanagers und der Treiber in die Log-Datei test.log
.
try
{
DriverManager.setLogWriter(new PrintWriter(new File("test.log")));
Class.forName("org.postgresql.Driver");
}
catch(ClassNotFoundException e)
{
// ...
}
catch(FileNotFoundException e)
{
// ...
}
Alternativ zur forName
-Methode kann ein Datenbanktreiber auch so geladen werden
try
{
Driver driver = new org.postgresql.Driver();
DriverManager.registerDriver(driver);
}
catch(SQLException e)
{
// ...
}
Auch die Verbindung zu einer konkreten Datenbank wird durch den Treibermanager hergestellt. Dazu mehr im nächsten Abschnitt.
Datenbankverbindung
[Bearbeiten]Wir wollen nun eine Verbindung mit der PostgreSQL-Datenbank testdb
herstellen. Die Datenbank ist am
lokalen Rechner (localhost) abgelegt und es wird der Standardport 5432 verwendet. Der Datenbankbenutzer
soll user
mit dem Passwort password
sein.
try
{
Class.forName("org.postgresql.Driver");
Connection conn = DriverManager.getConnection("jdbc:postgresql:testdb", "user", "password");
conn.close();
}
catch(ClassNotFoundException e)
{
// ...
}
catch(SQLException e)
{
// ...
}
Die getConnection()-Methode der Klasse DriverManager gibt es mit verschiedenen Signaturen. Der erste Parameter ist immer erforderlich. Dabei handelt es sich um die sogenannte URL (Uniform Ressource Locator) der Datenbank. Diese URL beginnt immer mit jdbc:, dann folgt die Datenbankprotokoll-/Treiberidentifikation und abschließend noch die Angabe der Datenquelle. Die genaue Syntax dieser URL ist treiberabhängig. Im Anwendungsfall also immer das maßgebliche RDBMS-Handbuch und/oder die Treiberdokumentation konsultieren.
Die Verbindung zur Datenbank kann mittels close() wieder geschlossen werden.
Datenbankabfragen
[Bearbeiten]Beispieldatenbank
[Bearbeiten]Die Datenbank testdb enthalte eine Tabelle (Relation) leute mit der Tabellenstruktur
Spaltenbezeichnung | SQL-Datentyp | PostgreSQL-Datentyp | Anmerkung |
---|---|---|---|
id | INTEGER | INTEGER | PRIMARY KEY |
vorname | VARCHAR(15) | VARCHAR(15) | |
nachname | TEXT | ||
geburtsdatum | DATE | DATE |
und dem Inhalt
id | vorname | nachname | geburtsdatum |
---|---|---|---|
1 | Hansi | Hubsi | 1999-12-01 |
2 | Willy | Wupp | 1964-02-22 |
3 | Struppi | Strupp | 2001-03-27 |
4 | Milly | Miker | 1948-11-08 |
Statement und ResultSet
[Bearbeiten]Alle JDBC-Datenbankabfragen setzen auf Statement-Interfaces auf.
Statement
[Bearbeiten]Die Schnittstelle Statement kann für alle einfachen SQL-Abfragen verwendet werden.
try { Class.forName("org.postgresql.Driver"); Connection conn = DriverManager.getConnection("jdbc:postgresql:testdb", "user", "password"); Statement statement = conn.createStatement(); statement.close(); conn.close(); } catch(ClassNotFoundException e) { // ... } catch(SQLException e) { // ... }
ResultSet
[Bearbeiten]Damit man an die Daten in der Datenbank herankommt, führt man die Statement-Methode executeQuery() aus. Diese Methode retourniert eine ResultSet-Instanz mit den angeforderten Daten.
try { Class.forName("org.postgresql.Driver"); Connection conn = DriverManager.getConnection("jdbc:postgresql:testdb", "user", "password"); Statement statement = conn.createStatement(); ResultSet rset = statement.executeQuery("SELECT * FROM leute;"); rset.close(); statement.close(); conn.close(); } catch(ClassNotFoundException e) { // ... } catch(SQLException e) { // ... }
Hinweise
[Bearbeiten]- Pro Statement-Instanz kann jeweils nur eine ResultSet-Instanz existieren.
- Der übergebene SQL-String muss natürlich SQL-konform, sinnvoll und korrekt sein. Übergibt man Datenmüll, so bekommt man auch nur Fehlermeldungen oder Unsinn zurück.
- Gefährlich: Man muss keinen SELECT-String übergeben. Man kann z.B. auch einen INSERT-String übergeben. Man erhält dann zwar eine Fehlermeldung, der Datensatz wurde aber trotzdem eingefügt.
Daten aus ResultSet herausholen
[Bearbeiten]In der ResultSet-Instanz sind nun die vom Datenbanksystem als Reaktion auf unsere Anfrage zurückgegebenen Daten gespeichert.
Die einzelnen Tupel kann man mit der ResultSet-Methode next() durchlaufen. Damit der Datensatzcursor auf das erste Tupel zeigt, muss next() einmal aufgerufen werden.
Um an die einzelnen Datenwerte des Tupels heranzukommen, liefert ResultSet verschiedene getter-Methoden (hier nur auszugsweise dargestellt):
int getInt(int columnIndex) |
int getInt(String columnName) |
String getString(int columnIndex) |
String getString(String columnName) |
Date getDate(int columnIndex) |
Date getDate(String columnName) |
Die Identifikation der gewünschten Spalte erfolgt mittels Index oder direkt durch Angabe der Spaltenbezeichnung. Bei Angabe der Spaltennummer ist zu beachten, dass der Index bei 1 beginnt (Spalte1 = 1). Weiters kann man durch die SQL-Abfrage die Tabellenspalten durcheinanderwürfeln. Das muss bei der Indexmethode zusätzlich berücksichtigt werden.
try { Class.forName("org.postgresql.Driver"); Connection conn = DriverManager.getConnection("jdbc:postgresql:testdb", "user", "password"); Statement statement = conn.createStatement(); ResultSet rset = statement.executeQuery("SELECT * FROM leute;"); while(rset.next()) { System.out.println(rset.getInt("id") + "\t" + rset.getString("vorname") + "\t" + rset.getString("nachname") + "\t" + rset.getDate(4)); } rset.close(); statement.close(); conn.close(); } catch(ClassNotFoundException e) { // ... } catch(SQLException e) { // ... }
liefert wie erwartet als Ergebnis
1 Hansi Hubsi 1999-12-01 2 Willy Wupp 1964-02-22 3 Struppi Strupp 2001-03-27 4 Milly Miker 1948-11-08
NULL-Check
[Bearbeiten]- Bei getXXX()-Methoden, die als Ergebnis ein Objekt zurückliefern, kann man einen NULL-Wert durch den Rückgabewert null erkennen.
- Ansonsten erfolgt die Überprüfung eines Datenwertes auf NULL mit der wasNull()-Methode
try { Class.forName("org.postgresql.Driver"); Connection conn = DriverManager.getConnection("jdbc:postgresql:testdb", "user", "password"); Statement statement = conn.createStatement(); ResultSet rs = statement.executeQuery("SELECT * FROM leute;"); rs.next(); rs.getInt(1); System.out.println("Ist Datenwert NULL? " + rs.wasNull()); rs.close(); statement.close(); conn.close(); } catch(ClassNotFoundException e) { // ... } catch(SQLException e) { // ... }
liefert
Ist Datenwert NULL? false
Scrollable ResultSet
[Bearbeiten](ab JDBC 2.0)
Eine komfortable Datensatznavigation ermöglichen folgende Connection-Methoden:
Statement createStatement(int resultSetType, int resultSetConcurrency); |
Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability); |
mit
resultSetType:
- ResultSet.TYPE_FORWARD_ONLY
- ResultSet.TYPE_SCROLL_INSENSITIVE
- ResultSet.TYPE_SCROLL_SENSITIVE
resultSetConcurrency:
- ResultSet.CONCUR_READ_ONLY
- ResultSet.CONCUR_UPDATABLE
resultSetHoldability:
- ResultSet.HOLD_CURSORS_OVER_COMMIT
- ResultSet.CLOSE_CURSORS_AT_COMMIT
Die genaue Bedeutung dieser Parameter soll hier nicht erläutert werden. Für ein scrollbares ResultSet ist der Parameter resultSetType auf ResultSet.TYPE_SCROLL_INSENSITIVE oder ResultSet.TYPE_SCROLL_SENSITIVE zu setzen.
Schon kann der Datensatzcursor fast beliebig positioniert werden:
next() | Bewegt den Datensatzcursor zum nächsten Datensatz |
previous() | Bewegt den Datensatzcursor zum vorherigen Datensatz |
first() | Bewegt den Datensatzcursor zum ersten Datensatz |
last() | Bewegt den Datensatzcursor zum letzten Datensatz |
afterLast() | Bewegt den Datensatzcursor hinter den letzten Datensatz |
beforeFirst() | Bewegt den Datensatzcursor vor den ersten Datensatz |
absolute(int n) | Bewegt den Datensatzcursor auf den n-ten Datensatz |
relative(int n) | Bewegt den Datensatzcursor relativ zur momentanen Position |
try { Class.forName("org.postgresql.Driver"); Connection conn = DriverManager.getConnection("jdbc:postgresql:testdb", "user", "password"); Statement statement = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = statement.executeQuery("SELECT * FROM leute"); // Erster Datensatz rs.next(); System.out.println(rs.getInt("id") + "\t" + rs.getString("vorname") + "\t" + rs.getString("nachname") + "\t" + rs.getDate(4)); // Letzter Datensatz rs.afterLast(); rs.previous(); System.out.println(rs.getInt("id") + "\t" + rs.getString("vorname") + "\t" + rs.getString("nachname") + "\t" + rs.getDate(4)); // 2. Datensatz rs.absolute(2); System.out.println(rs.getInt("id") + "\t" + rs.getString("vorname") + "\t" + rs.getString("nachname") + "\t" + rs.getDate(4)); // 1. Datensatz rs.relative(-1); System.out.println(rs.getInt("id") + "\t" + rs.getString("vorname") + "\t" + rs.getString("nachname") + "\t" + rs.getDate(4)); // Letzter Datensatz rs.absolute(-1); System.out.println(rs.getInt("id") + "\t" + rs.getString("vorname") + "\t" + rs.getString("nachname") + "\t" + rs.getDate(4)); System.out.println("Tuple = " + rs.getRow()); rs.close(); statement.close(); conn.close(); } catch(ClassNotFoundException e) { System.out.println("Class Error"); } catch(SQLException e) { e.printStackTrace(); }
liefert
1 Hansi Hubsi 1999-12-01 4 Milly Miker 1948-11-08 2 Willy Wupp 1964-02-22 1 Hansi Hubsi 1999-12-01 4 Milly Miker 1948-11-08 Tuple = 4
PreparedStatement
[Bearbeiten]Will man SQL-Abfragen parametrisieren, dann ist die Schnittstelle PreparedStatement genau richtig. PreparedStatement ist ein Subinterface von Statement.
try { Class.forName("org.postgresql.Driver"); Connection conn = DriverManager.getConnection("jdbc:postgresql:testdb", "user", "password"); PreparedStatement statement = conn.prepareStatement( "SELECT * FROM leute WHERE vorname LIKE ?;", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); statement.setString(1, "Milly"); ResultSet rs = statement.executeQuery(); while(rs.next()) { System.out.println(rs.getInt("id") + "\t" + rs.getString("vorname") + "\t" + rs.getString("nachname") + "\t" + rs.getDate(4)); } rs.close(); statement.close(); conn.close(); } catch(ClassNotFoundException e){ // ... } catch(SQLException e) { // ... }
liefert
4 Milly Miker 1948-11-08
Darüber hinaus speichert die Datenbank alle Prepared Statements (genauer gesagt den berechneten Execution Plan) um es bei nachfolgenden Ausführungen zu beschleunigen.
Datenbankmanipulation
[Bearbeiten]JDBC-Datenbankmanipulationen setzen auf den bereits bekannten Statement-Interfaces auf.
executeUpdate()
[Bearbeiten]Die Statement-Anweisung
int executeUpdate(String sql)
ist für die Ausführung von INSERT-, UPDATE-, DELETE- oder DDL-Anweisungen gedacht.
try { Class.forName("org.postgresql.Driver"); Connection conn = DriverManager.getConnection("jdbc:postgresql:testdb", "user", "password"); Statement statement = conn.createStatement(); int result = statement.executeUpdate("INSERT INTO leute VALUES(5, 'Trude', 'Trudscherl', '2005-08-18');"); System.out.println("Eingefügte Datensätze: " + result); statement.close(); conn.close(); } catch(ClassNotFoundException e) { // ... } catch(SQLException e) { // ... }
Updateable ResultSet
[Bearbeiten]Wir haben bereits die Realisierung scrollbarer ResultSets unter Zuhilfenahme der Connection- Methode
Statement createStatement(int resultSetType, int resultSetConcurrency)
kennengelernt.
Setzen wir darin den zweiten Parameter auf ResultSet.CONCUR_UPDATEABLE, so können wir auch auf diese Weise Datensätze einfügen, löschen oder einzelne Datenwerte ändern, wenn für die Tabelle ein Primärschlüssel vorhanden ist.
try { Class.forName("org.postgresql.Driver"); Connection conn = DriverManager.getConnection("jdbc:postgresql:testdb", "user", "password"); Statement statement = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = statement.executeQuery("SELECT * FROM leute;"); // Den Nachnamen von Trude Trudscherl ändern rs.last(); rs.updateString("nachname", "Tridscherl"); rs.updateRow(); rs.close(); statement.close(); conn.close(); } catch(ClassNotFoundException e) { // ... } catch(SQLException e) { // ... }
Datensätze kann man mit der ResultSet-Methode void insertRow() einfügen und mit void deleteRow() löschen.
Transaktionen
[Bearbeiten]Auch Transaktionen lassen sich mit Hilfe der Connection-Methoden
void setAutoCommit(boolean autoCommit) |
void commit() |
void rollback() |
void rollback(Savepoint savepoint) |
nachbilden.
Standardmäßig arbeitet JDBC im AutoCommit-Modus.
Als Beispiel wollen wir nun einige Datensätze "in einem Rutsch" in die Tabelle leute einfügen. Die Betonung liegt auf dem Wörtchen "wollen".
try { Class.forName("org.postgresql.Driver"); Connection conn = DriverManager.getConnection("jdbc:postgresql:testdb", "user", "password"); conn.setAutoCommit(false); Statement statement = conn.createStatement(); statement.executeUpdate("INSERT INTO leute VALUES(11, 'Andi', 'Handi', '1999-02-04')"); statement.executeUpdate("INSERT INTO leute VALUES(12, 'Bandi', 'Handi', '1999-02-04')"); statement.executeUpdate("INSERT INTO leute VALUES(13, 'Candi', 'Handi', '1999-02-04')"); statement.executeUpdate("INSERT INTO leute VALUES(14, 'Dandi', 'Handi', '1999-02-04')"); statement.executeUpdate("INSERT INTO leute VALUES(14, 'Eandi', 'Handi', '1999-02-04')"); conn.commit(); conn.setAutoCommit(true); statement.close(); conn.close(); } catch(ClassNotFoundException e) { // ... } catch(SQLException e) { // eventuell ein explizites Rollback durchführen. PostgreSQL führt // automatisch intern ein Rollback durch wenn kein commit erfolgt. }
Was geschieht bei der Ausführung dieses Codes? Die letzte INSERT-Anweisung verletzt die Bedingung für einen Primärschlüssel, somit wird eine SQLException geworfen. Die commit()-Anweisung wird nie aufgerufen. Allfällige Änderungen werden durch ein Rollback wieder rückgängig gemacht. Die Tabelle bleibt konsistent im ursprünglichen Zustand erhalten.
Alternativ können Transaktionen natürlich auch ohne die JDBC-Transaktionsmethoden komplett mittels SQL-Code übergeben werden.
statement.executeUpdate("BEGIN TRANSACTION"); statement.executeUpdate("INSERT INTO leute VALUES(11, 'Andi', 'Handi', '1999-02-04')"); // ... statement.executeUpdate("COMMIT");
Batch-Updates
[Bearbeiten](ab JDBC 2.0)
Sind viele Daten auf einmal zu manipulieren, so spielt die Effizienz des Übertragungsvorganges eine große Rolle. Batch-Updates sind dann den konventionellen execute-Methoden überlegen.
try { Class.forName("org.postgresql.Driver"); Connection conn = DriverManager.getConnection("jdbc:postgresql:testdb", "user", "password"); conn.setAutoCommit(false); Statement statement = conn.createStatement(); statement.addBatch("INSERT INTO leute VALUES(11, 'Andi', 'Handi', '1999-02-04')"); statement.addBatch("INSERT INTO leute VALUES(12, 'Bandi', 'Handi', '1999-02-04')"); statement.addBatch("INSERT INTO leute VALUES(13, 'Candi', 'Handi', '1999-02-04')"); statement.addBatch("INSERT INTO leute VALUES(14, 'Dandi', 'Handi', '1999-02-04')"); statement.addBatch("INSERT INTO leute VALUES(15, 'Eandi', 'Handi', '1999-02-04')"); statement.executeBatch(); conn.commit(); conn.setAutoCommit(true); statement.close(); conn.close(); } catch(ClassNotFoundException e) { // ... } catch(SQLException e) { // eventuell ein Rollback durchführen. PostgreSQL führt automatisch ein Rollback durch, wenn // kein commit erfolgt }
Metadaten
[Bearbeiten]Die Struktur der in einer Datenbank gespeicherten Daten wird durch die so genannten Metadaten repräsentiert.
Datenbank-Metadaten
[Bearbeiten]try { Class.forName("org.postgresql.Driver"); Connection conn = DriverManager.getConnection("jdbc:postgresql:testdb", "user", "password"); DatabaseMetaData dbmd = conn.getMetaData(); System.out.println("RDBMS = " + dbmd.getDatabaseProductName()); System.out.println("Version = " + dbmd.getDatabaseProductVersion()); System.out.println("Treiber = " + dbmd.getDriverName()); System.out.println("Version = " + dbmd.getDriverVersion()); System.out.println("Datenbank-URL = " + dbmd.getURL()); // usw. conn.close(); } catch(ClassNotFoundException e) { // ... } catch(SQLException e) { // ... }
liefert
RDBMS = PostgreSQL Version = 8.0.3 Treiber = PostgreSQL Native Driver Version = PostgreSQL 8.0 JDBC3 with SSL (build 312) Datenbank-URL = jdbc:postgresql:testdb
Tabellen-Metadaten
[Bearbeiten]try { Class.forName("org.postgresql.Driver"); Connection conn = DriverManager.getConnection("jdbc:postgresql:testdb", "user", "password"); Statement statement = conn.createStatement(); ResultSet rs = statement.executeQuery("SELECT * FROM leute;"); ResultSetMetaData rsmd = rs.getMetaData(); System.out.println("Anzahl der Tabellenspalten = " + rsmd.getColumnCount()); System.out.println("Spaltenname der Spalte 2 = " + rsmd.getColumnName(2)); System.out.println("Typ der Spalte 2 = " + rsmd.getColumnType(2)); // usw. rs.close(); statement.close(); conn.close(); } catch(ClassNotFoundException e) { // ... } catch(SQLException e) { // ... }
liefert
Anzahl der Tabellenspalten = 4 Spaltenname der Spalte 2 = vorname Typ der Spalte = 12
Ausblick
[Bearbeiten]Im Rahmen dieses Konvoluts konnte das komplexe Thema JDBC nur an der Oberfläche angekratzt werden. Interessierte werden zwecks weiterführender Information auf die Java-API-, JDBC-API-Dokumentation und die Sun-Tutorials (in englischer Sprache) verwiesen. Die API-Dokumentationen und die Sun-JDBC-Tutorials können unter Oracle Java-Homepage gefunden werden.
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
String path = "C:/example.mdb";
String dsn = "jdbc:odbc:Driver={Microsoft Access Driver (*.mdb)};DBQ=" + path + ";UID=admin";
accessConn = DriverManager.getConnection(dsn, "", "");
try {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/MySite?user=MyAccount&password=MyPassword");
conn.close();
} catch(SQLException e) { e.printStackTrace(); }
import java.sql.*;
import java.io.*;
import oracle.jdbc.*;
import oracle.sql.*;
public class OracleDatabase {
public static void main(String[] args) {
try {
DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:MyDatabase", "MyAccount", "MyPassword");
conn.setAutoCommit(true);
String sql = "SELECT sum(bytes) from dba_segments;";
Statement stmt = conn.createStatement();
stmt.execute(sql);
stmt.close();
conn.close();
} catch(SQLException e) { e.printStackTrace(); }
}
}