Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage
<< | < | > | >> | API | Kapitel 42 - Datenbankzugriffe mit JDBC

## 42.3 Die DirDB-Beispieldatenbank

- 42.3 Die DirDB-Beispieldatenbank
  - 42.3.1 Anforderungen und Design
  - 42.3.2 Das Rahmenprogramm
  - 42.3.3 Die Verbindung zur Datenbank herstellen
    - Systemvoraussetzungen
  - 42.3.4 Anlegen und Füllen der Tabellen
  - 42.3.5 Zählen der Verzeichnisse und Dateien
  - 42.3.6 Suchen von Dateien und Verzeichnissen
  - 42.3.7 Die zehn größten Dateien
  - 42.3.8 Speicherverschwendung durch Clustering

### 42.3.1 Anforderungen und Design

In diesem Abschnitt wollen wir uns die zuvor eingeführten Konzepte in der Praxis ansehen. Dazu erzeugen wir eine einfache Datenbank DirDB, die Informationen zu Dateien und Verzeichnissen speichern kann. Über eine einfache Kommandozeilenschnittstelle können die Tabellen mit den Informationen aus dem lokalen Dateisystem gefüllt und auf unterschiedliche Weise abgefragt werden.

DirDB besitzt lediglich zwei Tabellen dir und file für Verzeichnisse und Dateien. Sie haben folgende Struktur:

Name | Typ | Bedeutung
did | INT | Primärschlüssel
dname | CHAR(100) | Verzeichnisname
fatherdid | INT | Schlüssel Vaterverzeichnis
entries | INT | Anzahl der Verzeichniseinträge

Tabelle 42.2: Die Struktur der dir-Tabelle

Name | Typ | Bedeutung
fid | INT | Primärschlüssel
did | INT | Zugehöriges Verzeichnis
fname | CHAR(100) | Dateiname
fsize | INT | Dateigröße
fdate | DATE | Änderungsdatum
ftime | TIME | Änderungszeit

Tabelle 42.3: Die Struktur der file-Tabelle

Beide Tabellen besitzen einen Primärschlüssel, der beim Anlegen eines neuen Satzes vom Programm vergeben wird. Die Struktur von dir ist baumartig; im Feld fatherid wird ein Verweis auf das Verzeichnis gehalten, in dem das aktuelle Verzeichnis enthalten ist. Dessen Wert ist im Startverzeichnis per Definition 0. Über den Fremdschlüssel did zeigt jeder Datensatz aus der file-Tabelle an, zu welchem Verzeichnis er gehört. Die Tabellen stehen demnach in einer 1:n-Beziehung zueinander. Auch die Tabelle dir steht in einer 1:n-Beziehung zu sich selbst. Abbildung 42.1 zeigt ein vereinfachtes E/R-Diagramm des Tabellendesigns.

Abbildung 42.1: E/R-Diagramm für DirDB

Das Programm DirDB.java soll folgende Anforderungen erfüllen:

- Es soll wahlweise mit MS-Access, InstantDB oder mit HSQLDB zusammenarbeiten.
- Es soll folgende Befehle interpretieren:
  - Leeren der Datenbank und Einlesen eines vorgegebenen Verzeichnisses inkl. aller Unterverzeichnisse.
  - Zählen der Dateien und Verzeichnisse in der Datenbank.
  - Suchen beliebiger Dateien oder Verzeichnisse.
  - Anzeige der größten Dateien.
  - Berechnen des durch Clustering verschwendeten Festplattenplatzes.

### 42.3.2 Das Rahmenprogramm

