Headerbild: Stichwortsuche mit Java/MySQL

Volltextdatenbank mit MySQL und Java


Die Realisierung einer Volltextdatenbank mit MySQL und Java ist einfach. Ein Teilnehmer meines Seminars „MySQL – Vom Anfänger zum Experten“ hat mich auf die Idee gebracht. Der Teilnehmer wünschte sich eine Datenbank, die ihm hilft, die Videos zu bestimmten Aspekten des Seminars schnell wiederfinden zu können. Bei 151 Lektionen kann der Überblick, in welcher Lektion was genau behandelt wurde, tatsächlich schnell verloren gehen.

Nun, ich habe mir diesen geäußerten Wunsch zu eigen gemacht. Ich skizziere hier, wie man sich eine Volltextdatenbank mit MySQL und Java zur Stichwortsuche schnell und einfach implementieren kann. Das hier vorgestellte Projekt stellt keine vollumfängliche Anwendung dar. Der Code mag vielmehr Anregung für alle Interessierten sein, sich eine eigene Anwendung, ganz nach den persönlichen Bedürfnissen, zu bauen.

Anwendungsanforderungen

Es soll eine Datenbank entwickelt werden, die

  • Stichworte,
  • Erläuterungen und einen
  • Verweis auf die Seminarlektion

enthält. Die Datenbank soll mittels Volltextsuche Durchsicht werden können. Das Suchen soll von der Kommandozeile initiiert werden können, ohne erst ein anderes Datenbankwerkzeug starten zu müssen.

Das sind die Minimalanforderungen. Ohne viel Phantasie, kann sich jeder ausmalen, die gespeicherten Daten auszudehnen. So wäre es durchaus sinnvoll, die Lehrinhalte verschiedener Seminare in der Datenbank zu speichern. Ein entsprechender Hinweis auf das Seminar wäre dann wünschenswert. Ebenfalls denkbar ist es, Bücher, Zeitschriftenartikel mit entsprechenden Quellenverweisen zu integrieren. – Der Phantasie sind da kaum Grenzen gesetzt.

An dieser Stelle werde ich lediglich ein einfaches Client-Programm vorstellen, mit dem die Daten aus der Datenbank abgefragt werden können. Auch dieses mag ausgebaut werden. Es könnte dahingehend ausgestaltet werden, auch Datensätze darüber pflegen zu können und eine grafische Benutzeroberfläche wäre auch schön. Ebenfalls verlockend können verschiedene Ausgabeformate sein, wie beispielsweise bibtex, um Literaturangaben in LaTeX übernehmen zu können.

Systemanforderungen

Um eine Stichwortdatenbank mit MySQL und Java erstellen zu können, brauchen wir mindestens

  • Zugang zu einem MySQL-Server
  • und eine Java-Entwicklungsumgebung (JDK).

Den MySQL-Server können wir uns als Community-Edition von der MySQL-Homepage herunterladen. Dort finden wir ebenso den Java-Connector für MySQL (Connector/J). Weil ein Terminalprogramm seine Parameter üblicherweise in Form von Kommandozeilenargumenten empfängt, müssen wir in der Lage sein, die Kommandozeile zu parsen. Dazu habe ich hier commons-cli von Apache verwendet. Zu alledem brauchen wir natürlich auch noch das JDK.

Hier ist die Zusammenstellung der Links, zu den benötigten Ingredienzien:

Statt des hier verlinkten Oracle JDK kann natürlich auch OpenJDK verwendet werden.

Wenn nicht bereits geschehen, sind der MySQL-Server und auch das JDK zu installieren. Der Java-Connector beziehungsweise der Kommandozeilen-Perser sind im Klassensuchpfad für Java aufzunehmen. Unter macOS/Linux sieht das so aus:

export CLASSPATH=".:$HOME/Lib/Prog/mysql-connector.jar:$HOME/Lib/Prog/commons-cli.jar"

Die angegebenen Namen sind bei mir Links auf die echten Archivdateien, deren Namen etwas länger sind. Bei macOS sollte dies in der .bash_profile, unter Linux in der .profile stehen. Windows-Anwender setzen den Klassenpfad unter den Umgebungsvariablen in der Systemsteuerung.

Datenbank anlegen

Die Anforderungen an unsere Stichwort-Datenbank sind (im Moment) sehr gering. Es reicht eine einzige Tabelle. Um sie jedoch von anderen Projekten abzugrenzen, soll dieser Tabelle eine eigene Datenbank spendiert werden.

Wird das Projekt weiter ausgebaut, wird es im Mehrbenutzerbetrieb mit unterschiedlichen Berechtigungen für die Anwender verwendet, mag eine dedizierte Datenbank tatsächlich wünschenswert sein.

Hier nun das SQL-Skript. Es erzeugt eine Datenbank, legt die (derzeit einzige) Tabelle und einen Beispielanwender namens stwsucher an. Dieser Anwender kann und darf lediglich Daten abfragen.

