Volltextdatenbank mit MySQL und Java

Beispielsitzung einer Schlüsselwortsuche

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;                     ## verwende neu erstellte Datenbank

DROP TABLE IF EXISTS stichworte;   ## evtl. vorhandene Tabelle löschen und neu anlegen
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

Schreibe einen Kommentar

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