Datenbankprogrammierung
 

Axel Kohnert
Lehrstuhl Mathematik 2
WS 2000/2001
 
 
 
 
 
 

Teil 3  JDBC

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

3.1 Einführung


Mit JDBC wird wird eine Programmierschnittstelle (API Application Programming Interface) bezeichnet die es ermöglicht aus Java Programmen mit einer Datenbank zu kommunizieren. JDBC steht für Java Database Connectivity. JDBC ist auf SQL zugeschnitten. Von den Fähigkeiten her ist JDBC mit embedded SQL für C vergleichbar. JDBC ist mit der ODBC Schnittstelle unter Windows vergleichbar. Die Java Schnittstelle JDBC ist fester Bestandteil der Programmiersprache JAVA. Man kann JDBC als eine Low Level API bezeichnen, da lediglich SQL Anweisungen in Java eingebunden werden. Besser (?) wäre ein direkter Zugriff auf Datenbankobjekte (Tabellen, ..) über Java Klassen.
 
 
 
 
 
 

3.2 Grundlagen


Das JDBC API besteht aus zwei Teilen, dem Paket java.sql welches Datenbank unabhängig Klassen zur Verfügung stellt um die typischen Aufgaben bei einer Datenbank programmierung zu erledigen. Dies sind

- Verbindung aufbauen
-  SQL Befehle absetzen
- Ergebnisse manipulieren

um dies zu erreichen wird mit sog. interfaces gearbeitet. Dies sind abstrakte Klassen, die erst durch Hinzunahme des zweiten Teils, den sog. Treibern implementiert werden. Die Treiber werden z.B. vom jeweiligen Hersteller der Datenbank zur Verfügung gestellt. Dadurch wird erreicht, dass JDBC Programme Datenbank unabhängig bleiben (sollten).  Die oben aufgeführten Aufgaben sind innerhalb von java.sql in 3 Klassen verteilt:

- java.sql.Connection
- java.sql.Statement
- java.sql.ResultSet

Ferner gibt es noch eine vierte Klasse zur Verwaltung von Treibern:

- java.sql.DriverManager
 
 
 

3.3 Erstes Beispiel


import java.sql.*;
 

public class BSP1
{
        public static void main(String[] argv)
        {
                try
                {

                        String url = "jdbc:yardjdbc://btm2xq:1142/sample/beispiel";
                        Class.forName ("yardjdbc.YardDriver");
                        Connection con = DriverManager.getConnection(url,"DATA12","data12");
                        doselect(con,"select * from primzahlen where zahl<10000");
                }
                catch (ClassNotFoundException e)
               {
                        System.err.println("Class not found");
                        System.exit(1);
                }
                catch (SQLException e)
                {
                        dberror("main",e);
                        System.exit(1);
                }
        }

        /*
        **      perform a SELECT statement
        */

        static void doselect(Connection con, String stmt)
        {
                int ret;

                try
               {
                        Statement st = con.createStatement();
                        ResultSet c = st.executeQuery(stmt);

                        dumpResultSet(c);
                }
               catch (SQLException e)
                {
                        dberror("doselect("+stmt+")",e);
                        System.exit(0);
               }
        }

        /*
        **      perform a non SELECT statement
        */

        static void dosql(Connection con, String stmt)
        {
                int ret;

                try
                {
                        Statement st = con.createStatement();

                        ret = st.executeUpdate(stmt);
                }
                catch (SQLException e)
               {
                        dberror("dosql("+stmt+")",e);
                        System.exit(0);
                }
        }

       static void dumpResultSet(ResultSet rs) throws SQLException
        {
                ResultSetMetaData md = rs.getMetaData();
                int i, cols = md.getColumnCount();
                int row = 0;

                for (i=0;i<cols;i++)
                        System.out.println("Column " + i + ": " +
                                md.getColumnName(i+1));
                while (rs.next())
                {
                        System.out.print(row+++": ");
                        for (i=0;i<md.getColumnCount();i++)
                                System.out.print(rs.getString(i+1)+" ");
                        System.out.println();
                }
        }

        /*
        **      Report the SQLCODE if an error occurs
        */

        static void dberror(String loc, SQLException e)
       {
                System.err.println(loc + ": ");
                e.printStackTrace();
                do
                {
                       System.err.println(e.getErrorCode()+
                                "("+e.getSQLState()+
                                ")"+": "+e.getMessage());
                        e = e.getNextException();
                }
                while (e != null);
        }
}
 
 

Der erste Schritt ist das explite Laden des Treibers, dies ist nötig um die Funktionen aus java.sql mit Leben zu Füllen. Dies geschieht mit dem Aufruf
 

    Class.forName ("yardjdbc.YardDriver");
 

Dazu muss natürlich die entsprechende Klasse gefunden werden (CLASSPATH). Danach wird mit dem Datenbanksystem eine Verbindung aufgebaut. Dazu gibt es das Connection Objekt.  Zum Anmelden bei der Datenbank gibt es ein sog. JDBC URL bestehend aus Treiberspezifikation und DatenbankSystem und Datenbank. Ausser dem URL muss noch Benutzer und Passwort angegeben werden. Das Connection Objekt welches man erhält ist im nächsten Schritt wichtig wenn man über diese Verbindung Befehle an die Datenbank schickt. Da SELECT Befehle im Gegensatz zu anderen SQL Befehlen ein Ergenis erzeugen wird dies in der Routine doselect abgehandelt. Egal um welchen Befehl es sich handelt wird die Anfrage von einem Statement Objekt erledigt.
 

  Statement st = con.createStatement();
 

Dabei wird von der zuvor erzeugten Verbindung ausgegangen. Mit dem Befehl
 

  ResultSet c = st.executeQuery(stmt);
 

erhält man das dritte Objekt, das Ergebnis des SELECT Befehls ein ResultSet Objekt. Die Navigation innerhalb des ResultSet Objekts geschieht wie mit einem Cursor (auch schon aus Embedded SQL für C bekannt). Dies geschieht in der Routine dumpResultSet. Um Informationen über das Ergebnis zu bekommen bedient man sich der der Klasse
 

ResultSetMetaData,
 

die Informationen über das ResultSet enthält, so die Anzahl der Spalten
 

getColumnCount()
 

und die Namen
 

getColumnName()
 

der Spalten. Die Zeilen des Ergebnis können nur mittels der Methode
 

next()
 

durchlaufen werden, die solange true liefert wie noch eine Zeile gelesen werden kann. Die Spalten werden mit 1,2... durchnummeriert, und es geibt verschiedene Methoden
 

getString(), getDouble(), ..
 

um aus den Spalten innerhalb der aktuellen Zeile zu lesen. Im Beispiel werden die Einträge als Strings ausgegeben. Man kann Statement und ResultSet explizit Löschen mit der Methode close() oder dies passiert automatisch am Ende der Lebenszeit der Objekte. Die Fehlerbehandlung passiert mit einem Objekt der Klasse
 

SQLException
 

die mittels try / catch abgefangen werden können. Im Beispielprogramm wird dazu die Routine dberror() aufgerufen. Die zweite Routine dosql() wird verwendet wenn ein nonSELECT Befehl ausgeführt werden soll. Dafür gibt es die Methode
 

executeUpdate()
 