Wir implementieren eine Klasse `DirDB`, die (der Einfachheit halber) alle Funktionen mit Hilfe statischer Methoden realisiert. Die Klasse und ihre `main`-Methode sehen so aus:

    001 import java.util.*;
    002 import java.io.*;
    003 import java.sql.*;
    004 import java.text.*;
    005 import gk.util.*;
    006
    007 public class DirDB
    008 {
    009   //---Constants-----------------------------------------------
    010   static int INSTANT185 = 1;
    011   static int ACCESS95   = 2;
    012   static int HSQLDB     = 3;
    013
    014   //---Pseudo constants----------------------------------------
    015   static String FILESEP = System.getProperty("file.separator");
    016
    017   //---Static Variables----------------------------------------
    018   static int              db = INSTANT185;
    019   static Connection       con;
    020   static Statement        stmt;
    021   static Statement        stmt1;
    022   static DatabaseMetaData dmd;
    023   static int              nextdid = 1;
    024   static int              nextfid = 1;
    025
    026   //---main-------------------------------------------------
    027   public static void main(String[] args)
    028   {
    029     if (args.length < 1) {
    030       System.out.println(
    031         "usage: java DirDB [A|I|H] <command> [<options>]"
    032       );
    033       System.out.println("");
    034       System.out.println("command      options");
    035       System.out.println("---------------------------------");
    036       System.out.println("POPULATE     <directory>");
    037       System.out.println("COUNT");
    038       System.out.println("FINDFILE     <name>");
    039       System.out.println("FINDDIR      <name>");
    040       System.out.println("BIGGESTFILES <howmany>");
    041       System.out.println("CLUSTERING   <clustersize>");
    042       System.exit(1);
    043     }
    044     if (args[0].equalsIgnoreCase("A")) {
    045       db = ACCESS95;
    046     } else if (args[0].equalsIgnoreCase("H")) {
    047       db = HSQLDB;
    048     }
    049     try {
    050       if (args[1].equalsIgnoreCase("populate")) {
    051         open();
    052         createTables();
    053         populate(args[2]);
    054         close();
    055       } else if (args[1].equalsIgnoreCase("count")) {
    056         open();
    057         countRecords();
    058         close();
    059       } else if (args[1].equalsIgnoreCase("findfile")) {
    060         open();
    061         findFile(args[2]);
    062         close();
    063       } else if (args[1].equalsIgnoreCase("finddir")) {
    064         open();
    065         findDir(args[2]);
    066         close();
    067       } else if (args[1].equalsIgnoreCase("biggestfiles")) {
    068         open();
    069         biggestFiles(Integer.parseInt(args[2]));
    070         close();
    071       } else if (args[1].equalsIgnoreCase("clustering")) {
    072         open();
    073         clustering(Integer.parseInt(args[2]));
    074         close();
    075       }
    076     } catch (SQLException e) {
    077       while (e != null) {
    078         System.err.println(e.toString());
    079         System.err.println("SQL-State: " + e.getSQLState());
    080         System.err.println("ErrorCode: " + e.getErrorCode());
    081         e = e.getNextException();
    082       }
    083       System.exit(1);
    084     } catch (Exception e) {
    085       System.err.println(e.toString());
    086       System.exit(1);
    087     }
    088   }
    089 }

Listing 42.2: Das Rahmenprogramm der DirDB-Datenbank

In `main` wird zunächst ein usage-Text definiert, der immer dann ausgegeben wird, wenn das Programm ohne Argumente gestartet wird. Die korrekte Aufrufsyntax ist:

    java DirDB [A|I|H] <command> [<options>]

Nach dem Programmnamen folgt zunächst der Buchstabe "A", "I" oder "H", um anzugeben, ob die Access-, InstantDB- oder HSQLDB-Datenbank verwendet werden soll. Das nächste Argument gibt den Namen des gewünschten Kommandos an. In der folgenden verschachtelten Verzweigung werden gegebenenfalls weitere Argumente gelesen und die Methode zum Ausführen des Programms aufgerufen. Den Abschluß der `main`-Methode bildet die Fehlerbehandlung, bei der die Ausnahmen des Typs `SQLException` und `Exception` getrennt behandelt werden.

|  | Das vollständige Programm findet sich auf der CD-ROM zum Buch unter dem Namen DirDB.java. In diesem Abschnitt sind zwar auch alle Teile abgedruckt, sie finden sich jedoch nicht zusammenhängend wieder, sondern sind über die verschiedenen Unterabschnitte verteilt. Das importierte Paket `gk.util` kann wie in Abschnitt 13.2.3 beschrieben installiert werden. |  |
Hinweis

### 42.3.3 Die Verbindung zur Datenbank herstellen

Wie in Listing 42.2 zu sehen ist, rufen alle Kommandos zunächst die Methode `open` zum Öffnen der Datenbank auf. Anschließend führen sie ihre spezifischen Kommandos aus und rufen dann `close` auf, um die Datenbank wieder zu schließen.