DROP DATABASE IF EXISTS stichworte; -- ACHTUNG: bestehende Datenbank wird gelöscht!!!
CREATE DATABASE stichworte
  CHARACTER SET utf8
  COLLATE utf8_german2_ci;

USE stichworte;

DROP TABLE IF EXISTS stichworte;   

CREATE TABLE stichworte
(
  FTS_DOC_ID BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
  stichwort VARCHAR(100) NOT NULL,
  beschreibung VARCHAR(2000),
  lektion INT UNSIGNED
);
CREATE FULLTEXT INDEX ft_idx ON stichworte (stichwort, beschreibung);

-- ein paar Beispieldatensätze
INSERT INTO stichworte (stichwort, beschreibung, lektion)
VALUES
  ('create database', 'Anlegen einer Datenbank', 8),
  ('create table', 'Tabelle erstellen', 10),
  ('create table', 'Tabelle mit mehreren Spalten erstellen', 11),
  ('null', 'NULL-Werte', 12);

-- Beispielanwender anlegen
DROP USER IF EXISTS 'stwsucher'@'localhost';
CREATE USER 'stwsucher'@'localhost' IDENTIFIED BY '1234';
GRANT SELECT ON stichworte.stichworte TO 'stwsucher'@'localhost';

Dieses Skript beherbergt wenig Geheimnisse. Nennenswert ist jedoch das Attribut FTS_DOC_ID. Das Attribut wird von MySQL benötigt, um eine Volltextsuche zu gestatten beziehungsweise einen Volltextindex zu erstellen. Fehlt es, wird es von MySQL automatisch angelegt. Die Erzeugung des Volltextindizes wirft dann eine Warnung aus. Der Volltextindex wird wie jeder andere Index auch erzeugt. Allerdings wird das Schlüsselwort FULLTEXT verwendet. Im Beispiel werden die Spalten stichwort und beschreibung indiziert.

MySQL unterstützt verschiedene Formen der Volltextsuche. Diese lassen sich in der MySQL-Dokumentation unter der Überschrift Full-Text Search Functions nachlesen. Das hier vorgestellte Java-Programm verwendet lediglich die Modi NATURAL LANGUAGE und BOOLEAN. Bei der booleschen Suche stehen die folgende Operatoren zur Verfügung:

  • + bedeutet AND
  • - bedeute NOT
  • [kein Operator] bedeutet OR

Damit lassen sich dann zum Beispiel folgende Abfragen formulieren:

SELECT stichwort, beschreibung, lektion FROM stichworte
WHERE MATCH (stichwort, beschreibung)
AGAINST ('Tabelle viele Spalten' IN NATURAL LANGUAGE MODE);
stichwortbeschreibunglektion
create tableTabelle mit mehreren Spalten erstellen11
create tableTabelle erstellen10
SELECT stichwort, beschreibung, lektion FROM stichworte
WHERE MATCH (stichwort, beschreibung)
AGAINST ('+Tabelle viele +Spalten' IN BOOLEAN MODE);
stichwortbeschreibunglektion
create tableTabelle mit mehreren Spalten erstellen11

Die zweite Abfrage ist konkreter, weil sie zwingend das gemeinsame Vorkommen zweier Worte fordert.

Java-Client

Der folgende Programmcode zeigt beispielhaft den grundsätzlichen Aufbau eines Java-Programms, welches auf eine MySQL-Datenbank zugreift. Es verbleibt noch viel Luft nach oben. Fest im Programmcode verankerte Benutzerdaten, und andere Dinge mehr, sind kein guter Stil. Zur Verdeutlichung mag der Code aber ausreichen.

/*
Keyword - einfaches Suchprogramm fuer Stichworte
... kein perfektes Programm, mehr eine Anregung fuer
all diejenigen, die Lust und Laune haben mehr daraus
zu machen!

Hinweis: MySQL-Datenbank und Anwender muessen bestehen
(stichworte.sql)

Autor..: Karsten Brodmann (kb@punkt-akademie.de)
Version: 0.1
Datum..: 2017-11-01
*/
import java.sql.*;
import org.apache.commons.cli.*;

public class Keyword {
    // JDBC-Treiber Verbindungsdaten
    static final String JDBCDRIVER = "com.mysql.jdbc.Driver";
    static final String DBURL = "jdbc:mysql://localhost/stichworte?useSSL=false";
    static final String USER = "stwsucher";
    static final String PASS = "1234";

    // Datenbankverbindung
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;
    // Programmparameter
    String searchfor;
    boolean booleanmode = false;

    // Benutzerhinweise
    private static void printUsage(Options opts)
    {
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp("Keyword", opts);
        System.exit(1);
    }

    // DB-Verbindung herstellen
    private void getConnection() throws ClassNotFoundException, SQLException
    {
        Class.forName(JDBCDRIVER);
        conn = DriverManager.getConnection(DBURL,USER,PASS);
    }