für ein Statement Objekt. Im Falle eines wirklichen UPDATE/INSERT/DELETE liefert es die Anzahl der betroffenen Zeilen. Im Falle von DDL Befehlen (CREATE TABLE,..) den Wert 0. Es ist noch zu bemerken, dass ein paar wenige SQL Befehle, nämlich für die Transaktionssteuerung innerhalb der Klasse Connection erledigt werden, und nicht über ein Statement Objekt.
 
 
 
 

3.4 Treiber und Connection


Der erste Schritt ist immer das Anmelden eines Treibers, dies geschieht mit der Klasse:
 

java.sql.DriverManager
 

die Methoden im Zusammenhang mit Treibern zur Verfügung stellt. Die Methoden sind statisch, da nur ein Treibermanager existieren darf. Folgendes wird ermöglicht:
 

- Registrieren und Laden von Treibern
- Herstellung einer Verbindung zu einer Datenbank
- Konfigurieren der Verbindung
 

Zum Laden gibt es zwei Möglichkeiten:

explizit wie im Beispiel mit dem Aufruf Class.forName, oder beim Aufruf über die System Eigenschaft sql.drivers, d.h. der Aufruf lautet

java -Dsql.drivers=....
 

Es gibt auch Situation wo man direkt mit dem Treiber arbeiten muss. Man erhält den Treiber mit der Methode
 

static Driver getDriver(String url) throws SQLException
 

aus der Klasse DriverManager. Mit einem Driver Objekt kann man überprüfen ob diese Version JDBC konform ist:
 

boolean jdbcCompilant()
 

oder man kann die Version überprüfen
 

int getMajorVersion()
int getMinorVersion()
 

und man kann auch explizit einen Treiber wieder abmelden
 

void deregisterDriver()
 

das korrespondierende registerDriver wird automatisch beim Laden gemacht. Eine letzte Verwendung von DriverPropertyInfo Objekten, dies funktioniert prinzipiell mit allen Treibern, nicht nur JDBC Treibern
 

DriverPropertyInfo[] getPropertyInfo (String url, ..) throws SQLException
 

und ein DriverPropertyInfo Objekt hat folgende Attribute

name
value
description
required                        boolean
choices                        ein array mit möglichen Werten
 

und man kann die Informationen wie folgt abfragen:

  try
                {
                        String url = "...";
                        Class.forName (url);
                        Driver theDriver = DriverManager.getDriver(url);
                        DriverPropertyInfo[] props =
                                theDriver.getPropertyInfo(url,null);
                        for (int i=0;i<props.length;i++)
                                if (props[i].required)
                                        System.out.println(props[i].name + ":"+
                                                props[i].description);
                }
 
 
 

Verbindung zur Datenbank
 

Nach dem Laden des Treibers ist der nächste Schritt der Aufbau der Verbindung zur Datenbank. Die Methoden hierfür werden vom DriverManager zur Verfügung gestellt:
 

Connection getConnection(String URL) throws SQLException
 

haben wir im Beispiel schon kennengelernt. Ein URL (uniform resource locator) zum Aufbau einer Verbindung ist wie folgt aufgebaut
 

jdbc:<Protokoll>:<Datenbank>
 

das Protokoll ist treiberspezifisch und der dritte Teil dient zur Auswahl der Datenbank, enthält also den Rechnernamen wenn es über Netz geht, legt fest welches Datenbanksystem angesprochen wird und dann eventuell die ausgewählte Database (SQL Befehl DATABASE). Im Beispiel war das Protokoll der YARDTreiber  yardjdbc, es wurde über Netz auf den Rechner btm2xq am Port 1142 zugegriffen und es wurde die database beispiel im System sample angesprochen.

String url = "jdbc:yardjdbc://btm2xq:1142/sample/beispiel";

Es kann sich beim Aufbau der Verbindung auch gleich eingeloggt werden:
 

Connection getConnection(String URL,String user, String passwd) throws SQLExceception
 

und es geht auch über properties:
 

Connection getConnection(String URL, java.util.Porperties info) throws SQLExceception
 

wie man an folgenden Codefragment sieht:
 

Properties p = new Properties();
String url = "...";
p.put("user","DATA12");
p.put("password","data12");
Connection c = DriverManager.getConnection(url,p);


Da es möglich ist mehrere Treiber zu verwenden wird beim Audfbau der Verbindung getestet bis ein passender Treiber gefunden wurde. Dazu gibt es die Methode acceptsURL in java.sql.Driver.
 

Timeouts, Tracing
 

In der Klasse DriverManager gibt es Methoden zur Manipulation der Wartezeit beim Verbindungsaufbau. Dies sind
 

getLoginTimeout()
void setLoginTimeout(int sekunden)
 

Ferner hat man die Möglichkeit in der Klasse DriverManager einen Stream zum Protokollieren der Treiberoperationen zu setzen.
 

java.io.PrintStream getLogStream()
void setLogStream(java.io.PrintStream out)
 

wird bei setLogStream der Wert null übergeben wird das Protokollieren wieder ausgeschaltet.
 
 

Connection
 

Ein Connection Objekt repräsentiert eine Verbindung zu einer Datenbank. Es kann davon mehrere geben. Erzeugt wird ein Connection Objekt mit der Methode getConnection aus der DriverManager Klasse. Das Connection wird hauptsächlich dazu verwendet SQL Befehle an die Datenbank zu senden. In der Connection Klasse git es die Methoden
 

setCatalog(String Catalog) throws SQLException
String getCatalog() throws SQLException
 

damit kann zwischen verschiedenen 'Katalogen' gewechselt werden. Unter Katalog versteht man ein Organisationseinheit oberhalb von DATABASE und wird selten verwendet. Das Methodenpaar
 

setReadOnly(boolen readOnly) throws SQLException
boolean isReadOnly() throws SQLException
 

dient dazu eine Connection  auf readonly zu schalten, das bedeutet das hierüber keine Schreiboperationen an der Datenbank möglich sind. Zum Beispiel kann es so möglich sein die SQL Operationen anders zu optimieren.   Um eine Verbindung zu beenden gibt es
 

void close() throws SQLException
boolean isClosed() throws SQLException
 

diese Methode wird automatisch am Ende der Lebenszeit eines Conneciton Objekts aufgerufen. Es wird aber empfohlen es stets selber zu tun. Dies setzt auch resourcen auf Seiten des Datenbanksystems frei.
 
 

Transaktionen
 

Auch die Befehle zur Transaktionssteuerung sind als Methoden in der Klasse Connection angesiedelt.
 

void commit() throws SQLException
void rollback() throws SQLException
 

Wird ein neues Connection Objekt erzeugt ist dies im soge. auto-commit-Modus, das bedeutet nach jeder Anweisung wird automatisch ein Commit durchgeführt (anders als bisher vorgestellt). Dies kann man ändern mit den Methoden
 

void setAutoCommit (boolean enable) throws SQLException
boolean get AutoCommit() throws SQLException
 

Nach dem Ändern dieser Einstellung kann man erst wirkliche Transaktionsverarbeitung unter JDBC machen. Speziell ist es also in der default Einstellung nicht möglich SQL Befehle in JDBC rückgängig zu machen.

Ein Beispielprogrammausschnitt:

 
try {
    stmt = c.createStatement();
    c.setAutoCommit(false);
    stmt.executeUpdate(..);
    stmt.executeUpdate(..):
    stmt.executeUpdate();
    c.commit();
} catch (SQLException e) {
    c.rollback();
    throw e;
}
wenn nun während der drei SQL Befehle ein Fehler auftritt (es wird SQLException geworfen) wird ein rollback gemacht und der Fehler weitergereicht. D.h. die drei Befehle werden als Transaktion durchgeführt.
 