Beim Öffnen der Datenbank wird zunächst mit `Class.forName` der passende Datenbanktreiber geladen und beim Treibermanager registriert. Anschließend besorgt das Programm ein `Connection`-Objekt, das an die statische Variable `con` gebunden wird. An dieser Stelle sind die potentiellen Code-Unterschiede zwischen den beiden Datenbanken gut zu erkennen:

- Während der Treibername für InstantDB die Bezeichnung `jdbc.idbDriver` hat, lautet er für die JDBC-ODBC-Bridge `sun.jdbc.odbc.JdbcOdbcDriver` und für HSQLDB `org.hsqldb.jdbcDriver`.
- Der Connection-URL bei InstantDB ist "jdbc:idb=", gefolgt vom Namen einer Property-Datei, die zusätzliche Konfigurationsangaben enthält. Bei der JDBC-ODBC-Bridge beginnt der URL immer mit "jdbc:odbc", gefolgt vom Namen der ODBC-Datenquelle. Bei HSQLDB beginnt er mit "jdbc:hsqldb:", gefolgt vom Namen der Datenbank.

    001 /**
    002  * Öffnet die Datenbank.
    003  */
    004 public static void open()
    005 throws Exception
    006 {
    007   //Treiber laden und Connection erzeugen
    008   if (db == INSTANT185) {
    009     Class.forName("jdbc.idbDriver");
    010     con = DriverManager.getConnection(
    011       "jdbc:idb=dirdb.prp",
    012       new Properties()
    013     );
    014   } else if (db == HSQLDB) {
    015     Class.forName("org.hsqldb.jdbcDriver");
    016     con = DriverManager.getConnection(
    017       "jdbc:hsqldb:hsqldbtest",
    018       "SA",
    019       ""
    020     );
    021   } else {
    022     Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    023     con = DriverManager.getConnection("jdbc:odbc:DirDB");
    024   }
    025   //Metadaten ausgeben
    026   dmd = con.getMetaData();
    027   System.out.println("");
    028   System.out.println("Connection URL: " + dmd.getURL());
    029   System.out.println("Driver Name:    " + dmd.getDriverName());
    030   System.out.println("Driver Version: " + dmd.getDriverVersion());
    031   System.out.println("");
    032   //Statementobjekte erzeugen
    033   stmt = con.createStatement();
    034   stmt1 = con.createStatement();
    035 }
    036
    037 /**
    038  * Schließt die Datenbank.
    039  */
    040 public static void close()
    041 throws SQLException
    042 {
    043   stmt.close();
    044   stmt1.close();
    045   con.close();
    046 }

Listing 42.3: Öffnen und Schließen der DirDB-Datenbank

Nachdem die Verbindung hergestellt wurde, liefert der Aufruf von `getMetaData` ein Objekt des Typs `DatabaseMetaData`. Es kann dazu verwendet werden, weitere Informationen über die Datenbank abzufragen. Wir geben lediglich den Connection-String und Versionsinformationen zu den geladenen Treibern aus. `DatabaseMetaData` besitzt darüber hinaus noch viele weitere Variablen und Methoden, auf die wir hier nicht näher eingehen wollen. Am Ende von `open` erzeugt das Programm zwei `Statement`-Objekte `stmt` und `stmt1`, die in den übrigen Methoden zum Ausführen der SQL-Befehle verwendet werden. Zum Schließen der Datenbank werden zunächst die beiden `Statement`-Objekte und dann die Verbindung selbst geschlossen.

#### Systemvoraussetzungen

Um tatsächlich eine Verbindung zu einer der drei angegebenen Datenbanken herstellen zu können, müssen auf Systemebene die nötigen Voraussetzungen dafür geschaffen werden:

- Soll auf eine ODBC-Datenquelle zugegriffen werden, muß diese unter Windows-Systemsteuerung mit Hilfe des Menüpunkts »ODBC-Datenquellen« zunächst eingerichtet werden. Im Falle von Microsoft Access ist eine »Benutzer-DSN« hinzuzufügen, in der als wichtigste Eigenschaften der gewünschte Name der Datenquelle sowie der Pfad der .mdb-Datei angegeben werden müssen. In unserem Beispiel wird »DirDB« als Datenquellenname verwendet. Im Verzeichnis mit den Beispieldateien zum Buch befindet sich eine leere Datenbank emptydb.mdb, die mit Access 2000 erstellt wurde und für erste Experimente verwendet werden kann. Sie sollte zu den Access-ODBC-Treibern, die aktuellen Windows-Betriebssystemen beiliegen, kompatibel sein.
- InstantDB und HSQLDB sind reine Java-Datenbanken, die ohne separat zu installierende Treiber betrieben werden können. Üblicherweise muß bei ihnen lediglich das zugehörige jar-Archiv in den `CLASSPATH` aufgenommen werden, damit ein Java-Programm darauf zugreifen kann. InstantDB war früher frei verfügbar, gehört aber mittlerweile zu Lutris Technologies. Privatleute können es zu Forschungs- und Entwicklungszwecken immer noch frei downloaden. Die Homepage von InstantDB ist http://www.lutris.com/products/instantDB/index.html. HSQLDB ist der Nachfolger von Hypersonic SQL. Die Datenbank ist frei verfügbar und wird als SourceForge-Projekt weiterentwickelt. Ihre Homepage ist http://hsqldb.sourceforge.net/.

### 42.3.4 Anlegen und Füllen der Tabellen

Unsere Anwendung geht davon aus, daß die Datenbank bereits angelegt ist, erstellt die nötigen Tabellen und Indexdateien aber selbst. Die dazu nötigen SQL-Befehle sind `CREATE TABLE` zum Anlegen einer Tabelle und `CREATE INDEX` zum Anlegen einer Indexdatei. Diese Befehle werden mit der Methode `executeUpdate` des `Statement`-Objekts ausgeführt, denn sie produzieren keine Ergebnismenge, sondern als DDL-Anweisungen lediglich den Rückgabewert 0. Das Anlegen der Tabellen erfolgt mit der Methode `createTables`:

    001 /**
    002  * Legt die Tabellen an.
    003  */
    004 public static void createTables()
    005 throws SQLException
    006 {
    007   //Anlegen der Tabelle dir
    008   try {
    009     stmt.executeUpdate("DROP TABLE dir");
    010   } catch (SQLException e) {
    011     //Nichts zu tun
    012   }
    013   stmt.executeUpdate("CREATE TABLE dir (" +
    014     "did       INT," +
    015     "dname     CHAR(100)," +
    016     "fatherdid INT," +
    017     "entries   INT)"
    018   );
    019   stmt.executeUpdate("CREATE INDEX idir1 ON dir ( did )");
    020   stmt.executeUpdate("CREATE INDEX idir2 ON dir ( fatherdid )");
    021   //Anlegen der Tabelle file
    022   try {
    023     stmt.executeUpdate("DROP TABLE file");
    024   } catch (SQLException e) {
    025     //Nichts zu tun
    026   }
    027   stmt.executeUpdate("CREATE TABLE file (" +
    028     "fid       INT ," +
    029     "did       INT," +
    030     "fname     CHAR(100)," +
    031     "fsize     INT," +
    032     "fdate     DATE," +
    033     "ftime     CHAR(5))"
    034   );
    035   stmt.executeUpdate("CREATE INDEX ifile1 ON file ( fid )");
    036 }

Listing 42.4: Anlegen der DirDB-Tabellen

|  | Um die Tabellen zu löschen, falls sie bereits vorhanden sind, wird zunächst die Anweisung `DROP TABLE` ausgeführt. Sie ist in einem eigenen `try`-`catch`-Block gekapselt, denn manche Datenbanken lösen eine Ausnahme aus, falls die zu löschenden Tabellen nicht existieren. Diesen »Fehler« wollen wir natürlich ignorieren und nicht an den Aufrufer weitergeben. |  |
Hinweis

Nachdem die Tabellen angelegt wurden, können sie mit der Methode `populate` gefüllt werden. `populate` bekommt dazu vom Rahmenprogramm den Namen des Startverzeichnisses übergeben, das rekursiv durchlaufen werden soll, und ruft `addDirectory` auf, um das erste Verzeichnis mit Hilfe des Kommandos `INSERT INTO` (das ebenfalls an `executeUpdate` übergeben wird) in die Tabelle dir einzutragen. Der Code sieht etwas unleserlich aus, weil einige Stringliterale einschließlich der zugehörigen einfachen Anführungsstriche übergeben werden müssen. Sie dienen in SQL-Befehlen als Begrenzungszeichen von Zeichenketten.