    // Programmoptionen auslesen, im Fehlerfall Programm beenden
    // gültige Optionen: -s --search (Suchstring)
    // -b --bool (boolesche Suche)
    private void getOptions(String[] args)
    {
        Options options = new Options();

        Option searchstring = new Option("s", "search", true, "searchstring");
        searchstring.setRequired(true);
        options.addOption(searchstring);

        Option booleansearch = new Option("b", "bool", false, "boolean search");
        booleansearch.setRequired(false);
        options.addOption(booleansearch);

        CommandLineParser parser = new DefaultParser();
        CommandLine cmd = null;
        try {
            cmd = parser.parse(options, args);
        } catch (ParseException e) {
            System.out.println(e.getMessage());
            printUsage(options); // beendet Programm
        }
        searchfor = cmd.getOptionValue("s");
        if(cmd.hasOption("b")) {
            booleanmode = true;
        }
    }

    // SSQL-Statement basteln
    private String createSQL(String searchfor, boolean booleanmode)
    {
        String sql;

        sql = "SELECT stichwort, beschreibung, lektion ";
        sql = sql + "FROM stichworte ";
        sql = sql + "WHERE MATCH (stichwort, beschreibung) ";
        sql = sql + "AGAINST ('" + searchfor;
        if (booleanmode) {
            sql = sql + "' IN BOOLEAN MODE)";
        } else {
            sql = sql + "' IN NATURAL LANGUAGE MODE)";
        }

        return sql;
    }

    // Ergebnisse ausgeben
    private void printResult(int no, String kw, String desc, String lec)
    {
        String hline = "----------------------------------------";

        if (no == 1) {
            System.out.println(hline);
        }
        System.out.println("Treffer.....: " + no);
        System.out.println("Stichwort...: " + kw);
        System.out.println("Beschreibung: " + desc);
        System.out.println("Lektion.....: " + lec);
        System.out.println(hline);
    }

    // Ergebnisse aus DB abrufen
    private void getResults() throws SQLException
    {
        stmt = conn.createStatement();
        rs = stmt.executeQuery(createSQL(searchfor, booleanmode));
        int no = 0;
        while(rs.next()) {
            no++;
            printResult(no, rs.getString("stichwort"),
                rs.getString("beschreibung"),
                rs.getString("lektion"));
        }
        System.out.println("(" + no + " Treffer insgesamt)" );
        rs.close();
    }

    // Aufraeumarbeiten
    private void cleanup() {
        try {
            if (stmt!=null)
                stmt.close();
        } catch(SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn!=null)
                conn.close();
        } catch(SQLException e) {
            e.printStackTrace();
        }
    }

    // Hauptprogramm
    public static void main(String[] args)
    {
        Keyword kw = new Keyword();
        kw.getOptions(args);
        try {
            kw.getConnection();
            kw.getResults();
            kw.cleanup();
        } catch(SQLException e) {
    // JDBC-Fehler abfangen
            e.printStackTrace();
        } catch(Exception e) {
    // andere Fehler abfangen
            e.printStackTrace();
        } finally {
    // auf jeden Fall aufraeumen
            kw.cleanup();
        }
    }
}

Das Programm zeigt ganz rudimentär die erforderlichen Schritte, sich mit einer MySQL-Datenbank zu verbinden und Daten abzufragen.

Eine Beispielsitzung unter macOS oder Linux könnte so ausschauen:

$ java Keyword --search '+Tabelle viele +Spalten' --bool
----------------------------------------
Treffer.....: 1
Stichwort...: create table
Beschreibung: Tabelle mit mehreren Spalten erstellen
Lektion.....: 11
----------------------------------------
(1 Treffer insgesamt)
$ _

Fazit

Der Bedarf an Optimierungen und Erweiterungen ist unübersehbar. Das Prinzip, wie eine Stichwortdatenbank mit MySQL und Java aufgebaut und implementiert werden kann, sollte aber offensichtlich sein. – Es ist nicht schwierig.

Laden Sie hier den Beispielcode herunter.

DOWNLOAD

Auf Basis der oben gezeigten Grundlagen können leicht weitere Features implementiert werden, so dass das Beispiel schnell zu einer „ausgewachsenen Anwendung“ wachsen kann. – Viel Spaß dabei!

Karsten Brodmann

Karsten Brodmann

Karsten Brodmann hat an der Universität Osnabrück BWL/Wirtschaftsinformatik studiert. Er hat viele Jahre in der IT gearbeitet und dort Web- und Datenbankanwendungen entwickelt. Seit Gründung der Punkt-Akademie veröffentlicht Karsten Brodmann auch Schulungsvideos zur Datenbankentwicklung, Unix und Programmierung bei Udemy. In seiner Freizeit fotografiert Karsten Brodmann gerne. Seit vielen Jahren fotografiert er analog und digital. Dabei behält er jeweils den gesamten Workflow in der eigenen Hand, von der Aufnahme über die Dunkelkammer oder auch den Scanner sowie die Bildbearbeitung und den Ausdruck am PC.

Weitere Beiträge

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.