ACID
 

Bei den Transaktionen gibt es einen Standard und eine Theorie dahinter. Eine Transaktion ist definiert als eine Folge von Aktionen die die Datenbank  von einen definierten Zustand in einen neuen definierten Zustand überführt. Dabei soll das ACID Prinzip gelten:

 
Atomicity: d.h. ununterbrechbar, es kann nicht passieren dass eine Transaktion nur
                       teilweise ausgeführt wird
Consistency: d.h. nach einer Transaktion ist die Datenbank vernünftig
Isolation: es wird der isolierte Ablauf verlangt, d.h. es ist egal ob eine Transaktion einzeln
                        oder parallel mit anderen läuft. Man spricht auch von serializable, d.h. das
                        Ergebnis muss gleich sein egal in welcher Reihenfolge die Transaktionen
                       ablaufen.
Durability: nach der Transaktion muss das Ergebnis sicher sein.


dies sind Wünsche an eine Transaktion, SQL Datenbank erlauben die Dinge aus Laufzeitgründen lockerer zu handhaben. Es gibt verschiedenen Isolationsebenen die diese Restriktionen aufheben.
 

read uncommitted: erlaubt bei nur Lese Transaktionen den Zugriff auf noch nicht geschriebene Daten (dirty read)
read committed: erlaubt nur das Lesen von endgültig geschriebenen Daten (commited), man hat aber noch das Problem der non repeatable reads.
repeatable reads: dieses Problem taucht auf, wenn zwei Transaktionen das gleiche Objekt brauchen. Die erste liest es, die zweite ändert es, die erste liest es nochmal, und jetzt ist es aber geändert, ein non repeatable read. Dies wird auf diesem Isolationslevel verhindert. Es gibt aber immer noch das Phantomproblem.
serializable: Damit wird wirklich erreicht, das sich zwei Transaktionen in die Quere kommen. Ein Phantom ist ein Objekt das existieren kann, aber nicht während der Gesamtdauer der Transaktion da ist. Wenn z.B. ein COUNT gemacht wird und während dieser Transaktion ein eine andere Transaktion ein weiteres passendes Objekt einfügt.
 

Um diese Isolationsebenen (d.h. die Transaktionen sind unterschiedlich isoliert voneinander) zu realisieren gibt es in der Connection Klasse:
 

static int TRANSACTION_NONE
static int TRANSACTION_READ_UNCOMMITTED
static int TRANSACTION_READ_COMMITTED
static int TRANSACTION_REPEATABLE_READ
static int TRANSACTION_SERIALIZABLE

void setTransactionIsolation(int level) throws SQLException
int getTransactionIsolation() throws SQLException
 

das level TRANSACTION_NONE bedeutet das vom System keine Transaktionen unterstützt werden.
 

3.5. Ausführen von Anweisungen


Es gibt drei Klassen von Anweisungen

 
java.sql.Statement
java.sql.PreparedStatement
java.sql.CallableStatement
dies sind normale Anweisungen, prepared Statements und stored procedures (schon bekannt aus dem Abschnitt über embedded SQL für C).

Um diese Objekte zur Erzeugen gibt es in der Connection Klasse die Methoden:
 

Statement createStatement()  throws SQLException
PreparedStatement prepareStatement(String sql)  throws SQLException
CallableStatement prepareCall(String sql)  throws SQLException
 

Bei dem 'normalen' Statement wird der SQL Befehl erst beim Ausführen angegeben, im Gegensatz zu den beiden anderen wo bereits vorher der Befehl angegeben werden muss.
 

Ausführen von Befehlen
 

Es gibt in der Klasse Statement drei Methoden zum Ausführen eines SQL Befehls;
 

ResultSet executeQuery(String sql)  throws SQLException
int executeUpdate(String sql)  throws SQLException
boolean execute(String sql)  throws SQLException
 

die ersten beiden Methoden wurden schon im ersten Beispiel vorgestellt, executeQuery wird für ein Select verwendet, wenn eine Tabelle als Ergebnis geliefert wird. Die zweite Methode dient für DDL und DML Befehle. Bei DML ist der Rückgabewert die Anzahl der betroffenen Zeilen und im Falle von DDL ist der Rückgabewert 0.  Die dritte Methode wird verwendet wenn ein Aufruf mehrere Ergebnisse liefert, z.B. beim Aufruf einer stored procedure. Das Ergebnis ist true wenn das Ergebnis ein ResultSet ist und false wenn es ein Integerwert ist, z.b. ein Updatezähler. Man kann auf diese mit folgenden Methoden aus Statement zugreifen:
 

ResultSet getResultSet()  throws SQLException
int getUpdateCount()  throws SQLException
boolean getMoreResults() throws SQLException
 

interessant ist dabei getMoreResults() denn durch einen Rückgabewert wie bei der Methode execute() wird mitgeteilt ob noch weitere Ergebnisse anstehen. getResultSet() liefert die nächste Tabelle oder null wenn keine Tabelle mehr anliegt. getUpdateCount() liefert -1 wenn keine Zahl mehr anliegt. D.h. man hat alle Ergebnisse wenn gilt

(getMoreResults() == false) && (getUpdateCount() == -1)
 
 
 

Escape-Klauseln
 

Ein Problem bei plattformunabhängigen JDBC Programmen ist die Tatsache, dass sich die unterschiedlichen Datenbanksystemen sher wohl unterscheiden. Ein typische Beispiel ist die Datumsnotation. D.h. die JDBC Programme sind gleich, aber müssen unterschiedlich SQL Anweisungen mit z.B. executeQuery abgeschickt werden. Dazu gibt es sog. Escape Klauseln, diese werden innerhalb des SQL Befehls der z.B. an executeQuery übergeben wird eingesetzt. Diese werden vom Treiber erkannt und entsprechend umgesetzt, sodass bei der Datenbank der richtige Befehl ankommt. Eine Escape Klausel sieht wie folgt aus:

{ keyword parameter }

an Schlüsselwörten gibt es u.a.

escape Metazeichen bei LIKE
d Datum
t Zeit
call Aufruf für stored procedure
oj outer join
..
damit versteht man auch folgenden code Ausschnitt zu dem vorherigen Abschnitt mit der Mathode execute()..
 
 
CallableStatement  stmt= c.prepareCall("{ call MultipleResultsProc}");
stmt.execute();
while (stmt.getMoreResults()) {
        ResultSet rs = stmt.getResultSet();
        ....
        }


dabei wurde die Escape Klausel verwendet um wirklich plattformunabhängig bleiben zu können.
 

Einstellungen bei der Ausführung
 

Man kann Parameter bei der Ausführung festlegen. Mit setQueryTimeout() kann man festlegen wie lange in Sekunden auf eine Antwort bei einer Query gewartet werden soll. Default ist 0, was unendlich bedeutet. Bei den Ergebnistabellen kann man mit setMaxRows die maximale Anzahl der Zeilen festlegen. Bei den Spalten vom Typ CHAR/VARCHAR/LONGVARCHAR/VARBINARY/LONGVARBINARY kann man die maximale Anzahl der Bytes mit setMaxFieldSize festlegen. In beiden Fällen bedeutet der Defaultwert 0 wieder ohne Beschränkung.
 

int getMaxFieldSize()  throws SQLException
void setMaxFieldSIze(int max)  throws SQLException
int getMaxRows()  throws SQLException
void setMaxRows()  throws SQLException
int getQueryTimeout()  throws SQLException
void setQueryTimeout()  throws SQLException
 