Für das aktuelle Verzeichnis wird dann ein `File`-Objekt erzeugt und mit `listFiles` (seit dem JDK 1.2 verfügbar) eine Liste der Dateien und Verzeichnisse in diesem Verzeichnis erstellt. Jede Datei wird mit einem weiteren `INSERT INTO` in die file-Tabelle eingetragen, für jedes Unterverzeichnis ruft `addDirectory` sich selbst rekursiv auf.

|  | Am Ende wird mit einem `UPDATE`-Kommando die Anzahl der Einträge im aktuellen Verzeichnis in das Feld entries der Tabelle dir eingetragen. Der Grund für diese etwas umständliche Vorgehensweise (wir hätten das auch gleich beim Anlegen des dir-Satzes erledigen können) liegt darin, daß wir auch ein Beispiel für die Anwendung der `UPDATE`-Anweisung geben wollten. |  |
Hinweis

    001 /**
    002  * Durchläuft den Verzeichnisbaum rekursiv und schreibt
    003  * Verzeichnis- und Dateinamen in die Datenbank.
    004  */
    005 public static void populate(String dir)
    006 throws Exception
    007 {
    008   addDirectory(0, "", dir);
    009 }
    010
    011 /**
    012  * Fügt das angegebene Verzeichnis und alle
    013  * Unterverzeichnisse mit allen darin enthaltenen
    014  * Dateien zur Datenbank hinzu.
    015  */
    016 public static void addDirectory(
    017   int fatherdid, String parent, String name
    018 )
    019 throws Exception
    020 {
    021   String dirname = "";
    022   if (parent.length() > 0) {
    023     dirname = parent;
    024     if (!parent.endsWith(FILESEP)) {
    025       dirname += FILESEP;
    026     }
    027   }
    028   dirname += name;
    029   System.out.println("processing " + dirname);
    030   File dir = new File(dirname);
    031   if (!dir.isDirectory()) {
    032     throw new Exception("not a directory: " + dirname);
    033   }
    034   //Verzeichnis anlegen
    035   int did = nextdid++;
    036   stmt.executeUpdate(
    037     "INSERT INTO dir VALUES (" +
    038     did + "," +
    039     "\'" + name + "\'," +
    040     fatherdid + "," +
    041     "0)"
    042   );
    043   //Verzeichniseinträge lesen
    044   File[] entries = dir.listFiles();
    045   //Verzeichnis durchlaufen
    046   for (int i = 0; i < entries.length; ++i) {
    047     if (entries[i].isDirectory()) {
    048       addDirectory(did, dirname, entries[i].getName());
    049     } else {
    050       java.util.Date d = new java.util.Date(
    051         entries[i].lastModified()
    052       );
    053       SimpleDateFormat sdf;
    054       //Datum
    055       sdf = new SimpleDateFormat("yyyy-MM-dd");
    056       String date = sdf.format(d);
    057       //Zeit
    058       sdf = new SimpleDateFormat("HH:mm");
    059       String time = sdf.format(d);
    060       //Satz anhängen
    061       stmt.executeUpdate(
    062         "INSERT INTO file VALUES (" +
    063         (nextfid++) + "," +
    064         did + "," +
    065         "\'" + entries[i].getName() + "\'," +
    066         entries[i].length() + "," +
    067         "{d \'" + date + "\'}," +
    068         "\'" + time + "\')"
    069       );
    070       System.out.println("  " + entries[i].getName());
    071     }
    072   }
    073   //Anzahl der Einträge aktualisieren
    074   stmt.executeUpdate(
    075     "UPDATE dir SET entries = " + entries.length +
    076     "  WHERE did = " + did
    077   );
    078 }

Listing 42.5: Füllen der DirDB-Tabellen

|  | Hier tauchen die beiden im Rahmenprogramm definierten statischen Variablen `nextdid` und `nextfid` wieder auf. Sie liefern die Primärschlüssel für Datei- und Verzeichnissätze und werden nach jedem eingefügten Satz automatisch um eins erhöht. Daß dieses Verfahren nicht mehrbenutzerfähig ist, leuchtet ein, denn die Zähler werden lokal zur laufenden Applikation erhöht. Eine bessere Lösung bieten Primärschlüsselfelder, die beim Einfügen von der Datenbank einen automatisch hochgezählten eindeutigen Wert erhalten. Dafür sieht JDBC allerdings kein standardisiertes Verfahren vor, meist kann ein solches Feld in der `INSERT INTO`-Anweisung einfach ausgelassen werden. Alternativ könnten Schlüsselwerte vor dem Einfügen aus einer zentralen Key-Tabelle geholt und transaktionssicher hochgezählt werden. |  |
Hinweis

