Hans-Joachim Adam Jetzt lerne ich endlich... Dateien, Datenbanken und SQL mit Einführung Informationstechnik z:\_skripte\java datenbanken\adam skript db & java\dbundjava030310.doc DATEIBEHANDLUNG..............................................................................................................3 Stream .................................................................................................................................3 Datei schreiben....................................................................................................................3 1: DateiSchreiber...................................................................................................4 Datei lesen...........................................................................................................................4 2: DateiLeser .........................................................................................................4 Texte im TextArea ..............................................................................................................5 3: TextArea............................................................................................................5 4: DateiSchreiberLeser..........................................................................................5 FileWriter, FileReader ........................................................................................................5 5: DateiEditor ........................................................................................................6 6: LogEditor ..........................................................................................................6 Texteingaben von der Konsole (Tastatur) verarbeiten........................................................6 7: Tastatur .............................................................................................................7 ZUGRIFF AUF EINE DATENBANK ......................................................................................8 ODBC .................................................................................................................................8 Eine DSN einrichten ...........................................................................................................9 VERBINDUNG VON JAVA ZU EINER DATENBANK .......................................................9 DATENBANK .............................................................................................................................10 Metadaten der Datenbank holen..........................................................................................10 Namen der Tabellen und deren Eigenschaften holen..........................................................12 TABELLE ....................................................................................................................................14 Datenwerte einer Tabelle ausgeben ....................................................................................14 Senden eines SQL-Strings an eine Tabelle .........................................................................14 Tabellenwerte lesen.............................................................................................................15 DATENZUGRIFF AUF ODBC-DATENBANKEN UNTER JAVA ......................................18 TYPKONVERTIERUNG...........................................................................................................19 z:\_skripte\java datenbanken\adam skript db & java\dbundjava030310.doc 2 Dateibehandlung Stream Ein Stream ist eine Konstruktion, welche die Fähigkeit besitzt, Daten auf ein erdachtes „Ausgabegerät“ zu schreiben oder von diesem zu lesen. Man kann einen Stream auch als einen Kommunikationspfad zwischen einer Quelle und einem Ziel beschreiben. In Java ist ein Stream ein Objekt, das mit einem bestimmten Ziel oder einer bestimmten Quelle verbunden ist, zum Beispiel mit einer Datei oder mit einem anderen Rechner. Ist ein Stream mit einem Ziel verbunden auf dem Daten abgelegt werden, so ist der Stream ein Ausgabestream. Und anders herum, ist ein Stream mit einer Quelle verbunden, von der Daten empfangen werden, so ist er ein Eingabestream. In Java gibt es zwei Arten von Streams. Zum Einen die Byte-Streams und zum Anderen die ZeichenkettenStreams. Der größte Unterschied der beiden Streams ist, dass die Transporteinheit der Byte-Streams 8 Bit lange Bytes sind. Zeichenketten-Streams dagegen verwenden 16 Bit lange Unicode-Zeichen, um besser mit Strings und Zeichentypen zusammenarbeiten zu können. Die Byte-Streams werden in Java durch die beiden Klassen java.io.Inputstream (Eingabestream) und java.io.outputstream (Ausgabestream) repräsentiert. Die Zeichenketten-Streams durch java.io.Reader (Eingabestream) und java.io.Writer (Ausgabestream). Die wichtigsten Klassen in Verbindung mit Streams sind: Writer FileWriter BufferedWriter stringWriter Reader FileReader BufferedReader StringReader OutputStream FileOutputstream BufferedOutputstream DataOutputStream InputStream FileInputstream BufferedInputstream DataInputStream Diese Klassen stellen Grundlagen für das Lesen bzw. Schreiben von Daten durch Streams bereit. Sie sind im Paket java.io enthalten. Es ist empfehlenswert das gesamte java.io Paket in ein Programm zu importieren, das Streams verwenden soll. Dies geschieht mit der Anweisung import java.io.*; Datei schreiben Die Datei-Ausgabe und -EinleseDateiSchreiber FileOutputStream Streams sind leicht zu handhaben. Nach dem Erstellen des Streamoutput Objekts ruft man die Methoden wriException write te() bzw. read() auf. Die Daten werclose den nur Byteweise übertragen: aus diesem Grund kann man beim Schreiben nicht den String direkt übergeben, sondern man muss die Methode getBytes() aufrufen, welche den String Byteweise zurückliefert. Diese Rückgaben können von der write()-Methode des OutputStream direkt verwertet werden. Falls der Schreibvorgang fehlschlägt (z.B. Verzeichnis nicht vorhanden, Diskette voll) wirft die write()-Methode eine IOException, die man mit der try .. catch Konstruktion auswerten kann. Die Verbindung zur Datei wird mit der Methode close() beendet. import java.io.*; class DateiSchreiber { public static void main(String args[]) { try{ String s = "Das ist ein FileOutputStream"; FileOutputStream output = new FileOutputStream("test.txt"); output.write(s.getBytes()); output.close(); System.out.println("Datei test.txt wurde erstellt"); } catch (IOException e){ System.out.println("Schreiben fehlgeschlagen"); } } } z:\_skripte\java datenbanken\adam skript db & java\dbundjava030310.doc 3 Dateibehandlung Datei lesen Die Methode write des OutputStream ruft immer wieder die ihm übergebenen Methode auf, bis dies das „Ende-Zeichen“ zurück gibt. Die Methode getBytes des String-Objekts wiederum gibt bei jedem Aufruf ein weiteres Zeichen des Strings aus. Das Programmbeispiel erzeugt die Ausgabe wie rechts dargestellt. 1: DateiSchreiber Erstellen Sie ein Programm zur Erzeugung einer Textdatei wie im Beispiel oben. Die Erfolgreiche Speicherung können Sie mittels des Windows-Notepad (oder Editor) überprüfen. Wenn dem Stream-Objekt keine Pfadbezeichnung, sondern nur der Dateiname übergeben wird, so schreibt es die Datei in das Verzeichnis, in dem die Klassendatei steht. Es ist möglich, relative und absolute Pfade und Laufwerkbuchstaben anzugeben. Achtung: anders als bei Windows üblich werden die Verzeichnisse nicht mit dem linken Schrägstrich (backslash \), sondern mit einem rechten Schrägstrich (slash /) getrennt. Es sind also mögliche Pfadangaben: FileOutputStream output = new FileOutputStream("../../dateiSchreiber/classes/test.txt"); FileOutputStream output = new FileOutputStream("/temp/test.txt"); FileOutputStream output = new FileOutputStream("c:/temp/test.txt"); Datei lesen Dateien lesen kann man mittels eines DateiLeser FileInputStream FileInputStreams. Die Methode read() gibt je Aufruf ein weiteres Byte aus der input Datei zurück. Um die gesamte Datei read auszulesen, muss man daher die input()close Methode in einer Schleife wiederholt ausführen lassen. Auch die input()Methode wirft eine IOException. Die Verbindung zur Datei wird ebenfalls mit der close()-Methode beendet. Exception byte b; while ((b = (byte)input.read()) != -1) { System.out.print((char)b); } 2: DateiLeser Es ist ein Leseprogramm zu schreiben. Sie können auch in der while-Schleife die eingelesenen Buchstaben in einem String s sammeln, und diesen am Schluss mit System.out.println(s); ausgeben. 4 Dateibehandlung Texte im TextArea Texte im TextArea Texte können in Java mittels eines TextAreas angezeigt und bearbeitet werden. Das TextArea stellt dazu die Methoden setText() und getText() zur Verfügung. TextArea ta1 = new TextArea(); ... String s; s = ta1.getText(); ta2.setText(s); 3: TextArea Schreiben Sie ein Programm wie im Beispiel rechts, mit dem Sie Text aus einem Textbereich in einen zweiten übertragen. Verändern Sie auch die Größe und das Aussehen des TextArea (z.B. mit / ohne Scrollbalken) 4: DateiSchreiberLeser Die Texte sollen mittels TextAreas ausgegeben und eingelesen werden. Auch hier können Sie die Arbeitsweise mittels des Windows-Notepad (oder Editor) überprüfen. FileWriter, FileReader Die Klassen FileWriter und FileReader ermöglichen ebenfalls das Schreiben und Lesen von Textdateien. import java.io.*; class DateiWriter { public static void main(String args[]) { try{ String s = "Das ist ein FileWriter"; FileWriter output = new FileWriter("test.txt"); output.write(s); output.close(); System.out.println("Datei test.txt wurde erstellt"); } catch (IOException e){ System.out.println("Schreiben fehlgeschlagen"); } } } Dieses Beispiel unterscheidet sich nicht sehr von den vorhergehenden Anwendungen, außer dass man sich die Funktion getBytes() sparen konnte, weil die Klasse FileWriter spezialisierter auf Strings ist als die Klasse FileOutputStream. Außerdem besitzt die Klasse FileWriter noch einen weiteren Konstruktor, der wie folgt aussieht: public FileWriter(String Dateiname, boolean append); Der zusätzliche Parameter gibt an, ob der Inhalt einer Datei, falls sie schon vorhanden ist, überschrieben werden soll oder die neuen Daten einfach hinten dran geschrieben werden FileWriter output write close DateiEditor ta1: TextArea Exception FileReader input read close Exception 5 Dateibehandlung Texteingaben von der Konsole (Tastatur) verarbeiten sollen. Man könnte also in Zeile 6 auch schreiben: new FileWriter("test.txt", true); so würde die Datei test.txt bei jedem Programmaufruf um eine neue Zeile erweitert werden. Die Klasse FileReader wird exakt gleich angewendet wie die Klasse FileInputStream: auch FileReader gibt die Datei nur zeichenweise zurück. Beide Anwendungen unterscheiden sich demnach nur in den Vokabeln. Der Dateiinhalt wird aber nicht direkt Zeichenweise auf dem Bildschirm ausgegeben, sondern die einzelnen Zeichen müssen in einem String gesammelt werden. Das kann man mit s = s + (char)b; innerhalb einer Schleife erreichen, wobei s der String und b das eingelesene Zeichen ist. 5: DateiEditor Erstellen Sie einen Mini-Editor: Im Textfeld oben kann der Dateiname eingetragen werden. Die Button unter dem Textbereich dienen zum Speichern bzw. Laden der Datei. Wenn sie nicht vorhanden ist, wird sie beim Speichern angelegt, beim Ladeversuch einer nicht vorhandenen Datei gibt es eine Fehlermeldung. Hier dient derselbe Textbereich sowohl zum Eintippen des von Programm einzulesenden Textes als auch als Ausgabebereich. 6: LogEditor In einem Logfile sollen auf Buttonklick Textzeilen angehängt werden. Ein Button soll die gesamte Log-Datei ausgeben, ein weiterer Button soll die Logdatei leeren. Texteingaben von der Konsole (Tastatur) verarbeiten Das Programm readsystem demonstriert, wie man mit Hilfe von Streams Eingaben des Benutzers verarbeiten kann. Das Programm tut nicht viel, aber zur Erläuterung des Problems, wie man Benutzereingaben verarbeitet, genügt es. Das Programm wartet eine Benutzereingabe ab und gibt diese dann wieder aus. Gibt man allerdings quit ein wird das Programm beendet. import java.io.*; class Readsystem { public static void main(String Arguments[]) throws IOException { String zeile; InputStreamReader input = new InputStreamReader(System.in); BufferedReader buffer = new BufferedReader(input); while ((zeile = buffer.readLine()) != null){ if (zeile.equals("quit")) { input.close(); break; } else { System.out.println(zeile); } } } } 6 Dateibehandlung Texteingaben von der Konsole (Tastatur) verarbeiten Die wichtigsten Zeilen sind: InputStreamReader input = new InputStreamReader(System.in); BufferedReader buffer = new BufferedReader(input); in der ersten Zeile wird ein Eingabe-Stream-Objekt erstellt, der als Parameter das Objekt System.in erhält. System.in repräsentiert die Eingaben der Konsole (wie System.out die Ausgaben repräsentiert). In der zweiten Zeile entsteht ein Objekt eines BufferedReader, dem Speicherobjekt für die Tastatureingaben, die von System.in geliefert werden. System InputStreamReader BufferedReader Readsystem zeile: String in input buffer readLine close Beide Zeilen werden oft zu einer zusammengefasst, weil man praktisch nie auf die Objekte einzeln zugreifen möchte. BufferedReader bekommt also nicht direkt ein Objekt übergeben, sondern eine Vorschrift, wie es sich das entsprechende Objekt erzeugen kann. Aus diesen “Zwischenklassen” werden intern selbstverständlich Objekte erzeugt, aber wir bekommen sie nicht zu Gesicht. Deshalb bezeichnet man sie als anonyme Klassen. BufferedReader buffer = new BufferedReader(new InputStreamReader(System.in)); Tastatureingaben werden also von Java her ähnlich wie Textdateien gesehen. Auch hier geschieht das Lesen mit Hilfe von Streams: Wir erzeugen eine Instanz des BufferedReader, welcher über einen InputStreamReader auf die Tastatur-Eingabe-Klasse (System.in) zugreift. System kann direkt verwendet werden, ohne dass man erst ein Objekt erzeugen muss (abstrakte Klasse). Mit dem Attribut System.in stellt System ein Byte Speicherplatz bereit, auf den man mit InputStreamReader zugreifen kann. BufferedReader wiederum kann Bytes dieses Streams lesen, speichern und mit der Methode readLine() zeilenweise bereitstellen. 7: Tastatur Es sollen Tastatureingaben direkt zeilenweise in ein TextArea übernommen werden. Bestimmte Steuerwörter (z.B. save, load, quit) sollen direkt ausgewertet und ausgeführt werden. 7 Zugriff auf eine Datenbank. ODBC Zugriff auf eine Datenbank . ODBC Um Datenbanken von Java aus anzusprechen, muss der ODBC-Treiber installiert und konfiguriert werden. Java greift nämlich mittels der JDBC-ODBC-Bridge über den ODBC-Treiber auf die Datenbank zu. Es gibt auch noch firmenspezifische (native) Datenbanktreiber und „all-Java“-Treiber, die direkten Zugriff auf die Datenbank ermöglichen. Diese werden hier aber nicht betrachtet. Datenbank (z.B. ACCESS) ODBC-Treiber JDBC-ODBC Bridge JDBC-Driver-Manager XYZ-Datenbank XYZ-Treiber (firmenspezifisch) } } Java-Pragramm Man muss also zunächst den ODBC-Zugriff auf die Datenbank einrichten. Das geschieht mit Hilfe des ODBC-Administrators. Mit diesem können Sie nicht nur die Verbindung zu bestehenden Datenbanken aufnehmen, sondern auch Datenbanken neu erzeugen. Rufen Sie den ODBC-Administrator auf. Sie Erreichen den ODBC-Administrator über Start - Einstellungen - Systemsteuerung - Verwaltung - Datenquellen (ODBC). Wem dieser Weg zu lang ist, kann sich auch die Datei C:\WINNT\System32\ODBCAD32.exe in ein beliebiges Verzeichnis kopieren und/oder eine Verknüpfung darauf anlegen. . Literatur: Roland Willms: Java Programmierung Praxisbuch, Franzis Verlag, Professional Series, ISBN 3-7723-7604-5 8 Verbindung von Java zu einer Datenbank Eine DSN einrichten Eine DSN einrichten Sie müssen mit dem ODBC Administrator eine System-DSN einrichten um auf ihre Datenbank zugreifen zu können. Das müssen Sie tun, wenn Sie die Datenbank mit dem ODBC Administrator erstellen oder auf eine zuvor mit ACCESS erstellte Datenbank zugreifen wollen. Mittels dem Button Hinzufügen erstellen Sie einen neuen Aliasnamen für die Datenbank. Wenn Sie auf eine bereits bestehende (ACCESS)Datenbank zugreifen wollen, dann klicken Sie den Button Konfigurieren und wählen die gewünschte ACCESSDatenbank aus: In diesem Beispiel ist die unter ACCESS erstellte Datenbank ITC2000.mdb, die sich im Ordner C:\Programme\Adam\SeQueL\ITC befindet unter dem Alias-Namen ITC ansprechbar. Die Vergabe Aliasnamen bringt den Vorteil, dass im Anwendungsprogramm nur die Alias-Namen erscheinen, und dieses daher nicht angepasst werden muss, wenn man den Pfad zur Datenbank ändert oder das System auf einen anderen Rechner überträgt. Verbindung von Java zu einer Datenbank Es muss das Paket java.sql in den Programmcode importiert werden. DriverManager import java.sql.*; Als erstes ist der Treiber zu laden. Wir wollen ODBC-konforme Datenbanken abfragen und müssen daher die JDBC-ODBC-Bridge laden: getConnection Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Danach muss die Verbindung zur Datenbank (mit dem Aliasnamen ITC) aufgebaut werden. Dazu ist ein Objekt der Klasse Connection zu erstellen. Dieses Objekt wird mit der Klassenmethode getConnection() der Klasse DriverManager erzeugt. Vom DriverManager braucht man kein Objekt zu erstellen! Dieses Connection-Objekt stellt die notwendigen Methoden zum Datentransport bereit. Connection createStatement getMetaData close Exception Connection con = DriverManager.getConnection ("jdbc:odbc:ITC"); 9 Datenbank Metadaten der Datenbank holen Datenbank Metadaten der Datenbank holen Um die Daten aufnehmen zu können, muss in Java ein Objekt erstellt werden, welches den Speicherplatz für die zu übertragenden Daten bereitstellt. Wir treffen hier auf das Problem, dass verschiedene Programme zusammenarbeiten müssen: die Datenbank stellt Daten bereit, unser Java-Programm muss diese lesen. Die Datenübertragung kann über Speicherbereiche erfolgen, auf die beide Anwendungen Zugriff haben. Die Frage ist nun, wer diesen Speicherplatz zur Verfügung stellt: das könnte das Betriebssystem sein, oder eines der beteiligten Objekte. Meist stellt das anfragende Objekt den Platz zur Verfügung, denn dann wird der Speicherplatz nur beansprucht, wenn auch tatsächlich Abfragen stattfinden sollen. Selbstverständlich kann dieser Speicherplatz nicht beliebig sein, sondern er muss nach Größe und Art genau zur zu übertragenden Datenstruktur passen. In diesem Fall hier benötigen wir die Klasse DatabaseMetaData. Zunächst wollen wir die Metadaten der Datenbank erfragen. DatabaseMetaData istwie auch Connection abstrakt deklariert, das bedeutet, dass wir den Speicherplatz direkt verwenden können, ohne erst ein Objekt der Klasse DatabaseMetaData erstellen zu müssen. Wir können direkt mit der Methode getMetaData() der Connection die Daten übernehmen: DatabaseMetaData dbmd = con.getMetaData(); Metadaten kann man nun direkt mit den entsprechenden Methoden auslesen und beispielsweise in einem TextArea ausgeben: text = ""; text += "Datenbank: " + dbmd.getDatabaseProductName() + "\n"; text += "Version: " + dbmd.getDatabaseProductVersion() + "\n"; area.setText(text); DatabaseMetaData getDatabaseProduktName(): String getDatabaseProduktVersion(): String Weitere Methoden können Sie der Java-Hilfe entnehmen. Unser Programm ist so aber noch nicht ganz fertig. Beim Datenbankzugriff kann es zu vielerlei Fehlern kommen: Datenbank ist nicht (mehr) vorhanden, Zugriff verweigert wegen fehlender Rechte usw. Deshalb wird die Datenbankabfrage in ein try - catch Konstrukt eingepackt, welches Systemfehler abfängt. try{ // hier stehen die Zugriffsbefehle } catch (Exception e){ area.setText(e.toString()); } Hier nun ein vollständiges Programm, welches das Fenster rechts erzeugt: import java.awt.*; import java.awt.event.*; import java.sql.*; class JavaDB extends Frame implements ActionListener{ Label lbdb = new Label("URL = jdbc:odbc:"); TextField url= new TextField("MS Datenbank",20); Button execute = new Button(" Ausführen "); TextArea area = new TextArea(10, 50); Font courier = new Font("Courier",Font.PLAIN,12); public JavaDB(){ addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e){ dispose(); 10 Datenbank Metadaten der Datenbank holen System.exit(0); } }); setLayout(new FlowLayout()); add(lbdb); add(url); add(execute); execute.addActionListener(this); add(area); area.setFont(courier); } public void actionPerformed(ActionEvent e){ if (e.getSource() == execute){ accessDatabase(); } } public void accessDatabase(){ try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Connection con = DriverManager.getConnection ("jdbc:odbc:"+url.getText()); DatabaseMetaData dbmd = con.getMetaData(); display(dbmd); } catch (Exception e){ area.setText(e.toString()); } } public void display(DatabaseMetaData dbmd){ String text = "Metadaten der Datenbank"+"\n\n"; area.setText(text); try{ text = ""; text += "Katalog: " + dbmd.getCatalogTerm() + "\n"; text += "Datenbank: " + dbmd.getDatabaseProductName() + "\n"; text += "Version: " + dbmd.getDatabaseProductVersion() + "\n"; text += "Treibername: " + dbmd.getDriverName() + "\n"; text += "Version: " + dbmd.getDriverVersion() + "\n"; text += "URL: " + dbmd.getURL() + "\n"; text += "User: " + dbmd.getUserName() ; area.append(text); } catch (Exception e){ area.setText(e.toString()); } } public static void main(String args[]){ System.out.println("Starting JavaDB..."); JavaDB mainFrame = new JavaDB(); mainFrame.setSize(400, 250); mainFrame.setTitle("JavaDB"); mainFrame.setVisible(true); } } 11 Datenbank Namen der Tabellen und deren Eigenschaften holen Namen der Tabellen und deren Eigenschaften holen Das vorige Beispiel gibt nicht so sehr interessante Daten preis. Um beispielsweise die Namen der in der Datenbank enthaltenen Tabellen zu erfahren muss man mit einem weiteren Zwischenschritt diese aus den Metadaten extrahieren. Das ist deshalb so kompliziert, weil man nicht von vornherein sagen kann, wie viele Tabellen vorkommen, und welche Felder diese enthalten. Daher kann man nicht einfach einige Speicherplätze (Attribute) in der Klasse DatabaseMetaData bereitstellen, sondern man braucht Methoden, die in einer weiteren Klasse definiert sind: in ResultSet. DatabaseMetaData getDatabaseProduktName(): String getDatabaseProduktVersion(): String ... getTables(): ResultSet getColumns(): ResultSet ResultSet Das Auslesen der Tabellennamen geschieht mit Hilfe der next Exception Klasse ResultSet. Auch von dieser Klasse braucht man getString("TABLE_NAME"); keine Objekte abzuleiten. Mit Hilfe der Methode getTables() aus der DatabaseMetaData-Klasse sind die Tabellennamen mittels Methoden des ResultSet-Objekts verfügbar. Rufen Sie die Online-Hilfe des Java-JDK auf um weitere Informationen über die folgenden ResultSet-Klasse und deren Methoden zu erhalten! text = ""; String[] tableType = {"TABLE"}; //nur Benutzertabellen ResultSet rsTab = dbmd.getTables(null,null,"%",tableType); Der wiederholte Aufruf der Methode rsTab.getString("TABLE_NAME") gibt je Aufruf einen weiteren Tabellennamen aus. Wiederholtes Aufrufen einer Methode zum Auslesen eines unbekannt großen Speicherbereiches wird öfter angewendet. Je Aufruf wird ein weiteres Datenelement geliefert, oder ein „Schlusszeichen“, welches das aufrufende Programm auswerten kann*). table = rsTab.getString("TABLE_NAME"); Der Aufruf wird in einer Schleife untergebracht: while (rsTab.next()){ table = rsTab.getString("TABLE_NAME"); text = table + "\n"; area.append(text); text = ""; } Die Namen der Felder die in dieser Tabelle enthalten sind erhält man nach dem Erzeugen eines weiteren ResultSet: rsCol = dbmd.getColumns(null,null,table,"%"); Aus diesem ResultSet erhalten Sie nacheinander die einzelnen Feldnamen durch wiederholten Aufruf der Methode rsCol.getString("COLUMN_NAME"). Entsprechend können auch die weiteren Eigenschaften der Felderwie TYPE_NAME, DATA_TYPE, REMARKS usw. ermittelt werden. text += rsCol.getString("COLUMN_NAME"); Die Aufrufe der einzelnen Tabellen und deren Feldnamen werden in ineinander geschachtelten Wiederholungsschleifen untergebracht. Die Methode next() gibt true zurück, solange noch weitere Elemente im ResultSet vorhanden sind. next() kann man daher als Wiederholungskriterium in einer while-Schleife verwenden. while (rsTab.next()){ table = rsTab.getString("TABLE_NAME"); text = table + "\n"; area.append(text); text = ""; *) Das ist uns schon mal im Kapitel „Streams, Auslesen eines TextArea in meinem Skript zur OO-Progrmmierung begegnet. 12 Datenbank Namen der Tabellen und deren Eigenschaften holen rsCol = dbmd.getColumns(null,null,table,"%"); while (rsCol.next()){ text += rsCol.getString("COLUMN_NAME") + " "; text += rsCol.getString("TYPE_NAME") + " "; text += rsCol.getString("DATA_TYPE") + " "; text += rsCol.getString("REMARKS") + "\n"; } area.append(text); } Ein vollständiges Programm kann mit einiger kosmetischer Korrektur bei der Ausgabe das folgende Bild liefern. Sie sehen die Ausgabe der Tabellennamen und darunter in jeweils einer Zeile ein Datenfeld samt Datentyp und Bemerkung. Hier folgt noch der komplette Code des Programms: import java.awt.*; import java.awt.event.*; import java.sql.*; class JavaDBTabellen extends Frame implements ActionListener{ Label lbdb = new Label("URL = jdbc:odbc:"); TextField url= new TextField("MS Datenbank",20); Button execute = new Button(" Ausführen "); TextArea area = new TextArea(20, 70); Font courier = new Font("Courier",Font.PLAIN,12); public JavaDBTabellen(){ addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e){ dispose(); System.exit(0); } }); setLayout(new FlowLayout()); add(lbdb); add(url); add(execute); execute.addActionListener(this); add(area); area.setFont(courier); } public void actionPerformed(ActionEvent e){ if (e.getSource() == execute){ accessDatabase(); } } public void accessDatabase(){ try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Connection con = DriverManager.getConnection("jdbc:odbc:"+url.getText()); 13 Tabelle Datenwerte einer Tabelle ausgeben DatabaseMetaData dbmd = con.getMetaData(); display(dbmd); } catch (Exception e){ area.setText(e.toString()); } } public void display(DatabaseMetaData dbmd){ String text = ""; try{ text = "Tabellen der Datenbank: " + dbmd.getURL() + "\n"; area.setText(text); text = ""; String[] tableType = {"TABLE"}; //nur Benutzertabellen ResultSet rsTab = dbmd.getTables(null,null,"%",tableType); // ResultSet rsTab = dbmd.getTables(null,null,"%",null); //ALLE Tabellen ResultSet rsCol; String table, l20 = " "; while (rsTab.next()){ table = rsTab.getString("TABLE_NAME"); text = rsTab.getString("TABLE_TYPE")+":\t"+table + "\n"; area.append("\n"+text); text = ""; rsCol = dbmd.getColumns(null,null,table,"%"); while (rsCol.next()){ text += "\t"+(rsCol.getString("COLUMN_NAME")+l20).substring(0,19); text += "\t"+(rsCol.getString("TYPE_NAME") +l20).substring(0,10) ; text += "\t"+(rsCol.getString("DATA_TYPE") +l20).substring(0,4) ; text += "\t"+ rsCol.getString("REMARKS") +"\n"; } area.append(text); } } catch (Exception e){ area.setText(e.toString()); } } public static void main(String args[]){ System.out.println("Starting JavaDBTabellen..."); JavaDBTabellen mainFrame = new JavaDBTabellen(); mainFrame.setSize(550, 400); mainFrame.setTitle("JavaDBTabellen"); mainFrame.setVisible(true); } } Tabelle Datenwerte einer Tabelle ausgeben Die in der Tabelle enthalten Inhalte der Felder, also die „Tabelle“ selbst, sind nach Absetzen der SQLAnweisung SELECT * FROM <Tabelle> mittels eines ResultSet verfügbar. Dazu muss der Name der Tabelle bekannt sein, zum Beispiel mittels des Verfahrens aus dem vorigen Kapitel. Senden eines SQL-Strings an eine Tabelle Erst muss ein Objekt der Klasse Statement erzeugt werden. Darin ist die Methode execute() enthalten, die als Parameter den SQL-String an die Datenbank-Tabelle übergibt. Mittels dieser execute-Methode kann man also in Form von SQLAnweisungen Informationen über die zu liefernden Daten an die Datenbank schicken. Die Rückgaben der Datenbank sind in einem ResultSet verfügbar Statement execute getResultSet: ResultSet SQLException 14 Tabelle Tabellenwerte lesen Statement tbstat = con.createStatement(); boolean control = tbstat.execute("SELECT * FROM Artikel"); Tabellenwerte lesen Die Daten der Tabelle sind mittels ResultSet dem Statement-Objekt zu entnehmen: ResultSet tbrs = tbstat.getResultSet(); Aus diesem ResultSet kann man zunächst die MetaDaten der Tabelle, also die Feldnamen („Köpfe“ der Spalten), auslesen. Dazu erzeugt man erst ein Objekt aus der Klasse ResultSetMetaData: ResultSetMetaData tbmd = tbrs.getMetaData(); int columns = tbmd.getColumnCount(); for (int i = 1; i <= columns; i++){ text += tbmd.getColumnName(i) + " | "; } Und weiter erhält man durch wiederholte Aufrufe der getString-Methode des ResultSet in einer Schleife die Tabellendaten (Feldwerte) Zeile für Zeile. Beachten Sie, dass Sie dieser Methode entweder die Nummer oder den Bezeichner (Kopf) der Spalte übergeben können! text = ""; while (tbrs.next()){ for (int i = 1; i <= columns; i++){ text += tbrs.getString(i) + " "; } text += "\n"; } area.append(text); ResultSet getMetaData: ResultSetMetaData next getString ResultSetMetaData getColumnName getColumnCount SQLException Bei diesem Verfahren können zwei Fehler auftreten: 1. Die Datenbank „versteht“ das übergebene SQL-Statement nicht und kann es nicht ausführen. Deshalb gibt tbstat.execute(SQL-String) einen bool-Wert zurück. Nur bei 'true' darf aus dem Statement ein ResultSet erzeugt werden. 2. Sie kann nichts zurückgeben, weil die Ergebnismenge leer ist: die Suchanfrage kann keinerlei Treffer ergeben, weil kein Datensatz dieser Bedingung entspricht. Die Rückgabemenge ist daher die leere Menge. Dieser Fall muss vom aufrufenden Programm berücksichtigt werden. Im folgenden Beispiel wird einfach die Verbindung geschlossen. Im folgenden Programmausschnitt wird die SQL-Anweisung aus einem Textfeld eingelesen und die Rückgabe der Datenbank an die Methode display() übergeben. Die Rückgabe einer leeren Menge schließt die Datenbankabfrage. boolean control = tbstat.execute(sql.getText()); if (control) { ResultSet tbmd = tbstat.getResultSet(); if (tbmd != null) { display(tbmd); } else{ area.setText(""); con.close(); } } Mit einigen kosmetischen Korrekturen und einem Textfeld zur Eingabe des SQL-Strings während der Laufzeit kann man dieses Ergebnis erzielen: 15 Tabelle Tabellenwerte lesen Und nun auch hierzu der komplette Quellcode: import java.awt.*; import java.awt.event.*; import java.sql.*; class JavaSQL extends Frame implements ActionListener{ Label lbdb = new Label("URL = jdbc:odbc:"); TextField url= new TextField("MS Datenbank",40); Label lbsql = new Label("SQL ="); TextField sql= new TextField("SELECT * FROM ",40); Button execute = new Button(" Ausführen "); TextArea area = new TextArea(20, 100); Font courier = new Font("Courier",Font.PLAIN,12); public JavaSQL(){ addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e){ dispose(); System.exit(0); } }); setLayout(new FlowLayout()); add(lbdb); add(url); add(lbsql); add(sql); add(execute); execute.addActionListener(this); add(area); area.setFont(courier); } public void actionPerformed(ActionEvent e){ if (e.getSource() == execute) { accessDatabase(); } } public void accessDatabase(){ try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Connection con = DriverManager.getConnection("jdbc:odbc:"+url.getText()); //Name in ODBC-Manager festlegen // muss _nicht_ gleich DB-Name sein!! Statement tbstat = con.createStatement(); boolean control = tbstat.execute(sql.getText()); if (control) { ResultSet tbrs = tbstat.getResultSet(); if (tbrs != null) { display(tbrs); 16 Tabelle Tabellenwerte lesen } else{ area.setText(""); con.close(); } } } catch (Exception e){ area.setText(e.toString()); } } public void display(ResultSet tbrs){ try { ResultSetMetaData tbmd = tbrs.getMetaData(); int columns = tbmd.getColumnCount(); String text = "| "; for (int i = 1; i <= columns; i++){ text += tbmd.getColumnName(i) + "\t | "; } text += "\n"; area.setText(text); text = ""; while (tbrs.next()){ text += "| "; for (int i = 1; i <= columns; i++){ text += tbrs.getString(i) + "\t | "; // Feldname ODER Spaltennummer 1..n } text += "\n"; } area.append(text); } catch (SQLException e){ area.setText(e.toString()); } } public static void main(String args[]){ System.out.println("Starting JavaSQL..."); JavaSQL mainFrame = new JavaSQL(); mainFrame.setSize(800, 450); mainFrame.setTitle("JavaSQL"); mainFrame.setVisible(true); } } 17 Datenzugriff auf ODBC-Datenbanken unter Java Tabellenwerte lesen Datenzugriff auf ODBC-Datenbanken unter Java Datenbank (z.B. ACCESS) DriverManager getConnection Connection createStatement getMetaData close Exception Statement execute getResultSet: ResultSet DatabaseMetaData SQLException ResultSet getDatabaseProduktName(): String getDatabaseProduktVersion(): String ... getTables(): ResultSet getColumns(): ResultSet getMetaData: ResultSetMetaData next getString ResultSetMetaData ResultSet next getString("TABLE_NAME") getString("COLUMN_NAME") Aussagen über Datenbank getColumnName getColumnCount SQLException Exception Aussagen über Daten in den Tabellen 18 Typkonvertierung Tabellenwerte lesen Typkonvertierung Die Datenübernahme ist nicht ganz problemlos: es ist darauf zu achten, dass die gespeicherten Datentypen verträglich sind mit den Typen auf die die Ausgabe erfolgt. Bei ungeschickter Wahl sind Datenverluste möglich! Dabei unterscheiden wir zwei Richtungen der Typkonvertierung: Die erste ist die Typkonvertierung von SQL nach Java, wobei jedoch nicht notwendigerweise alle SQL-Typen in allen Datenbanken unterstützt werden. Die zweite geht in die entgegengesetzte Richtung Java Æ SQL SQL type VARCHAR oder String LONGVARCHAR java.sql.Numeric NUMERIC boolean BIT byte TINYINT short SMALLINT int INTEGER long BIGINT float REAL double DOUBLE VARBINARY oder byte[] LONGVARBINARY java.sql.Date DATE java.sql.Time TIME java.sql.Timestamp TIMESTAMP Java type SQL Æ Java SQL type Java type CHAR String VARCHAR String LONGVARCHAR String NUMERIC java.sql.Numeric DECIMAL java.sql.Numeric BIT boolean TINYINT byte SMALLINT short INTER int BIGINT long REAL float FLOAT double DOUBLE double BINARY byte[] VARBINARY byte[] LONGVARBINARY byte[] DATE java.sql.Date TIME java.sql.Time TIMESTAMP java.sql.Timestamp 19