Zuletzt noch zwei Methoden, wie schon bei der Connection gibt es eine close() Methode, die auch gegenüber der automatischen garbagecollection bevorzugt werden sollte. Eine weitere interessante Methode ist
 

void cancel()  throws SQLException
 

damit kann man ein laufendes statement aus einem zweiten thread heraus abbrechen.
 

PreparedStatement
 

Auch in JDBC kann man mit Prepared Statements arbeiten. Auch dies ist aus dem Abschnitt Embedded SQL für C bekannt. Es gitb Laufzeitvorteile. Man hat auch die Möglichkeit an ein vorübersetztes SQL statement verschiedene Parameter übergeben. Ein Fragezeichen dient dabei als Platzhalter. Dies funktioniert auch in JDBC. Um vor dem eigentlichen Aufruf die Platzhalter zu Füllen gibt es verschiedene setXXX Methoden.
 

void setBoolean(int index, boolean b)  throws SQLException
void setString(int index, String s)  throws SQLException
void setInt(int index, int i)  throws SQLException
.....
 

die Vorgehensweise kann man am folgende Beispiel studieren:
 

PreparedStatement stmt = c.prepareStatement("INSERT INTO book "+
            "VALUES (?,?,?,?,?,?)");
stmt.SetString(1,"3-608-93421-9");
stmt.SetDouble(5,49.90);
...
stmt.executeUpdate();
Obwohl die Klasse PreparedStatement von der Klasse Statement abgeleitet wurden die execute Methoden neu geschreiben, da sie hier keinen Parameter erwarten. Der SQL Befehl wurde ja schon beim 'preparen' übergeben. Um NULL Werte einzufügen  oder um grosse Datenmengen einzufügen gibt es spezielle Methoden
 

void setNull(int index, int jdbcType) throws SQLException
void setAsciiStream(int index, java.io.InputStream s, int length)  throws SQLException
void setBinaryStream(int index, java.io.InputStream s, int length) throws SQLException
void setUnicodeStream(int index, java.io.InputStream s, int length) throws SQLException
 

Bei setNull muss der JDBC Datentyp angegeben werden, hierfür git es in java.sql.Types vordefinierte Konstanten:
 

CHAR,VARCHAR,LONGVARCHAR Zeichenketten
BIT Bit
BINARY,VARBINARY,LONGVARBINARY Binärfelder
TINYINT,SMALLINT,BIGINT,INTEGER Ganzzahlen
REAL,FLOAT,DOUBLE Gleitkommazahlen
DECIMAL,NUMERIC Dezimalzahlen
DATE,TIME,TIMESTAMP Datum,Uhrzeit

Bei den Stream Methoden wird ein geöffneter Stream übergeben und die Anzahl der Bytes, die daraus gelesen werden sollen. Bei ASCII und Unicode muss auf der Datenbankseite eine Zeichenkette erwartet werden, bei Binary muss auf der Datenbankseite ein Binärfeld erwartet werden. Zur Illustration ein Beispiel:

 
File content = new File("cover.jpd");
String isbn = "3-..";
int len = content.length();
FileInputStream fin = new FileInputStream(content);
PreparedStatement stmt = con.prepareStatement("UPDATE book SET cover =?"+
        "WHERE ISBN = ?");
stmt.setBinaryStream(1,fin,len);
stmt.setString(2,isbn);
stmt.excuteUpdate();
CallableStatement