### 42.3.5 Zählen der Verzeichnisse und Dateien

Das `COUNT`-Kommando soll die Anzahl der Verzeichnisse und Dateien zählen, die mit dem `POPULATE`-Kommando in die Datei eingefügt wurden. Wir verwenden dazu ein einfaches `SELECT`-Kommando, das mit der `COUNT(*)`-Option die Anzahl der Sätze in einer Tabelle zählt:

    001 /**
    002  * Gibt die Anzahl der Dateien und Verzeichnisse aus.
    003  */
    004 public static void countRecords()
    005 throws SQLException
    006 {
    007   ResultSet rs = stmt.executeQuery(
    008     "SELECT count(*) FROM dir"
    009   );
    010   if (!rs.next()) {
    011     throw new SQLException("SELECT COUNT(*): no result");
    012   }
    013   System.out.println("Directories: " + rs.getInt(1));
    014   rs = stmt.executeQuery("SELECT count(*) FROM file");
    015   if (!rs.next()) {
    016     throw new SQLException("SELECT COUNT(*): no result");
    017   }
    018   System.out.println("Files: " + rs.getInt(1));
    019   rs.close();
    020 }

Listing 42.6: Anzahl der Sätze in der DirDB-Datenbank

Die `SELECT`-Befehle werden mit der Methode `executeQuery` an das `Statement`-Objekt übergeben, denn wir erwarten nicht nur eine einfache Ganzzahl als Rückgabewert, sondern eine komplette Ergebnismenge. Die Besonderheit liegt in diesem Fall darin, daß wegen der Spaltenangabe `COUNT(*)` lediglich ein einziger Satz zurückgegeben wird, der auch nur ein einziges Feld enthält. Auf dieses können wir am einfachsten über seinen numerischen Index 1 zugreifen und es durch Aufruf von `getInt` gleich in ein `int` umwandeln lassen. Das Ergebnis geben wir auf dem Bildschirm aus und wiederholen anschließend dieselbe Prozedur für die Tabelle file.

### 42.3.6 Suchen von Dateien und Verzeichnissen

Um eine bestimmte Datei oder Tabelle in unserer Datenbank zu suchen, verwenden wir ebenfalls ein `SELECT`-Statement. Im Gegensatz zu vorher lassen wir uns nun mit der Spaltenangabe "*" alle Felder der Tabelle geben. Zudem hängen wir an die Abfrageanweisung eine `WHERE`-Klausel an, um eine Suchbedingung formulieren zu können. Mit Hilfe des `LIKE`-Operators führen wir eine Mustersuche durch, bei der die beiden SQL-Wildcards "%" (eine beliebige Anzahl Zeichen) und "_" (ein einzelnes beliebiges Zeichen) verwendet werden können. Zusätzlich bekommt InstantDB die (nicht SQL-92-konforme) Option `IGNORE CASE` angehängt, um bei der Suche nicht zwischen Groß- und Kleinschreibung zu unterscheiden (ist bei Access 7.0 nicht nötig).

Die von `executeQuery` zurückgegebene Ergebnismenge wird mit `next` Satz für Satz durchlaufen und auf dem Bildschirm ausgegeben. Mit Hilfe der Methode `getDirPath` wird zuvor der zugehörige Verzeichnisname rekonstruiert und vor dem Dateinamen ausgegeben. Dazu wird in einer Schleife zur angegebenen `did` (Verzeichnisschlüssel) so lange das zugehörige Verzeichnis gesucht, bis dessen `fatherdid` 0 ist, also das Startverzeichnis erreicht ist. Rückwärts zusammengebaut und mit Trennzeichen versehen, ergibt diese Namenskette den kompletten Verzeichnisnamen.

