From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001 From: Sven Eisenhauer Date: Fri, 10 Nov 2023 15:11:48 +0100 Subject: add new repo --- .../hjp5/html/k100268.html | 1187 ++++++++++++++++++++ 1 file changed, 1187 insertions(+) create mode 100644 Master/Reference Architectures and Patterns/hjp5/html/k100268.html (limited to 'Master/Reference Architectures and Patterns/hjp5/html/k100268.html') diff --git a/Master/Reference Architectures and Patterns/hjp5/html/k100268.html b/Master/Reference Architectures and Patterns/hjp5/html/k100268.html new file mode 100644 index 0000000..d5f6f9c --- /dev/null +++ b/Master/Reference Architectures and Patterns/hjp5/html/k100268.html @@ -0,0 +1,1187 @@ + + + +Handbuch der Java-Programmierung, 5. Auflage + + + + + + + + + +
 Titel  + Inhalt  + Suchen  + Index  + DOC  +Handbuch der Java-Programmierung, 5. Auflage +
 <<  +  <   +  >   + >>  + API  +Kapitel 42 - Datenbankzugriffe mit JDBC +
+
+ + + + +

42.3 Die DirDB-Beispieldatenbank

+
+ +
+ + + + +

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: +

+ + + + +

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 Abschluss 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 DVD 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: +

+ + +

+ + + + +
+ +
+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: +

+ + + + +

42.3.4 Anlegen und Füllen der Tabellen

+ +

+Unsere Anwendung geht davon aus, dass 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, dass 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, dass 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 Programms 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, dass 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, dass entweder der Stern +»*« anstelle des »%« als Wildcard erwartet wird +oder dass die Leerzeichen am Ende des Feldes als signifikant +angesehen werden und daher auch hinter dem Suchbegriff ein +Wildcard-Zeichen angegeben werden muss: + +

+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 allem 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 Einfluss 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, 5. Auflage, Addison +Wesley, Version 5.0.1 +
 <<  +  <   +  >   + >>  + API  +© 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de +
+ + + -- cgit v1.2.3