Dies ist die nächste Stufe, und diese Klasse wird wiederum vom PreparedStatement abgeleitet. Anders als ein preparedStatement ist dies ein persistentes Objekt, d.h. es lebt in der Datenbank auch nach dem Abmelden weiter. Hier handelt es sich um zuvor gespeicherte Funktionen oder Prozeduren. Parameter dienen zur Kommunikation (Übergabe von Werten (IN) oder Rückgabe von Werten (OUT) oder sogar beides (INOUT). Bei Funktionen kann es sogar einen Rückgabewret geben. Wieder dien ? als Platzhalter. Zum Beschreiben der Eingabeparameter dienen die bekannte setXXX Methoden aus der Klasse PreparedStatement.  Bei den OUT oder INOUT Parametern müssen diese vor der Ausführung in ihrem JDBC Typ festgelegt werden. Dazu gibt es in der Klasse CallableStatement die Methode
 

void registerOutParameter(int index, int jdbcType) throws SQLException
 

ein Beispiel bei dem eine Prozedur aufgerufen wird mit zwei Parameter, der zweite ist ein INOUT vom Typ FLOAT, der erste ein OUT Parameter vom Typ VARCHAR
 

CallableStatement stmt = con.prepareCall("{call TestProc(?,?)}");
stmt.setDouble(2,42.0);
stmt.registerOutParameter(1,java.sql.Types.VARCHAR);
stmt.registerOutParameter(2,java.sql.Types.FLOAT);
Jenachdem von welchen Typ die Prozedur ist (SELECT / UPDATE / ..) kommen zum Ausführen executeQuery, executeUpdate, execute in Frage.

Zum Auslesen des Ergebnis aus einem Parameter gibt es getXXX Methoden
 

int getInt(int index) throws SQLException
float getFloat(int index) throws SQLException
String getString(int index) throws SQLException
....
 

es ist aber zu beachten, dass es keine Methoden für streams gibt. Mit der Methode
 

boolean wasNull() throws SQLException
 

kann man überprüfen ob der zuletzt mit getXXX gelesene OUT Wert ein NULL Wert war. Um obiges Beispiel auszuführen und das Ergebnis zu Lesen:
 

con.executeUpdate();
double res = stmt.getDouble(2);

3.6 ResultSet

Ein Objekt der Klasse ResultSet is das Ergebnis einer SELECT Anweisung. Wie schon in Embedded SQL für C wird die resultierende Tabelle über einen Cursor verwaltet. Zum Navigieren im ResultSet verwendet man den next Operator um nacheinander alle Zeilen zu erreichen. Der schon bekannte SCROLL CURSOR wird erst in Version 2.1 von JDBC realisiert. Ein Objekt der Klasse ResultSet erhält man als Ergebnis eines executeQuery (oder mit getResultSet) Aufrufs. Die erste Methode in der Klasse ResultSet

boolean next() throws SQLException

um auf die nächste Zeile im ResultSet zuzugreifen. Am Anfang steht der Cursor vor der ersten Zeile. D.h. zuerst ist ein next nötig.

 
Statement s = c.createStatement();
ResultSet r = s.executeQuery("...");
while (r.next()) {
..
}
r.close();


void close() throws SQLException

sollte ebenso wie bei Statement und Connection verwendet werden. Wenn der Cursor positioniert ist kann man in der aktuellen Zeile der Tabelle auf die Einträge in den Spalten zugreifen. Beim Zugriff kann mit dem Spaltennamen indizieren oder über den Spaltenindex, wobei die erste Spalte den Index 1 hat.  Um  vom Spaltennamen zum Index zu kommen gibt es die Methode

int findColumn(String name) throws SQLException

um auf die Daten zuzugreifen gibt es (wie auch schon teilweise beim callableStatement) die Methoden

String getString(int index)  throws SQLException
String getString(String name)  throws SQLException
int getInt(int index) throws SQLException
int getInt(String name)  throws SQLException
...

Dazu eine nützliche Tabelle(aus Dehnhardt: Anwendungsprogrammierung mit JDBC)

 
 

Damit werden alle getXXX Methoden aufgeführt und welche JDBC Typen wie in Java Typen umgewandelt werden können. Noch ein Beispiel für den Zugriff mittels getXXXStream. Es zeigt wie man ein Bild aus der Datenbank bekommt und es anzeigt. Verwendet werden JPEG Klassen von Sun

 
ResultSet r = s.executeQuery("SELECT cover FROM book WHERE isbn="+
                    "'3-608-93421-9'");
while (r.next()) {
            java.io.InputStream i = rset.getBinaryStream(1);
            JPEGImageDecode d = JPEDCodec.createJPEGDecoder(i);
            BufferedImage image = d.decodeAsBufferedImage();
            // buffered image anzeigen
            }
Ein Problem sind noch die NULL Werte, ebenso wie beim callableStatement muss dies explizit getestet werden

boolean wasNull()  throws SQLException

getestet werden  muss wenn bei Objekten (getString, getDate, ...) null zurückkam, bei getBoolean false oder der Wert 0 bei getInt, getDouble, ...

ResultSetMetaData

Wichtige weitere Informationen über das ResultSet werden in der Klasse ResultSetMetaData versteckt. Wichtig ist dies zum Beispiel bei Anwendeungen, wo nicht klar ist welche Attribute und welche Datentypen in einer Tabelle anliegen. Z.B. bei einer Anwendung wo der Benutzer Verbindungen zu einer beliebigen Datenbank aufbauen kann. Ein Objekt der Klasse ResultSetMetaData wird durch die Methode

ResultSetMetaData getMetaData() throws SQLException

aus der Klasse ResultSet erzeugt. In der Klasse ResultSetMetaData gibt es verschiedene Methoden

int getColumnCount() throws SQLException
String getTableName (int index) throws SQLException
String getCoulmnName(int index) throws SQLException
int getColumnType(int index) throws SQLException
String getColumnTypeName(int index) throws SQLException
boolean isReadOnly(int index) throws SQLException
boolean isWriteable(int index) throws SQLException
boolean isNullable(int index) throws SQLException
...

Die Methode getColumnCount liefert die Anzahl der Spalten im ResultSet, getTableName liefert den Namen der Tabelle aus der die Spalte stammt, getColumnName den Attributnamen zur Spalte, getColumnTaype den JDBC Typ, d.h. ein Vergleich auf INTEGER ist möglich. Den Namen des Datentyps bekommt man über getColumnTypeName. Die letzten drei Methoden testen ob nur lesbar ob auch geschrieben werden kann und ob NULL Werte in der Spalte erlaubt sind. Ein vollständige Übersicht ist unter dem URL

http://java.sun.com/products//jdk/1.1/docs/api/java.sql.ResultSetMetaData.html

erhältlich. Noch ein Beispiel zur Verwendung des Objekts ResultSetMetaData.
 

 
ResultSet r = s.executeQuery(" .. ");
ResultSetMetaData rm = r.getMetaData();
int numcols = rm.getColumnCount();
for (int i = 1; i<=numcols;i++) {
    int ct = rm.getColumnType(i);
    String cn = rm.getColumnName(i);
    String ctn = rm.getColumnTypeName(i);
    System.out.println("Spalte #"+i+":"+cn+" vom Typ "+ctn+
                " (JDBC Typ: "+ct);
    }

3.7 Rest an Klassen


DatabaseMetaData

Ähnlich den ResultSetMetaData gibt es noch eine Klasse DataBaseMetaData, diese erhält man wenn man in der Klasse Connection mit

DatabaseMetaData getMetaData() throws SQLException

arbeitet. In dieser Klasse gibt es eine riesige Anzahl von Methoden die helfen sollen Informationen über das Datenbanksystem zu bekommen. Man erhält so auch Zugriff auf das Data Dictionary, eine DATABASE mit Informationen über das Datenbank System. Ein kleiner Ausschnitt aus der Menge der verfügbaren Methoden

String getURL() throws SQLException
String getDataBaseProductName() throws SQLException
String getDataBaseProductName() throws SQLException

um 'technische' Informationen über die Datenbank zu bekommen. Mit den nächsten Methoden erhält man Informationen über das SQL, das von dieser Datenbank unterstützt wird.

String getSQLKeywords() throws SQLException
String getStringFunctions() throws SQLException
String getNumericFunctions() throws SQLException

liefert eine Aufstellung (string mit Komma getrennt)  von SQL Befehlen und Funktionen

boolean supportsOuterJoins() throws SQLException
boolean supports AlterTableWithAddColumn() thrwos SQLEXECPTION

So können Eigenschaften abgefragt werden, in denen sich (leider) SQL Versionen bei verschiedenen Datenbankanbietern unterscheiden. Die weiteren Methoden betreffen eher Limitierungen des Treibers (nicht nur)

int getMaxConnections() throws SQLException
int getMaxColumnsInTable() throws SQLException
int getMaxColumnsInSelect() throws SQLException
...

0 bedeutet hier im unbeschränkt. Und im Gegensatz zu den ähnlichen Methoden in der Connection Klasse gibt es hier kein setXXX. Die letzte Gruppe von Methoden arbeitet mit dem Data Dictionary. Sie liefern ResultSet Objekte:

ResultSet getTables getTables(String catalog, String schemapattern,
        String tablepattern, String types[])  throws SQLException
ResultSet getProcedures(String catalog, String schemapattern,
        String procedurenamepattern)  throws SQLException
ResultSet getProcedureColumns(String catalog,String schemapattern,
         String procedureNamePattern, String columnNamePattern)
                            throws SQLException
ResultSet getColumns(String catalog, String schemaPattern,
          String tableNamePattern, String columnNamePattern)
                            throws SQLException

...

Die Vorgehensweise ist überall ähnlich, man benötigt einen Katalog (getCatalog aus der Connection Klasse) kann dann ein Schema (DATABASE) oder auch nur ein Pattern angeben, die Rückgabe sind jeweils die matchenden Objekte. Als ein Beispiel sollen alle Tabellen und Views aus der spezifizierten Datenbank   ausgegeben werden:
 
 
 

public class Schema {
  public static void printSchema (Connection con, String schema)
      throws SQLException {
    DatabaseMetaData db_meta = con.getMetaData ();
    String[] tbl_types = { "TABLE", "VIEW" };
    ResultSet rset1 = db_meta.getTables (con.getCatalog (),
                        schema, "%", tbl_types);
    while (rset1.next ()) {
      String tbl_name = rset1.getString ("TABLE_NAME");
      System.out.println ("Tabelle: " + tbl_name);
      ResultSet rset2 = db_meta.getColumns (con.getCatalog (),
                          schema, tbl_name, "%");
      while (rset2.next ()) {
        String col_name = rset2.getString ("COLUMN_NAME");
        String col_type = rset2.getString ("TYPE_NAME");
        int col_size = rset2.getInt ("COLUMN_SIZE");
        System.out.println ("\t" + col_name + " " + col_type + "(" +
                            col_size + ")");
      }
      System.out.println ();
      rset2.close ();
    }
    rset1.close ();
  }

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

SQLException

Fehler werden wie in Java üblich über Exceptions mitgeteilt.  Für SQL gibt es die Klasse java.sql.SQLException. Sie ist eine abgleitete Klasse aus java.lang.Exception. Im Falle eines Fehlers wird ein Objekt der Klasse erzeugt.  Will man also Fehler erkennen muss diese Art von Exception abgefangen werden. In der Klasse gibt es Methoden um die genaue Fehlerursache zu erkennen, diese werden in einer SQLException zur Verfügung gestellt.
 

String getSQLState();
int getErrorCode();
SQLException getNextException();
 

Prinzipiell mag auch die ererbete Methode aus java.sql.Throwable
 

String getMessage()
 

von Interesse sein. Mit getSQLState erhält man einen String der in einer standardisierten Form. Mit getErrorCode erhält man einen Treiberspezifischen Fehlercode und mit getNextException wird eine weiterere evtl wartende Exception geholt. Ein Beispiel zur Verwendung:
 

 
     try {
     }
     catch (SQLException ex) {

                     // A SQLException was generated.  Catch it and
                     // display the error information.  Note that there
                     // could be multiple error objects chained
                     // together

             System.out.println ("\n*** SQLException caught ***\n");

             while (ex != null) {
                     System.out.println ("SQLState: " +
                                     ex.getSQLState ());
                     System.out.println ("Message:  " + ex.getMessage ());
                     System.out.println ("Vendor:   " +
                                     ex.getErrorCode ());
                     ex = ex.getNextException ();
                     System.out.println ("");
                     }
             }


Eine typische Verwendung ist in Zusammenhang mit commit/rollback, dies auch speziell wenn man mehrere SQL Anweisungen in einen try Block zusammenfasst:

 
try {
    c.setAutoCommit(false);
    ...
    }
catch (SQLException e) {
    c.rollback();
  }
Eine Besonderheit bei der SQL Ausnahmebehandlung ist die Tatsache, dass es nicht nur Fehler gibt. Die Klasse SQLWarning  ist von der Klasse SQLException abgeleitet. Der Unterschied ist, dass Warnings nicht zum Verlassen eines Blocks führen, sie müssen daher explizit abgefragt werden:
 

SQLWarning getWarning();
void clearWarnings();
 

und natürlich die Methoden aus SQLException. Ein Programm in dem Warnings abgefangen werden haben typischerweise folgende Form:

 
   try
                         {
                           ...

                           stmt = con.createStatement();
                           sqlw = con.getWarnings();
                           while( sqlw != null)
                           {
                             // handleSQLWarnings

                             sqlw = sqlw.getNextWarning();
                           }
                           con.clearWarnings();

                           stmt.executeUpdate( sUpdate );
                           sqlw = stmt.getWarnings();
                           while( sqlw != null)
                           {
                             // handleSQLWarnings

                             sqlw = sqlw.getNextWarning();
                           }
                         } // end try
                         catch ( SQLException SQLe)
                         {
                           ...
                         } // end catch
 
 

Die vielleicht häufigste Warnung betrifft das unvollständige Lesen/Schreiben von Daten. Dafür gibt es eine eigene Klasse DataTruncation, die von SQLWarning abgeleitet wird. Beim Lesen erzeugt dies nur eine Warning, beim Schreiben eine Exception. In SQLstate wird der Code "01004" gespeichert. In der DataTruncation Klasse gibt es dann weitere Methoden
 

int getDataSize();
int getIndex()
boolean getParameter()
boolean getRead();
int getTransferSize();
 

mit getRead wird festgestellt ob es sich um eine Lese (true) oder Schreibvorgang ging. getIndex() liefert die betroffene Spalte der Tabelle. getParameter liefert True wenn es sich um einen Fehler bei einem Parameter handelt, false wenn es sich um eine Spalte in einer Tabelle handelt. getDataSize liefert die gewünschte Grösse der Daten, getTransferSize ist die tatsächliche Grösse (-1 wenn unbekannt).
 
 
 

3.7 JDBC  2

 

 
 
 
 
 
 
 

scrollable / updatable resultSets

In JDBC 2, das beginnend ab JDK1.2 ausgeliefert wurde gibt es einige Änderungen gegenüber der bisher vorgestellten Version JDBC 1. Eine wichtige Änderung betrifft scrollable und updateable ResultSets. Dazu wurde eine neue CreateStatement  Methode implementiert:
 

 Statement createStatement(    int resultSetType,   int resultSetConcurrency )
        throws SQLException
 

für resultSetType gibt es:

                        ResultSet.TYPE_FORWARD_ONLY--default
                          wie JDBC 1.0: nur vorwärts, ejde Spalte einmal zu lesen
                          Falls  ResultSet.next() = false, ist das  ResultSet nicht mehr
                          verfügbar und die Daten gelöscht.

                        ResultSet.TYPE_SCROLL_INSENSITIVE
                          scroll  cursor wie aus embedded SQL für C. Aber statische
                          Daten: d.h. insensitive gegenüber Änderungen in der Datenbank

                        ResultSet.TYPE_SCROLL_SENSITIVE
                          ÄNDERUNGEN IN DER Datenbank werden sichtbar

resultSetConcurrency erlaubt folgende Werte:

                        ResultSet.CONCUR_READ_ONLY

                       ResultSet.CONCUR_UPDATABLE d.h im ResultSet können Änderungen
                          gemacht werden, die in der Datenbank sichtbar werden.
 

Es gibt auch analoge Befehle für PreparedStatement und CallableStatement:
 
 

 PreparedStatement createStatement(    String sql,
            int resultSetType,   int resultSetConcurrency )
        throws SQLException
 Statement CallableStatement(   String sql,
            int resultSetType,   int resultSetConcurrency )
        throws SQLException
 
 
 

Hat man dann ein ResultSet gibt es viele neue Methoden zum Positionieren (ausser dem bekannten next())
 

void beforeFirst() throws SQLException;
void afterLast() throws SQLException;
boolean first() throws SQLException;
boolean last() throws SQLException;
 

ist das ResultSet nicht scrollable gibt es eine SQLException. Statt next gibt es auch
 

boolean previous() throws SQLException;

und

boolean absolute (int row) throws SQLException;
boolean relative (int row) throws SQLException;
 

bei absolute bedeutet 1 die erste Reihe auch -1 ist erlaubt und bedeutet die letzte Reihe. Um hinter die letzte Reihe zukommen, kann man nur mit next arbeiten, um vor die erste Reihe zu kommen kann man nur mit previous arbeiten. Um herauszubekommen wo der Cursor ist
 

int getRow() throws SQLException;
boolean isLast() throws SQLException;
boolean isFirst() throws SQLException;
boolean isAfterLast() throws SQLException;
boolean isBeforeFirst() throws SQLException;
 

Dies betrifft scrollable ResultSets. Bei updateable ResultSets, d.h. bei Statement Objekten mit dem Parameter CONCUR_UPDATABLE erzeugt. Um die folgenden Methoden zu verwenden muss es natürlich für den Server möglich sein eine Änderung zu machen. Das geht natürlich nicht bei allen resultSets. Um ein Update zu machen gibt es analog zu getXXX Methoden in der Klasse ResultSet einen Satz von
 

void updateString(int index)  throws SQLException
void updateString(String name)  throws SQLException
void updateInt(int index) throws SQLException
void updateInt(String name)  throws SQLException
...
 

damit werden die Werte der aktuellen Zeile geändert, um diese Änderung in der Datenbank wirksam zu machen verwendet man die Methode
 

void updateRow() throws SQLException;
 

ändert man die Position der Zeile ohne den Aufruf der updateRow Methode werden die Änderungen verworfen, was man auch durch
 

void cancelRowUpdates() throws SQLException;
 

erreichen kann. Will man eine Zeile einfügen erzeugt man eine leere Zeile im resultSet mit
 

void moveToInsertRow() throws SQLException;
void insertRow() throws SQLException;
void moveToCurrentRow() throws SQLException;
 

mit insertRow wird nach updateXXX die Zeile eingefügt mit moveTo... kann man wieder zur vorherigen Position im ResultSet gelangen. Dazu ein Beispiel aus der Sun Dokumentation:
 

import java.sql.*;

          public class InsertRows {
              public static void main(String args[]) {
                  String url = "jdbc:mySubprotocol:myDataSource";
                  Connection con;
                  Statement stmt;
              try {
                  Class.forName("myDriver.ClassName");
              } catch(java.lang.ClassNotFoundException e) {
                  System.err.print("ClassNotFoundException: ");
                  System.err.println(e.getMessage());
              }
              try {
                  con = DriverManager.getConnection(url, "myLogin", "myPassword");
                  stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
                                 ResultSet.CONCUR_UPDATABLE);
                  ResultSet uprs = stmt.executeQuery("SELECT * FROM COFFEES");
                  uprs.moveToInsertRow();
                  uprs.updateString("COF_NAME", "Kona");
                  uprs.updateInt("SUP_ID", 150);
                  uprs.updateFloat("PRICE", 10.99f);
                  uprs.updateInt("SALES", 0);
                  uprs.updateInt("TOTAL", 0);
                  uprs.insertRow();
                  uprs.updateString("COF_NAME", "Kona_Decaf");
                  uprs.updateInt("SUP_ID", 150);
                  uprs.updateFloat("PRICE", 11.99f);
                  uprs.updateInt("SALES", 0);
                  uprs.updateInt("TOTAL", 0);
                  uprs.insertRow();
                  uprs.beforeFirst();
                 System.out.println("Table COFFEES after insertion:");
                  while (uprs.next()) {
                      String name = uprs.getString("COF_NAME");
                      int id = uprs.getInt("SUP_ID");
                      float price = uprs.getFloat("PRICE");
                      int sales = uprs.getInt("SALES");
                      int total = uprs.getInt("TOTAL");
                      System.out.print(name + "   " + id + "   " + price);
                      System.out.println("   " + sales + "   " + total);
                  }

                  uprs.close();
                  stmt.close();
                  con.close();

              } catch(SQLException ex) {
                  System.err.println("SQLException: " + ex.getMessage());
              }
          }
 

Zum Löschen gibt es noch die Routine
 

void deleteRow() throws SQLException;
 

da Änderungen sich auch im resultSet auswirken gibt es die Methode
 

void refreshRow() throws SQLException;
 
 
 

neue Datentypen
 

eine weitere wichtige Neuerung sind die Unterstützung weiterer interner Datentypen, im wesentlichen handelt es sich um SQL Datentypen die in der neuen SQL Version (SQL99) definiert werden:
 

BLOB binary large object
CLOB charcter large object
REF Referenz
ARRAY array
struct Benutzerdefiniert

das bedeutet auch, dass entsprechende getXXX und setXXX  updateXXX Methoden vorhanden sein müssen.
 
 
 
 
 
 

3.8 Applet


Hierbei handelt es sich nicht um weitere JDBC spezifische Methoden, sondern es soll vorgestellt werden wie die JDBC in ein Applet eingebunden werden um eine Darstellung von Datenbankabfragen im WWW zu ermöglichen.

Dies ist ein Beispiel, indem eine Datenbankverbindung aufgebaut wird und nachfolgend kann man SQL Befehle direkt eingeben. Der erste Schritt ist HTML source code mit dem Aufruf eines Applets:

 
<TITLE>EinfachesApplet.html</TITLE>
<APPLET Code=EinfachesApplet.class
        Height=300 Width=450  >
</APPLET>


das ist alles, der interessantere Teil ist der Java source code mit Namen EinfachesApplet.java

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

public class EinfachesApplet extends java.applet.Applet
                             implements ActionListener {
  // GUI-Komponenten
  Button ausfuehren = new Button("Ausfuehren!");
  TextArea ein = new TextArea("SELECT", 3, 60);
  TextArea ausgabe = new TextArea();
   // DBMS-Variable
  Connection c;
  Statement s;

  public void init() {
    Panel eingabe = new Panel(new BorderLayout());
    Panel knopf = new Panel(new FlowLayout(FlowLayout.LEFT));
    knopf.add(ausfuehren);
    eingabe.add(knopf, "North");
    eingabe.add(ein, "Center");
    add(eingabe, "North");
    add(ausgabe, "Center");
    ausgabe.setEditable(false);         // fuer Eingaben sperren
    setSize(450, 300);                  // Fenstergröße
    setVisible(true);                   // Fenster anzeigen
    try {
      String url = "jdbc:yardjdbc://localhost:1142/sample/SYMMETRICA";
      Class.forName("yardjdbc.YardDriver");
      c = DriverManager.getConnection(url, "axel","verratichnicht");
      s = c.createStatement();
    }
    catch (Exception ex) {
      ausgabe.setText("Fehler:\n" + ex.getMessage());
    }
    ausfuehren.addActionListener(this);  // Eventlistener reg.
  }

  public void actionPerformed(ActionEvent e) {
    String sql = ein.getText();
    ResultSet r;
    try {
      if (s.execute(sql)) {           // SQL SELECT ausfuehren
        r = s.getResultSet();
        ResultSetMetaData rm = r.getMetaData();
        int x = rm.getColumnCount();
        ausgabe.setText("");
        for (int i = 1; i <= x; i++) {
          ausgabe.append(rm.getColumnName(i));
          if (i != x) ausgabe.append(", ");
          else ausgabe.append("\n");
        }
        while(r.next()) {
          for (int i = 1; i <= x; i++) {
            ausgabe.append(r.getString(i));
            if (i != x) ausgabe.append(", ");
            else ausgabe.append("\n");
          }
        }
      }
      else
        ausgabe.setText(String.valueOf(s.getUpdateCount()));
    }
    catch (Exception ex) {
      ausgabe.setText("SQL: " + sql + "\nFehler:\n" +
                            ex.getMessage());
    }
  }
}                               // Ende class EinfachesApplet
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

3.9 SWING


In JDK1.2 wurde ein neues graphisches Benutzerinterface eingeführt, das sog. SWING package.

Literatur:
Geary:Graphic Java Mastering the JFC Vol. 2 Swing
Sun online Dokumentation

Ein wichtiger Bestandteil des SWING package ist das JTable Objekt, welches hervorragend geeignet ist ein ResultSet darzustellen. Im folgenden soll ein Beispielprogramm aus dem JDK 1.2 Paket besprochen werden. Das Programm besteht aus zwei Fenstern, dem ersten Fenster um eine Verbindung zur Datenbank aufzubauen, hierzu muss im wesentlichen URL, Benutzername und Passwort eingegeben werden.
 

Im zweiten Fenster kann man SQL Befehle eingeben und das ResultSet wird dargestellt. Dies ist ähnlich dem schon bekannten Applet.

 Um mit einem JTable Objekt zu arbeiten, muss beim Anlegen der JTable Information über die Tabelle übergeben werden. Man kann dies direkt tun

public JTable(Vector rowData, Vector columnNames)
public JTable(Object[][] rowData, Object[] columnNames)

oder aber über sog. TableModel Objekte, diese stellen Methoden zur Verfügung um auf die Einträge der Tabelle zu zugreifen. Diese Vorgehensweise bietet sich für die Zusammenarbeit mit einer Datenbank an. In diesem Beispiel wird die Klasse AbstractTableModel verwendet. Zuerst betrachten wir daher die Klasse JDBCAdapter die davon abgeleitet wird:
 

/**
 * An adaptor, transforming the JDBC interface to the TableModel interface.
 *
 * @version 1.20 09/25/97
 * @author Philip Milne
 */

import java.util.Vector;
import java.sql.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.event.TableModelEvent;

public class JDBCAdapter extends AbstractTableModel {
    Connection          connection;
    Statement           statement;
    ResultSet           resultSet;
    String[]            columnNames = {};
    Vector  rows = new Vector();
    ResultSetMetaData   metaData;

    public JDBCAdapter(String url, String driverName,
                       String user, String passwd) {
        try {
            Class.forName(driverName);
            System.out.println("Opening db connection");

            connection = DriverManager.getConnection(url, user, passwd);
            statement = connection.createStatement();
        }
        catch (ClassNotFoundException ex) {
            System.err.println("Cannot find the database driver classes.");
            System.err.println(ex);
        }
        catch (SQLException ex) {
            System.err.println("Cannot connect to this database.");
            System.err.println(ex);
        }
     }
 

Bereits beim Anlegen des JDBCAdapter Objekts wird eine Verbindung aufgebaut und ein Statement angelegt
 
 
    public void executeQuery(String query) {
        if (connection == null || statement == null) {
            System.err.println("There is no database to execute the query.");
            return;
        }
        try {
            resultSet = statement.executeQuery(query);
            metaData = resultSet.getMetaData();

            int numberOfColumns =  metaData.getColumnCount();
            columnNames = new String[numberOfColumns];
            // Get the column names and cache them.
            // Then we can close the connection.
            for(int column = 0; column < numberOfColumns; column++) {
                columnNames[column] = metaData.getColumnLabel(column+1);
            }

            // Get all rows.
            rows = new Vector();
            while (resultSet.next()) {
                Vector newRow = new Vector();
                for (int i = 1; i <= getColumnCount(); i++) {
             newRow.addElement(resultSet.getObject(i));
                }
                rows.addElement(newRow);
            }

            fireTableChanged(null); // Tell the listeners a new table has arrived.
        }
        catch (SQLException ex) {
            System.err.println(ex);
        }
    }
 

Nach einer Abfrage wird der Inhalt der Datenbank lokal kopiert.
   public void close() throws SQLException {
        System.out.println("Closing db connection");
        resultSet.close();
        statement.close();
        connection.close();
    }

    protected void finalize() throws Throwable {
        close();
        super.finalize();
    }

    //////////////////////////////////////////////////////////////////////////
    //
    //             Implementation of the TableModel Interface
    //
    //////////////////////////////////////////////////////////////////////////
 

Das sind die Methoden die bei einem TableModel da sein müssen
    // MetaData

    public String getColumnName(int column) {
        if (columnNames[column] != null) {
            return columnNames[column];
        } else {
            return "";
        }
    }

    public Class getColumnClass(int column) {
        int type;
        try {
            type = metaData.getColumnType(column+1);
        }
        catch (SQLException e) {
            return super.getColumnClass(column);
        }

        switch(type) {
        case Types.CHAR:
        case Types.VARCHAR:
        case Types.LONGVARCHAR:
            return String.class;

        case Types.BIT:
            return Boolean.class;

        case Types.TINYINT:
        case Types.SMALLINT:
        case Types.INTEGER:
            return Integer.class;

        case Types.BIGINT:
            return Long.class;

        case Types.FLOAT:
        case Types.DOUBLE:
            return Double.class;

        case Types.DATE:
            return java.sql.Date.class;

        default:
            return Object.class;
        }
    }

    public boolean isCellEditable(int row, int column) {
        try {
            return metaData.isWritable(column+1);
        }
        catch (SQLException e) {
            return false;
        }
    }

    public int getColumnCount() {
        return columnNames.length;
    }

    // Data methods

    public int getRowCount() {
        return rows.size();
    }

    public Object getValueAt(int aRow, int aColumn) {
        Vector row = (Vector)rows.elementAt(aRow);
        return row.elementAt(aColumn);
    }

    public String dbRepresentation(int column, Object value) {
        int type;

        if (value == null) {
            return "null";
        }

        try {
            type = metaData.getColumnType(column+1);
        }
        catch (SQLException e) {
            return value.toString();
        }

        switch(type) {
        case Types.INTEGER:
        case Types.DOUBLE:
        case Types.FLOAT:
            return value.toString();
        case Types.BIT:
            return ((Boolean)value).booleanValue() ? "1" : "0";
        case Types.DATE:
            return value.toString(); // This will need some conversion.
        default:
            return "\""+value.toString()+"\"";
        }

    }

    public void setValueAt(Object value, int row, int column) {
        try {
            String tableName = metaData.getTableName(column+1);
           // Some of the drivers seem buggy, tableName should not be null.
            if (tableName == null) {
                System.out.println("Table name returned null.");
            }
            String columnName = getColumnName(column);
            String query =
                "update "+tableName+
                " set "+columnName+" = "+dbRepresentation(column, value)+
                " where ";
            // We don't have a model of the schema so we don't know the
            // primary keys or which columns to lock on. To demonstrate
            // that editing is possible, we'll just lock on everything.
            for(int col = 0; col<getColumnCount(); col++) {
                String colName = getColumnName(col);
                if (colName.equals("")) {
                    continue;
                }
                if (col != 0) {
                    query = query + " and ";
                }
                query = query + colName +" = "+
                    dbRepresentation(col, getValueAt(row, col));
            }
            System.out.println(query);
            System.out.println("Not sending update to database");
            // statement.executeQuery(query);
        }
        catch (SQLException e) {
            //     e.printStackTrace();
            System.err.println("Update failed");
        }
        Vector dataRow = (Vector)rows.elementAt(row);
        dataRow.setElementAt(value, column);

    }
}

Damit kann man schon ein einfaches Java Programm schreiben um auf eine JDBC Datenbank zuzugreifen:
 
 
/**
 * A minimal example, using the JTable to view data from a database.
 *
 * @version 1.20 09/25/97
 * @author Philip Milne
 */

import javax.swing.*;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.Dimension;

public class TableExample2 {

    public TableExample2(String URL, String driver, String user,
                         String passwd, String query) {
        JFrame frame = new JFrame("Table");

Basisobjekt in  SWING
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {System.exit(0);}});
Damit das Programm aufhört wenn man auf Aufhören (X) klickt
 
        JDBCAdapter dt = new JDBCAdapter(URL, driver, user, passwd);
        dt.executeQuery(query);

        // Create the table
        JTable tableView = new JTable(dt);

        JScrollPane scrollpane = new JScrollPane(tableView);
        scrollpane.setPreferredSize(new Dimension(700, 300));
 

JTable sollen immer in ein JScrollPane
 
        frame.getContentPane().add(scrollpane);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        if (args.length != 5) {
            System.err.println("Needs database parameters eg. ...");
            System.err.println("java TableExample2 \"jdbc:sybase://dbtest:1455/pubs2\" \"connect.sybase.SybaseDriver\" guest trustworthy \"select * from titles\"");
            return;
        }
        new TableExample2(args[0], args[1], args[2], args[3], args[4]);
    }
}

Damit erhält man als Ausgabe das schon vorgestellte Bild.