|  |  |  | Der in dieser Methode verwendete `ResultSet` wurde mit dem zweiten `Statement`-Objekt `stmt1` erzeugt. Hätten wir dafür die zu diesem Zeitpunkt noch geöffnete Variable `stmt` verwendet, wäre das Verhalten des Programmes undefiniert gewesen, weil die bestehende Ergebnismenge durch das Erzeugen einer neuen Ergebnismenge auf demselben `Statement`-Objekt ungültig geworden wäre. |  |
Warnung

    001 /**
    002  * Gibt eine Liste aller Files auf dem Bildschirm aus,
    003  * die zu dem angegebenen Dateinamen passen. Darin dürfen
    004  * die üblichen SQL-Wildcards % und _ enthalten sein.
    005  */
    006 public static void findFile(String name)
    007 throws SQLException
    008 {
    009   String query = "SELECT * FROM file " +
    010                  "WHERE fname LIKE \'" + name + "\'";
    011   if (db == INSTANT185) {
    012     query += " IGNORE CASE";
    013   }
    014   ResultSet rs = stmt.executeQuery(query);
    015   while (rs.next()) {
    016     String path = getDirPath(rs.getInt("did"));
    017     System.out.println(
    018       path + FILESEP +
    019       rs.getString("fname").trim()
    020     );
    021   }
    022   rs.close();
    023 }
    024
    025 /**
    026  * Liefert den Pfadnamen zu dem Verzeichnis mit dem
    027  * angegebenen Schlüssel.
    028  */
    029 public static String getDirPath(int did)
    030 throws SQLException
    031 {
    032   String ret = "";
    033   while (true) {
    034     ResultSet rs = stmt1.executeQuery(
    035       "SELECT * FROM dir WHERE did = " + did
    036     );
    037     if (!rs.next()) {
    038       throw new SQLException(
    039         "no dir record found with did = " + did
    040       );
    041     }
    042     ret = rs.getString("dname").trim() +
    043           (ret.length() > 0 ? FILESEP + ret : "");
    044     if ((did = rs.getInt("fatherdid")) == 0) {
    045       break;
    046     }
    047   }
    048   return ret;
    049 }

Listing 42.7: Suchen nach Dateien in der DirDB-Datenbank

Das DirDB-Programm bietet mit dem Kommando `FINDDIR` auch die Möglichkeit, nach Verzeichnisnamen zu suchen. Die Implementierung dieser Funktion ähnelt der vorigen und wird durch die Methode `findDir` realisiert:

    001 /**
    002  * Gibt eine Liste aller Verzeichnisse auf dem Bildschirm
    003  * aus, die zu dem angegebenen Verzeichnisnamen passen.
    004  * Darin dürfen die üblichen SQL-Wildcards % und _
    005  * enthalten sein.
    006  */
    007 public static void findDir(String name)
    008 throws SQLException
    009 {
    010   String query = "SELECT * FROM dir " +
    011                  "WHERE dname LIKE \'" + name + "\'";
    012   if (db == INSTANT185) {
    013     query += " IGNORE CASE";
    014   }
    015   ResultSet rs = stmt.executeQuery(query);
    016   while (rs.next()) {
    017     System.out.println(
    018       getDirPath(rs.getInt("did")) +
    019       " (" + rs.getInt("entries") + " entries)"
    020     );
    021   }
    022   rs.close();
    023 }

Listing 42.8: Suchen nach Verzeichnissen in der DirDB-Datenbank

|  | Wird das DirDB-Programm von der Kommandozeile aufgerufen, kann es unter Umständen schwierig sein, die Wildcards "%" oder "_" einzugeben, weil sie vom Betriebssystem oder der Shell als Sonderzeichen angesehen werden. Durch Voranstellen des passenden Escape-Zeichens (das könnte beispielsweise der Backslash sein) kann die Sonderbedeutung aufgehoben werden. In der DOS-Box von Windows 95 oder NT kann die Sonderbedeutung des "%" nur aufgehoben werden, indem das Zeichen "%" doppelt geschrieben wird. Soll beispielsweise nach allen Dateien mit der Erweiterung .java gesucht werden, so ist `DirDb` unter Windows wie folgt aufzurufen:
    java DirDB I findfile %%.java
Weiterhin ist zu beachten, daß die Interpretation der Wildcards von den unterschiedlichen Datenbanken leider nicht einheitlich gehandhabt wird. Während das obige Kommando unter InstantDB korrekt funktioniert, ist bei der Access-Datenbank die Ergebnismenge leer. Der Grund kann - je nach verwendeter Version - darin liegen, daß entweder der Stern "*" anstelle des "%" als Wildcard erwartet wird oder daß die Leerzeichen am Ende des Feldes als signifikant angesehen werden und daher auch hinter dem Suchbegriff ein Wildcard-Zeichen angegeben werden muß:
    java DirDB A findfile %%.java%%
Hinweis

### 42.3.7 Die zehn größten Dateien

Eine ähnliche `SELECT`-Anweisung begegnet uns, wenn wir uns die Aufgabe stellen, die howmany größten Dateien unserer Datenbank anzuzeigen. Hierzu fügen wir eine `ORDER BY`-Klausel an und sortieren die Abfrage absteigend nach der Spalte fsize. Von der Ergebnismenge geben wir dann die ersten howmany Elemente aus:

    001 /**
    002  * Gibt die howmany größten Dateien aus.
    003  */
    004 public static void biggestFiles(int howmany)
    005 throws SQLException
    006 {
    007   ResultSet rs = stmt.executeQuery(
    008     "SELECT * FROM file ORDER BY fsize DESC"
    009   );
    010   for (int i = 0; i < howmany; ++i) {
    011     if (rs.next()) {
    012       System.out.print(
    013         getDirPath(rs.getInt("did")) +
    014         FILESEP + rs.getString("fname").trim()
    015       );
    016       System.out.println(
    017         Str.getFormatted("%10d", rs.getInt("fsize"))
    018       );
    019     }
    020   }
    021   rs.close();
    022 }

Listing 42.9: Sortieren der Ergebnismenge

### 42.3.8 Speicherverschwendung durch Clustering

Bevor wir uns weiterführenden Themen zuwenden, wollen wir uns eine letzte Anwendung unserer Beispieldatenbank ansehen. Viele Dateisysteme (allen voran das alte FAT-Dateisystem unter MS-DOS und Windows) speichern die Dateien in verketteten Zuordnungseinheiten fester Größe, den Clustern. Ist die Clustergröße beispielsweise 4096 Byte, so belegt eine Datei auch dann 4 kByte Speicher, wenn sie nur ein Byte groß ist. Immer, wenn die Größe einer Datei nicht ein genaues Vielfaches der Clustergröße ist, bleibt der letzte Cluster unvollständig belegt und wertvoller Plattenspeicher bleibt ungenutzt. Ist die Clustergröße hoch, wird vor allen dann viel Platz verschwendet, wenn das Dateisystem sehr viele kleine Dateien enthält. Die folgende Funktion `clustering` berechnet zu einer gegebenen Clustergröße die Summe der Dateilängen und stellt sie dem tatsächlichen Platzbedarf aufgrund der geclusterten Speicherung gegenüber:

    001 /**
    002  * Summiert einerseits die tatsächliche Größe aller
    003  * Dateien und andererseits die Größe, die sie durch
    004  * das Clustering mit der angegebenen Clustergröße
    005  * belegen. Zusätzlich wird der durch das Clustering
    006  * "verschwendete" Speicherplatz ausgegeben.
    007  */
    008 public static void clustering(int clustersize)
    009 throws SQLException
    010 {
    011   int truesize = 0;
    012   int clusteredsize = 0;
    013   double wasted;
    014   ResultSet rs = stmt.executeQuery(
    015     "SELECT * FROM file"
    016   );
    017   while (rs.next()) {
    018     int fsize = rs.getInt("fsize");
    019     truesize += fsize;
    020     if (fsize % clustersize == 0) {
    021       clusteredsize += fsize;
    022     } else {
    023       clusteredsize += ((fsize / clustersize) + 1)*clustersize;
    024     }
    025   }
    026   System.out.println("true size      = " + truesize);
    027   System.out.println("clustered size = " + clusteredsize);
    028   wasted = 100 * (1 - ((double)truesize / clusteredsize));
    029   System.out.println("wasted space   = " + wasted + " %");
    030 }

Listing 42.10: Cluster-Berechnung mit der DirDB-Datenbank

Um beispielsweise den Einfluß der geclusterten Darstellung bei einer Clustergröße von 8192 zu ermitteln, kann das Programm wie folgt aufgerufen werden:

    java DirDB I clustering 8192

Die Ausgabe des Programms könnte dann beispielsweise so aussehen:

    InstantDB  - Version 1.85
    Copyright (c) 1997-1998 Instant Computer Solutions Ltd.

    Connection URL: jdbc:idb:dirdb.prp
    Driver Name:    InstantDB JDBC Driver
    Driver Version: Version 1.85

    true size      = 94475195
    clustered size = 112861184
    wasted space   = 16.290799323884464 %

Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de